From 854a26f0d4f3d1395238d76791b10e7c61fcc411 Mon Sep 17 00:00:00 2001 From: Thong Doan Date: Sat, 13 Jun 2026 21:10:00 +0700 Subject: [PATCH 1/2] fix: resolve data races in concurrent task execution --- workspace/task/runner.go | 22 +++--------- workspace/task/runner_test.go | 8 ++--- workspace/task/task.go | 65 ++++++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/workspace/task/runner.go b/workspace/task/runner.go index 0ba04c6..0e043f7 100644 --- a/workspace/task/runner.go +++ b/workspace/task/runner.go @@ -36,33 +36,19 @@ func (r *Runner) Run(ctx context.Context) { defer wg.Done() if ctx.Err() != nil { - task.mu.Lock() - task.State = StateFailed - task.Err = ctx.Err() - task.History = append(task.History, StateFailed) - task.mu.Unlock() + task.Fail(ctx.Err()) return } - task.mu.Lock() - task.State = StateRunning - task.StartedAt = time.Now() - task.History = append(task.History, StateRunning) - task.mu.Unlock() + task.Start() err := task.Action(ctx) - task.mu.Lock() - task.FinishedAt = time.Now() if err != nil { - task.State = StateFailed - task.Err = err - task.History = append(task.History, StateFailed) + task.Fail(err) } else { - task.State = StateCompleted - task.History = append(task.History, StateCompleted) + task.Complete() } - task.mu.Unlock() }(t) } wg.Wait() diff --git a/workspace/task/runner_test.go b/workspace/task/runner_test.go index d02f599..7a7da7f 100644 --- a/workspace/task/runner_test.go +++ b/workspace/task/runner_test.go @@ -26,11 +26,11 @@ func TestRunner_Run(t *testing.T) { runner.Run(context.Background()) - if t1.State != StateCompleted { - t.Errorf("expected t1 to be Completed, got %s", t1.State) + if state := t1.GetState(); state != StateCompleted { + t.Errorf("expected t1 to be Completed, got %s", state) } - if t2.State != StateFailed { - t.Errorf("expected t2 to be Failed, got %s", t2.State) + if state := t2.GetState(); state != StateFailed { + t.Errorf("expected t2 to be Failed, got %s", state) } } diff --git a/workspace/task/task.go b/workspace/task/task.go index 8f403dc..ca2c12e 100644 --- a/workspace/task/task.go +++ b/workspace/task/task.go @@ -18,79 +18,104 @@ const ( type Task struct { mu sync.RWMutex ID string - State State + state State Action func(ctx context.Context) error - Err error - Metadata map[string]interface{ } - History []State - StartedAt time.Time - FinishedAt time.Time + err error + metadata map[string]interface{} + history []State + startedAt time.Time + finishedAt time.Time } func NewTask(id string, action func(ctx context.Context) error) *Task { return &Task{ ID: id, - State: StatePending, + state: StatePending, Action: action, - Metadata: make(map[string]interface{}), - History: []State{StatePending}, + metadata: make(map[string]interface{}), + history: []State{StatePending}, } } func (t *Task) GetState() State { t.mu.RLock() defer t.mu.RUnlock() - return t.State + return t.state } func (t *Task) SetState(state State) { t.mu.Lock() defer t.mu.Unlock() - t.State = state - t.History = append(t.History, state) + t.state = state + t.history = append(t.history, state) } func (t *Task) GetErr() error { t.mu.RLock() defer t.mu.RUnlock() - return t.Err + return t.err } func (t *Task) SetErr(err error) { t.mu.Lock() defer t.mu.Unlock() - t.Err = err + t.err = err } func (t *Task) GetMetadata(key string) (interface{}, bool) { t.mu.RLock() defer t.mu.RUnlock() - val, ok := t.Metadata[key] + val, ok := t.metadata[key] return val, ok } func (t *Task) SetMetadata(key string, val interface{}) { t.mu.Lock() defer t.mu.Unlock() - t.Metadata[key] = val + t.metadata[key] = val } func (t *Task) GetHistory() []State { t.mu.RLock() defer t.mu.RUnlock() - h := make([]State, len(t.History)) - copy(h, t.History) + h := make([]State, len(t.history)) + copy(h, t.history) return h } func (t *Task) GetStartedAt() time.Time { t.mu.RLock() defer t.mu.RUnlock() - return t.StartedAt + return t.startedAt } func (t *Task) GetFinishedAt() time.Time { t.mu.RLock() defer t.mu.RUnlock() - return t.FinishedAt + return t.finishedAt +} + +func (t *Task) Start() { + t.mu.Lock() + defer t.mu.Unlock() + t.state = StateRunning + t.startedAt = time.Now() + t.history = append(t.history, StateRunning) +} + +func (t *Task) Complete() { + t.mu.Lock() + defer t.mu.Unlock() + t.state = StateCompleted + t.finishedAt = time.Now() + t.history = append(t.history, StateCompleted) +} + +func (t *Task) Fail(err error) { + t.mu.Lock() + defer t.mu.Unlock() + t.state = StateFailed + t.err = err + t.finishedAt = time.Now() + t.history = append(t.history, StateFailed) } \ No newline at end of file From 86e73a6d7c49785c2abb377b90eb586bd50c8d7b Mon Sep 17 00:00:00 2001 From: Thong Doan Date: Sat, 13 Jun 2026 21:16:04 +0700 Subject: [PATCH 2/2] fix: remove unused time import in runner.go --- workspace/task/runner.go | 1 - 1 file changed, 1 deletion(-) diff --git a/workspace/task/runner.go b/workspace/task/runner.go index 0e043f7..135c21e 100644 --- a/workspace/task/runner.go +++ b/workspace/task/runner.go @@ -3,7 +3,6 @@ package task import ( "context" "sync" - "time" ) type Runner struct {