mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: fix data race in agentscripts.Runner (#17630)
Fixes https://github.com/coder/internal/issues/604 Fixes a data race in `agentscripts.Runner` where a concurrent `Execute()` call races with `Init()`. We hit this race during shut down, which is not synchronized against starting up. In this PR I've chosen to add synchronization to the `Runner` rather than try to synchronize the calls in the agent. When we close down the agent, it's OK to just throw an error if we were never initialized with a startup script---we don't want to wait for it since that requires an active connection to the control plane.
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -104,7 +103,6 @@ type Runner struct {
|
||||
closed chan struct{}
|
||||
closeMutex sync.Mutex
|
||||
cron *cron.Cron
|
||||
initialized atomic.Bool
|
||||
scripts []runnerScript
|
||||
dataDir string
|
||||
scriptCompleted ScriptCompletedFunc
|
||||
@@ -113,6 +111,9 @@ type Runner struct {
|
||||
// execute startup scripts, and scripts on a cron schedule. Both will increment
|
||||
// this counter.
|
||||
scriptsExecuted *prometheus.CounterVec
|
||||
|
||||
initMutex sync.Mutex
|
||||
initialized bool
|
||||
}
|
||||
|
||||
// DataDir returns the directory where scripts data is stored.
|
||||
@@ -154,10 +155,12 @@ func WithPostStartScripts(scripts ...codersdk.WorkspaceAgentScript) InitOption {
|
||||
// It also schedules any scripts that have a schedule.
|
||||
// This function must be called before Execute.
|
||||
func (r *Runner) Init(scripts []codersdk.WorkspaceAgentScript, scriptCompleted ScriptCompletedFunc, opts ...InitOption) error {
|
||||
if r.initialized.Load() {
|
||||
r.initMutex.Lock()
|
||||
defer r.initMutex.Unlock()
|
||||
if r.initialized {
|
||||
return xerrors.New("init: already initialized")
|
||||
}
|
||||
r.initialized.Store(true)
|
||||
r.initialized = true
|
||||
r.scripts = toRunnerScript(scripts...)
|
||||
r.scriptCompleted = scriptCompleted
|
||||
for _, opt := range opts {
|
||||
@@ -227,6 +230,18 @@ const (
|
||||
|
||||
// Execute runs a set of scripts according to a filter.
|
||||
func (r *Runner) Execute(ctx context.Context, option ExecuteOption) error {
|
||||
initErr := func() error {
|
||||
r.initMutex.Lock()
|
||||
defer r.initMutex.Unlock()
|
||||
if !r.initialized {
|
||||
return xerrors.New("execute: not initialized")
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if initErr != nil {
|
||||
return initErr
|
||||
}
|
||||
|
||||
var eg errgroup.Group
|
||||
for _, script := range r.scripts {
|
||||
runScript := (option == ExecuteStartScripts && script.RunOnStart) ||
|
||||
|
||||
Reference in New Issue
Block a user