Skip to content

Commit a6901ae

Browse files
authored
chore: fix race in cron close behavior (TestAgent_WriteVSCodeConfigs) (coder#11243)
* chore: add unit test to excercise flake * Implement a *fix for cron stop() before run() This fix still has a race condition. I do not see a clean solution without modifying the cron libary. The cron library uses a boolean to indicate running, and that boolean needs to be set to "true" before we call "Close()". Or "Close()" should prevent "Run()" from doing anything. In either case, this solves the issue for a niche unit test bug in which the test finishes, calling Close(), before there was an oppertunity to start the go routine. It probably isn't worth a lot of time investment, and this fix will suffice
1 parent 56cbd47 commit a6901ae

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

agent/agentscripts/agentscripts.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,18 @@ func (r *Runner) StartCron() {
129129
// has exited by the time the `cron.Stop()` context returns, so we need to
130130
// track it manually.
131131
err := r.trackCommandGoroutine(func() {
132-
r.cron.Run()
132+
// Since this is run async, in quick unit tests, it is possible the
133+
// Close() function gets called before we even start the cron.
134+
// In these cases, the Run() will never end.
135+
// So if we are closed, we just return, and skip the Run() entirely.
136+
select {
137+
case <-r.cronCtx.Done():
138+
// The cronCtx is cancelled before cron.Close() happens. So if the ctx is
139+
// cancelled, then Close() will be called, or it is about to be called.
140+
// So do nothing!
141+
default:
142+
r.cron.Run()
143+
}
133144
})
134145
if err != nil {
135146
r.Logger.Warn(context.Background(), "start cron failed", slog.Error(err))
@@ -315,6 +326,7 @@ func (r *Runner) Close() error {
315326
return nil
316327
}
317328
close(r.closed)
329+
// Must cancel the cron ctx BEFORE stopping the cron.
318330
r.cronCtxCancel()
319331
<-r.cron.Stop().Done()
320332
r.cmdCloseWait.Wait()

agent/agentscripts/agentscripts_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ func TestTimeout(t *testing.T) {
5353
require.ErrorIs(t, runner.Execute(context.Background(), nil), agentscripts.ErrTimeout)
5454
}
5555

56+
// TestCronClose exists because cron.Run() can happen after cron.Close().
57+
// If this happens, there used to be a deadlock.
58+
func TestCronClose(t *testing.T) {
59+
t.Parallel()
60+
runner := agentscripts.New(agentscripts.Options{})
61+
runner.StartCron()
62+
require.NoError(t, runner.Close(), "close runner")
63+
}
64+
5665
func setup(t *testing.T, patchLogs func(ctx context.Context, req agentsdk.PatchLogs) error) *agentscripts.Runner {
5766
t.Helper()
5867
if patchLogs == nil {

0 commit comments

Comments
 (0)