Skip to content

Commit dd1dc7f

Browse files
committed
tasks: fix race conditions
* show resources in task details * fix task state locking * return task object consistently Race condition iexisted where task State, err, and processReturnValue fields were written by consumer goroutine and read by concurrent accessors without proper synchronization, causing torn reads and data races.
1 parent 87e13b2 commit dd1dc7f

2 files changed

Lines changed: 41 additions & 27 deletions

File tree

task/list.go

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,30 @@ func (list *List) consumer() {
4444
for {
4545
select {
4646
case task := <-list.queue:
47+
// Set task state to RUNNING before processing
4748
list.Lock()
48-
{
49-
task.State = RUNNING
50-
}
49+
task.State = RUNNING
5150
list.Unlock()
5251

5352
go func() {
5453
retValue, err := task.process(aptly.Progress(task.output), task.detail)
5554

55+
// Update task completion state and cleanup with list lock held
5656
list.Lock()
5757
{
58-
task.processReturnValue = retValue
59-
task.err = err
6058
if err != nil {
6159
task.output.Printf("Task failed with error: %v", err)
6260
task.State = FAILED
61+
task.err = err
62+
task.processReturnValue = retValue
6363
} else {
6464
task.output.Print("Task succeeded")
6565
task.State = SUCCEEDED
66+
task.err = nil
67+
task.processReturnValue = retValue
6668
}
6769

68-
list.usedResources.Free(task.resources)
70+
list.usedResources.Free(task.Resources)
6971

7072
task.wgTask.Done()
7173
list.wg.Done()
@@ -74,9 +76,9 @@ func (list *List) consumer() {
7476
for _, t := range list.tasks {
7577
if t.State == IDLE {
7678
// check resources
77-
blockingTasks := list.usedResources.UsedBy(t.resources)
79+
blockingTasks := list.usedResources.UsedBy(t.Resources)
7880
if len(blockingTasks) == 0 {
79-
list.usedResources.MarkInUse(t.resources, t)
81+
list.usedResources.MarkInUse(t.Resources, t)
8082
// unlock list since queueing may block
8183
list.Unlock()
8284
unlocked = true
@@ -105,13 +107,15 @@ func (list *List) Stop() {
105107

106108
// GetTasks gets complete list of tasks
107109
func (list *List) GetTasks() []Task {
108-
tasks := []Task{}
109110
list.Lock()
111+
defer list.Unlock()
112+
113+
tasks := []Task{}
110114
for _, task := range list.tasks {
115+
// Copy task while holding list lock
111116
tasks = append(tasks, *task)
112117
}
113118

114-
list.Unlock()
115119
return tasks
116120
}
117121

@@ -139,11 +143,11 @@ func (list *List) DeleteTaskByID(ID int) (Task, error) {
139143
// GetTaskByID returns task with given id
140144
func (list *List) GetTaskByID(ID int) (Task, error) {
141145
list.Lock()
142-
tasks := list.tasks
143-
list.Unlock()
146+
defer list.Unlock()
144147

145-
for _, task := range tasks {
148+
for _, task := range list.tasks {
146149
if task.ID == ID {
150+
// Copy task while holding list lock
147151
return *task, nil
148152
}
149153
}
@@ -180,13 +184,16 @@ func (list *List) GetTaskDetailByID(ID int) (interface{}, error) {
180184

181185
// GetTaskReturnValueByID returns process return value of task with given id
182186
func (list *List) GetTaskReturnValueByID(ID int) (*ProcessReturnValue, error) {
183-
task, err := list.GetTaskByID(ID)
187+
list.Lock()
188+
defer list.Unlock()
184189

185-
if err != nil {
186-
return nil, err
190+
for _, task := range list.tasks {
191+
if task.ID == ID {
192+
return task.processReturnValue, nil
193+
}
187194
}
188195

189-
return task.processReturnValue, nil
196+
return nil, fmt.Errorf("could not find task with id %v", ID)
190197
}
191198

192199
// RunTaskInBackground creates task and runs it in background. This will block until the necessary resources
@@ -204,24 +211,29 @@ func (list *List) RunTaskInBackground(name string, resources []string, process P
204211
list.wg.Add(1)
205212
task.wgTask.Add(1)
206213

214+
// Copy task while still holding the lock to avoid racing with consumer
215+
// setting State=RUNNING after receiving from queue
216+
taskCopy := *task
217+
207218
// add task to queue for processing if resources are available
208219
// if not, task will be queued by the consumer once resources are available
209220
tasks := list.usedResources.UsedBy(resources)
210221
if len(tasks) == 0 {
211-
list.usedResources.MarkInUse(task.resources, task)
222+
list.usedResources.MarkInUse(task.Resources, task)
212223
// queueing task might block if channel not ready, unlock list before queueing
213224
list.Unlock()
214225
list.queue <- task
215226
} else {
216227
list.Unlock()
217228
}
218229

219-
return *task, nil
230+
return taskCopy, nil
220231
}
221232

222233
// Clear removes finished tasks from list
223234
func (list *List) Clear() {
224235
list.Lock()
236+
defer list.Unlock()
225237

226238
var tasks []*Task
227239
for _, task := range list.tasks {
@@ -230,8 +242,6 @@ func (list *List) Clear() {
230242
}
231243
}
232244
list.tasks = tasks
233-
234-
list.Unlock()
235245
}
236246

237247
// Wait waits till all tasks are processed
@@ -254,11 +264,14 @@ func (list *List) WaitForTaskByID(ID int) (Task, error) {
254264

255265
// GetTaskErrorByID returns the Task error for a given id
256266
func (list *List) GetTaskErrorByID(ID int) (error, error) {
257-
task, err := list.GetTaskByID(ID)
267+
list.Lock()
268+
defer list.Unlock()
258269

259-
if err != nil {
260-
return nil, err
270+
for _, task := range list.tasks {
271+
if task.ID == ID {
272+
return task.err, nil
273+
}
261274
}
262275

263-
return task.err, nil
276+
return nil, fmt.Errorf("could not find task with id %v", ID)
264277
}

task/task.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const (
4242
)
4343

4444
// Task represents as task in a queue encapsulates process code
45+
// All fields are protected by List.Mutex - access task fields only while holding list.Lock()
4546
type Task struct {
4647
output *Output
4748
detail *Detail
@@ -51,7 +52,7 @@ type Task struct {
5152
Name string
5253
ID int
5354
State State
54-
resources []string
55+
Resources []string
5556
wgTask *sync.WaitGroup
5657
}
5758

@@ -64,7 +65,7 @@ func NewTask(process Process, name string, ID int, resources []string, wgTask *s
6465
Name: name,
6566
ID: ID,
6667
State: IDLE,
67-
resources: resources,
68+
Resources: resources,
6869
wgTask: wgTask,
6970
}
7071
return task

0 commit comments

Comments
 (0)