carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package rootparticipation
4
5import (
6 "context"
7 "errors"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "syscall"
12 "testing"
13 "time"
14)
15
16type recoveryDisk struct{ testDisk }
17
18func (d *recoveryDisk) Write(name string, raw []byte) error {
19 f, err := os.CreateTemp(d.dir, "write-")
20 if err != nil {
21 return err
22 }
23 tmp := f.Name()
24 defer os.Remove(tmp)
25 if _, err = f.Write(raw); err == nil {
26 err = f.Sync()
27 }
28 err = errors.Join(err, f.Close())
29 if err != nil {
30 return err
31 }
32 if err = os.Rename(tmp, filepath.Join(d.dir, name)); err != nil {
33 return err
34 }
35 dir, err := os.Open(d.dir)
36 if err != nil {
37 return err
38 }
39 defer dir.Close()
40 return dir.Sync()
41}
42func (d *recoveryDisk) Lock(ctx context.Context, name string) (func(), error) {
43 f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_CREATE|os.O_RDWR, 0600)
44 if err != nil {
45 return nil, err
46 }
47 for {
48 err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
49 if err == nil {
50 return func() { _ = syscall.Flock(int(f.Fd()), syscall.LOCK_UN); _ = f.Close() }, nil
51 }
52 if err != syscall.EWOULDBLOCK {
53 f.Close()
54 return nil, err
55 }
56 select {
57 case <-ctx.Done():
58 f.Close()
59 return nil, ctx.Err()
60 case <-time.After(time.Millisecond):
61 }
62 }
63}
64
65func TestPlan14ExecutionRecoveryAfterProcessCrash(t *testing.T) {
66 if dir := os.Getenv("PLAN14_EXEC_CRASH_DIR"); dir != "" {
67 d := &recoveryDisk{testDisk: testDisk{dir: dir}}
68 l := Ledger{State: d, VerifyLaunches: func(context.Context, uint64) error { return nil }}
69 crash := func() error { os.Exit(31); return nil }
70 var err error
71 if os.Getenv("PLAN14_EXEC_CRASH_AT") == "acquire" {
72 err = l.Acquire(t.Context(), crash)
73 } else {
74 err = l.BeginUpdateWith(t.Context(), "update", crash)
75 }
76 t.Fatal("did not reach crash", err)
77 }
78 for _, at := range []string{"acquire", "update"} {
79 t.Run(at, func(t *testing.T) {
80 l := censusFixture(t)
81 dir := l.State.(*testDisk).dir
82 l.State = &recoveryDisk{testDisk: testDisk{dir: dir}}
83 child := exec.Command(os.Args[0], "-test.run=^TestPlan14ExecutionRecoveryAfterProcessCrash$")
84 child.Env = append(os.Environ(), "PLAN14_EXEC_CRASH_DIR="+dir, "PLAN14_EXEC_CRASH_AT="+at)
85 child.Dir = dir
86 out, err := child.CombinedOutput()
87 var exit *exec.ExitError
88 if !errors.As(err, &exit) || exit.ExitCode() != 31 {
89 t.Fatalf("crash: %v %s", err, out)
90 }
91 if err := l.Acquire(t.Context(), func() error { t.Error("acquired across crash"); return nil }); err == nil {
92 t.Fatal("crash grant")
93 }
94 if at == "acquire" {
95 if _, err := l.Census(); err == nil {
96 t.Fatal("pending acquisition accepted")
97 }
98 l.VerifyLaunches = nil
99 if err := l.RecoverAcquisition(t.Context()); err == nil {
100 t.Fatal("absence replaced kernel completeness")
101 }
102 l.VerifyLaunches = func(context.Context, uint64) error { return nil }
103 if err := l.RecoverAcquisition(t.Context()); err != nil {
104 t.Fatal(err)
105 }
106 } else {
107 failed := errors.New("terminal write failed")
108 if err := l.EndUpdateWith(t.Context(), "update", func() error { return failed }); !errors.Is(err, failed) {
109 t.Fatal(err)
110 }
111 if _, err := l.UpdateID(); err != nil {
112 t.Fatal("terminal failure lost exclusion", err)
113 }
114 if err := l.EndUpdate(t.Context(), "update"); err != nil {
115 t.Fatal(err)
116 }
117 }
118 if err := l.Acquire(t.Context(), func() error { return nil }); err != nil {
119 t.Fatal("positive recovery did not reopen", err)
120 }
121 })
122 }
123}
124