container/dispatcher: start goroutine in dispatcher
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m13s
Test / Hpkg (push) Successful in 4m1s
Test / Sandbox (race detector) (push) Successful in 4m28s
Test / Hakurei (race detector) (push) Successful in 5m6s
Test / Hakurei (push) Successful in 2m24s
Test / Flake checks (push) Successful in 1m38s
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m13s
Test / Hpkg (push) Successful in 4m1s
Test / Sandbox (race detector) (push) Successful in 4m28s
Test / Hakurei (race detector) (push) Successful in 5m6s
Test / Hakurei (push) Successful in 2m24s
Test / Flake checks (push) Successful in 1m38s
This allows instrumentation of calls from goroutine without relying on finalizers. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
b3da3da525
commit
0166833431
@ -22,10 +22,10 @@ type osFile interface {
|
|||||||
|
|
||||||
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
|
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
|
||||||
type syscallDispatcher interface {
|
type syscallDispatcher interface {
|
||||||
// new returns a new instance of syscallDispatcher for use in another goroutine.
|
// new starts a goroutine with a new instance of syscallDispatcher.
|
||||||
// A syscallDispatcher must never be used in any goroutine other than the one owning it,
|
// A syscallDispatcher must never be used in any goroutine other than the one owning it,
|
||||||
// just synchronising access is not enough, as this is for test instrumentation.
|
// just synchronising access is not enough, as this is for test instrumentation.
|
||||||
new() syscallDispatcher
|
new(f func(k syscallDispatcher))
|
||||||
|
|
||||||
// lockOSThread provides [runtime.LockOSThread].
|
// lockOSThread provides [runtime.LockOSThread].
|
||||||
lockOSThread()
|
lockOSThread()
|
||||||
@ -145,7 +145,7 @@ type syscallDispatcher interface {
|
|||||||
// direct implements syscallDispatcher on the current kernel.
|
// direct implements syscallDispatcher on the current kernel.
|
||||||
type direct struct{}
|
type direct struct{}
|
||||||
|
|
||||||
func (k direct) new() syscallDispatcher { return k }
|
func (k direct) new(f func(k syscallDispatcher)) { go f(k) }
|
||||||
|
|
||||||
func (direct) lockOSThread() { runtime.LockOSThread() }
|
func (direct) lockOSThread() { runtime.LockOSThread() }
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -113,7 +114,7 @@ type simpleTestCase struct {
|
|||||||
func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
k := &kstub{t: t, want: tc.want}
|
k := &kstub{t: t, want: tc.want, wg: new(sync.WaitGroup)}
|
||||||
if err := tc.f(k); !errors.Is(err, tc.wantErr) {
|
if err := tc.f(k); !errors.Is(err, tc.wantErr) {
|
||||||
t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
|
t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
|
||||||
}
|
}
|
||||||
@ -141,7 +142,7 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
state := &setupState{Params: tc.params}
|
state := &setupState{Params: tc.params}
|
||||||
k := &kstub{t: t, want: [][]kexpect{slices.Concat(tc.early, []kexpect{{name: "\x00"}}, tc.apply)}}
|
k := &kstub{t: t, want: [][]kexpect{slices.Concat(tc.early, []kexpect{{name: "\x00"}}, tc.apply)}, wg: new(sync.WaitGroup)}
|
||||||
errEarly := tc.op.early(state, k)
|
errEarly := tc.op.early(state, k)
|
||||||
k.expect("\x00")
|
k.expect("\x00")
|
||||||
if !errors.Is(errEarly, tc.wantErrEarly) {
|
if !errors.Is(errEarly, tc.wantErrEarly) {
|
||||||
@ -272,10 +273,14 @@ type kstub struct {
|
|||||||
track int
|
track int
|
||||||
// sub stores addresses of kstub created by new.
|
// sub stores addresses of kstub created by new.
|
||||||
sub []*kstub
|
sub []*kstub
|
||||||
|
// wg waits for all descendants to complete.
|
||||||
|
wg *sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleIncomplete calls f on an incomplete k and all its descendants.
|
// handleIncomplete calls f on an incomplete k and all its descendants.
|
||||||
func (k *kstub) handleIncomplete(f func(k *kstub)) {
|
func (k *kstub) handleIncomplete(f func(k *kstub)) {
|
||||||
|
k.wg.Wait()
|
||||||
|
|
||||||
if k.want != nil && len(k.want[k.track]) != k.pos {
|
if k.want != nil && len(k.want[k.track]) != k.pos {
|
||||||
f(k)
|
f(k)
|
||||||
}
|
}
|
||||||
@ -331,13 +336,15 @@ func checkArgReflect(k *kstub, arg string, got any, n int) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) new() syscallDispatcher {
|
func (k *kstub) new(f func(k syscallDispatcher)) {
|
||||||
k.expect("new")
|
k.expect("new")
|
||||||
if len(k.want) <= k.track+1 {
|
if len(k.want) <= k.track+1 {
|
||||||
k.t.Fatalf("new: track overrun")
|
k.t.Fatalf("new: track overrun")
|
||||||
}
|
}
|
||||||
k.sub = append(k.sub, &kstub{t: k.t, want: k.want, track: k.track + 1})
|
sk := &kstub{t: k.t, want: k.want, track: len(k.sub) + 1, wg: k.wg}
|
||||||
return k.sub[len(k.sub)-1]
|
k.sub = append(k.sub, sk)
|
||||||
|
k.wg.Add(1)
|
||||||
|
go func() { defer k.wg.Done(); f(sk) }()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) lockOSThread() { k.expect("lockOSThread") }
|
func (k *kstub) lockOSThread() { k.expect("lockOSThread") }
|
||||||
|
@ -178,6 +178,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
fmt.Sprintf("cannot prepare op at index %d:", i))
|
fmt.Sprintf("cannot prepare op at index %d:", i))
|
||||||
k.beforeExit()
|
k.beforeExit()
|
||||||
k.exit(1)
|
k.exit(1)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,6 +219,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
fmt.Sprintf("cannot apply op at index %d:", i))
|
fmt.Sprintf("cannot apply op at index %d:", i))
|
||||||
k.beforeExit()
|
k.beforeExit()
|
||||||
k.exit(1)
|
k.exit(1)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +335,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
info := make(chan winfo, 1)
|
info := make(chan winfo, 1)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
||||||
go func(k syscallDispatcher) {
|
k.new(func(k syscallDispatcher) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
wpid = -2
|
wpid = -2
|
||||||
@ -360,7 +362,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
}
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
}(k.new())
|
})
|
||||||
|
|
||||||
// handle signals to dump withheld messages
|
// handle signals to dump withheld messages
|
||||||
sig := make(chan os.Signal, 2)
|
sig := make(chan os.Signal, 2)
|
||||||
@ -385,7 +387,9 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
k.beforeExit()
|
||||||
k.exit(0)
|
k.exit(0)
|
||||||
|
return
|
||||||
|
|
||||||
case w := <-info:
|
case w := <-info:
|
||||||
if w.wpid == cmd.Process.Pid {
|
if w.wpid == cmd.Process.Pid {
|
||||||
@ -396,9 +400,11 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
case w.wstatus.Exited():
|
case w.wstatus.Exited():
|
||||||
r = w.wstatus.ExitStatus()
|
r = w.wstatus.ExitStatus()
|
||||||
k.verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
|
k.verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
|
||||||
|
|
||||||
case w.wstatus.Signaled():
|
case w.wstatus.Signaled():
|
||||||
r = 128 + int(w.wstatus.Signal())
|
r = 128 + int(w.wstatus.Signal())
|
||||||
k.verbosef("initial process exited with signal %s", w.wstatus.Signal())
|
k.verbosef("initial process exited with signal %s", w.wstatus.Signal())
|
||||||
|
|
||||||
default:
|
default:
|
||||||
r = 255
|
r = 255
|
||||||
k.verbosef("initial process exited with status %#x", w.wstatus)
|
k.verbosef("initial process exited with status %#x", w.wstatus)
|
||||||
@ -410,11 +416,13 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
|
|||||||
case <-done:
|
case <-done:
|
||||||
k.beforeExit()
|
k.beforeExit()
|
||||||
k.exit(r)
|
k.exit(r)
|
||||||
|
return
|
||||||
|
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
k.printf("timeout exceeded waiting for lingering processes")
|
k.printf("timeout exceeded waiting for lingering processes")
|
||||||
k.beforeExit()
|
k.beforeExit()
|
||||||
k.exit(r)
|
k.exit(r)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user