| 1 | //go:build windows |
| 2 | |
| 3 | package sidecar |
| 4 | |
| 5 | import ( |
| 6 | "errors" |
| 7 | "sync" |
| 8 | "testing" |
| 9 | |
| 10 | "golang.org/x/sys/windows" |
| 11 | |
| 12 | "reasonix/internal/proc" |
| 13 | ) |
| 14 | |
| 15 | func TestReleasedJobHandleIsNeverClosedAgain(t *testing.T) { |
| 16 | for _, first := range []string{"kill", "finish"} { |
| 17 | t.Run(first, func(t *testing.T) { |
| 18 | cmd := proc.Command("cmd", "/c", "exit", "0") |
| 19 | if err := cmd.Run(); err != nil { |
| 20 | t.Fatal(err) |
| 21 | } |
| 22 | job, err := windows.CreateJobObject(nil, nil) |
| 23 | if err != nil { |
| 24 | t.Fatal(err) |
| 25 | } |
| 26 | p := &process{cmd: cmd, job: uintptr(job)} |
| 27 | if first == "kill" { |
| 28 | p.kill() |
| 29 | } else { |
| 30 | p.finishJob() |
| 31 | } |
| 32 | if err := windows.SetHandleInformation(job, 0, 0); !errors.Is(err, windows.ERROR_INVALID_HANDLE) { |
| 33 | t.Fatalf("completed cleanup did not invalidate the Job Object: %v", err) |
| 34 | } |
| 35 | event, err := windows.CreateEvent(nil, 0, 0, nil) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | defer windows.CloseHandle(event) |
| 40 | // Model Windows reusing the stale handle for an unrelated object; |
| 41 | // choosing its value explicitly avoids depending on allocator timing. |
| 42 | p.job = uintptr(event) |
| 43 | var calls sync.WaitGroup |
| 44 | for range 8 { |
| 45 | calls.Go(p.kill) |
| 46 | calls.Go(p.finishJob) |
| 47 | } |
| 48 | calls.Wait() |
| 49 | if err := windows.SetEvent(event); err != nil { |
| 50 | t.Fatalf("cleanup closed an unrelated reused handle: %v", err) |
| 51 | } |
| 52 | }) |
| 53 | } |
| 54 | } |
| 55 |