diff --git a/.changelog/11741.txt b/.changelog/11741.txt new file mode 100644 index 000000000000..1302fc2e3e39 --- /dev/null +++ b/.changelog/11741.txt @@ -0,0 +1,3 @@ +```release-note:bug +client: Fixed a memory and goroutine leak for batch tasks and any task that exits without being shut down from the server +``` diff --git a/client/allocrunner/interfaces/task_lifecycle.go b/client/allocrunner/interfaces/task_lifecycle.go index ee99a507bd50..e02b1366faaf 100644 --- a/client/allocrunner/interfaces/task_lifecycle.go +++ b/client/allocrunner/interfaces/task_lifecycle.go @@ -89,7 +89,9 @@ type TaskPrestartHook interface { // Prestart is called before the task is started including after every // restart. Prestart is not called if the allocation is terminal. // - // The context is cancelled if the task is killed or shutdown. + // The context is cancelled if the task is killed or shutdown but + // should not be stored any persistent goroutines this Prestart + // creates. Prestart(context.Context, *TaskPrestartRequest, *TaskPrestartResponse) error } diff --git a/client/allocrunner/taskrunner/task_runner_hooks.go b/client/allocrunner/taskrunner/task_runner_hooks.go index c9ff34441ed5..a92194d48ecf 100644 --- a/client/allocrunner/taskrunner/task_runner_hooks.go +++ b/client/allocrunner/taskrunner/task_runner_hooks.go @@ -190,6 +190,11 @@ func (tr *TaskRunner) prestart() error { }() } + // use a join context to allow any blocking pre-start hooks + // to be canceled by either killCtx or shutdownCtx + joinedCtx, joinedCancel := joincontext.Join(tr.killCtx, tr.shutdownCtx) + defer joinedCancel() + for _, hook := range tr.runnerHooks { pre, ok := hook.(interfaces.TaskPrestartHook) if !ok { @@ -235,9 +240,6 @@ func (tr *TaskRunner) prestart() error { } // Run the prestart hook - // use a joint context to allow any blocking pre-start hooks - // to be canceled by either killCtx or shutdownCtx - joinedCtx, _ := joincontext.Join(tr.killCtx, tr.shutdownCtx) var resp interfaces.TaskPrestartResponse if err := pre.Prestart(joinedCtx, &req, &resp); err != nil { tr.emitHookError(err, name)