3 Commits

Author SHA1 Message Date
e7fc311d0b internal/outcome/shim: cover reparent and exit request paths
All checks were successful
Test / Create distribution (push) Successful in 26s
Test / Hakurei (push) Successful in 42s
Test / Sandbox (push) Successful in 39s
Test / Sandbox (race detector) (push) Successful in 39s
Test / Hakurei (race detector) (push) Successful in 43s
Test / Hpkg (push) Successful in 41s
Test / Flake checks (push) Successful in 1m31s
These test cases were missed when making the changes.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-02 11:58:09 +09:00
f5274067f6 internal/outcome/process: nil-safe unlock when failing to lock
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Hpkg (push) Successful in 4m13s
Test / Sandbox (push) Successful in 2m10s
Test / Hakurei (push) Successful in 3m9s
Test / Sandbox (race detector) (push) Successful in 4m7s
Test / Hakurei (race detector) (push) Successful in 4m57s
Test / Flake checks (push) Successful in 1m26s
This also prints a debug message which might be useful.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-02 11:47:51 +09:00
e7161f8e61 internal/outcome: measure finalise time
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m16s
Test / Hakurei (push) Successful in 3m11s
Test / Hpkg (push) Successful in 4m8s
Test / Flake checks (push) Successful in 1m19s
Test / Sandbox (race detector) (push) Successful in 4m4s
Test / Hakurei (race detector) (push) Successful in 4m56s
This also increases precision of state time output.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-11-02 05:17:33 +09:00
4 changed files with 49 additions and 11 deletions

View File

@@ -690,6 +690,7 @@ func (panicMsgContext) Value(any) any { panic("unreachable") }
type panicDispatcher struct{}
func (panicDispatcher) new(func(k syscallDispatcher, msg message.Msg)) { panic("unreachable") }
func (panicDispatcher) getppid() int { panic("unreachable") }
func (panicDispatcher) getpid() int { panic("unreachable") }
func (panicDispatcher) getuid() int { panic("unreachable") }
func (panicDispatcher) getgid() int { panic("unreachable") }

View File

@@ -3,6 +3,7 @@ package outcome
import (
"context"
"log"
"time"
"hakurei.app/hst"
"hakurei.app/message"
@@ -16,10 +17,13 @@ func Main(ctx context.Context, msg message.Msg, config *hst.Config) {
}
seal := outcome{syscallDispatcher: direct{msg}}
finaliseTime := time.Now()
if err := seal.finalise(ctx, msg, &id, config); err != nil {
printMessageError(msg.GetLogger().Fatalln, "cannot seal app:", err)
panic("unreachable")
}
msg.Verbosef("finalise took %.2f ms", float64(time.Since(finaliseTime).Nanoseconds())/1e6)
seal.main(msg)
panic("unreachable")

View File

@@ -4,7 +4,6 @@ import (
"context"
"encoding/gob"
"errors"
"iter"
"math"
"os"
"os/exec"
@@ -110,14 +109,13 @@ func (k *outcome) main(msg message.Msg) {
)
for {
var processTimePrev time.Time
processTimePrev, processTime = processTime, time.Now()
var processStatePrev uintptr
processStatePrev, processStateCur = processStateCur, processState
if !processTimePrev.IsZero() && processStatePrev != processLifecycle {
msg.Verbosef("state %d took %d ms", processStatePrev, processTime.Sub(processTimePrev).Milliseconds())
if !processTime.IsZero() && processStatePrev != processLifecycle {
msg.Verbosef("state %d took %.2f ms", processStatePrev, float64(time.Since(processTime).Nanoseconds())/1e6)
}
processTime = time.Now()
switch processState {
case processStart:
@@ -238,13 +236,15 @@ func (k *outcome) main(msg message.Msg) {
// this state transition to processFinal only
processState = processFinal
unlock, err := handle.Lock()
if err != nil {
unlock := func() { msg.Verbose("skipping unlock as lock was not successfully acquired") }
if f, err := handle.Lock(); err != nil {
perror(err, "acquire lock on store segment")
} else {
unlock = f
}
if entryHandle != nil {
if err = entryHandle.Destroy(); err != nil {
if err := entryHandle.Destroy(); err != nil {
perror(err, "destroy state entry")
}
}
@@ -252,8 +252,7 @@ func (k *outcome) main(msg message.Msg) {
if isBeforeRevert {
ec := system.Process
var entries iter.Seq[*store.EntryHandle]
if entries, _, err = handle.Entries(); err != nil {
if entries, _, err := handle.Entries(); err != nil {
// it is impossible to continue from this point,
// per-process state will be reverted to limit damage
perror(err, "read store segment entries")
@@ -288,7 +287,7 @@ func (k *outcome) main(msg message.Msg) {
}
}
if err = k.sys.Revert((*system.Criteria)(&ec)); err != nil {
if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil {
var joinError interface {
Unwrap() []error
error

View File

@@ -3,6 +3,7 @@ package outcome
import (
"bytes"
"context"
"io"
"log"
"os"
"syscall"
@@ -138,6 +139,19 @@ func TestShimEntrypoint(t *testing.T) {
call("fatalf", stub.ExpectArgs{"cannot set SUID_DUMP_DISABLE: %v", []any{stub.UniqueError(11)}}, nil, nil),
}}, nil},
{"receive exit request", func(k *kstub) error { shimEntrypoint(k); return nil }, stub.Expect{Calls: []stub.Call{
call("getMsg", stub.ExpectArgs{}, nil, nil),
call("getLogger", stub.ExpectArgs{}, (*log.Logger)(nil), nil),
call("setDumpable", stub.ExpectArgs{uintptr(container.SUID_DUMP_DISABLE)}, nil, nil),
call("getppid", stub.ExpectArgs{}, 0xbad, nil),
call("setupContSignal", stub.ExpectArgs{0xbad}, 0, nil),
call("receive", stub.ExpectArgs{"HAKUREI_SHIM", outcomeState{}, nil}, nil, io.EOF),
call("exit", stub.ExpectArgs{hst.ExitRequest}, stub.PanicExit, nil),
// deferred
call("wKeepAlive", stub.ExpectArgs{}, nil, nil),
}}, nil},
{"receive fd", func(k *kstub) error { shimEntrypoint(k); return nil }, stub.Expect{Calls: []stub.Call{
call("getMsg", stub.ExpectArgs{}, nil, nil),
call("getLogger", stub.ExpectArgs{}, (*log.Logger)(nil), nil),
@@ -177,6 +191,26 @@ func TestShimEntrypoint(t *testing.T) {
call("wKeepAlive", stub.ExpectArgs{}, nil, nil),
}}, nil},
{"reparent", func(k *kstub) error { shimEntrypoint(k); return nil }, stub.Expect{Calls: []stub.Call{
call("getMsg", stub.ExpectArgs{}, nil, nil),
call("getLogger", stub.ExpectArgs{}, (*log.Logger)(nil), nil),
call("setDumpable", stub.ExpectArgs{uintptr(container.SUID_DUMP_DISABLE)}, nil, nil),
call("getppid", stub.ExpectArgs{}, 0xbad, nil),
call("setupContSignal", stub.ExpectArgs{0xbad}, 0, nil),
call("receive", stub.ExpectArgs{"HAKUREI_SHIM", func() outcomeState {
state := templateState
state.Shim = newShimParams()
state.Shim.PrivPID = 0xfff
return state
}(), nil}, nil, nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbosef", stub.ExpectArgs{"process share directory at %q, runtime directory at %q", []any{m("/tmp/hakurei.10"), m("/run/user/1000/hakurei")}}, nil, nil),
call("fatalf", stub.ExpectArgs{"unexpectedly reparented from %d to %d", []any{0xfff, 0xbad}}, nil, nil),
// deferred
call("wKeepAlive", stub.ExpectArgs{}, nil, nil),
}}, nil},
{"invalid state", func(k *kstub) error { shimEntrypoint(k); return nil }, stub.Expect{Calls: []stub.Call{
call("getMsg", stub.ExpectArgs{}, nil, nil),
call("getLogger", stub.ExpectArgs{}, (*log.Logger)(nil), nil),