runTask is the worker’s decision point for applying a queued task’s desired state. It removes one task from the queue, finds its persisted counterpart, checks whether the requested state change is allowed, and delegates the actual container action to StartTask or StopTask. This keeps queue handling, task-state policy, and container operations separate while giving the worker one place to coordinate them. section 4.6.3 section 4.6.4
The worker treats a queued task as an instruction to place that task in a particular state. A task absent from storage is being seen for the first time and is stored before processing; an existing task supplies the current state against which the queued state is validated. An empty queue is not an error: runTask logs the condition and returns a result with no error. section 4.6.3 section 4.6.4
The basic processing path is deliberately small:
- Dequeue one value and return normally when no task is available. section 4.6.4
- Convert the dequeued interface value into a task.Task and locate it by ID in worker storage. section 4.6.4
- Store a first-seen task, then compare its persisted state with the queued desired state. section 4.6.3 section 4.6.4
- Start a valid Scheduled task, stop a valid Completed task, and return an error for any other requested transition. section 4.6.4
State validation protects the worker from acting on arbitrary updates. The lifecycle includes Pending, Scheduled, Running, Completed, and Failed states; Scheduled may move to Running through StartTask, while a Running task may move to Completed through StopTask. Completed and Failed are terminal in the defined transition map. section 4.6.3
Listing 4.12 Our implementation of the RunTask method
func (w *Worker) RunTask() task.DockerResult {
t := w.Queue.Dequeue() #1
if t == nil {
log.Println("No tasks in the queue")
return task.DockerResult{Error: nil}
}
taskQueued := t.(task.Task) #2
taskPersisted := w.Db[taskQueued.ID] #3
if taskPersisted == nil {
taskPersisted = &taskQueued
w.Db[taskQueued.ID] = &taskQueued
}
var result task.DockerResult
if task.ValidStateTransition(
taskPersisted.State, taskQueued.State) {
switch taskQueued.State {
case task.Scheduled:
result = w.StartTask(taskQueued) #4
case task.Completed:
result = w.StopTask(taskQueued) #5
default:
result.Error = errors.New("We should not get here")
}
} else {
err := fmt.Errorf("Invalid transition from %v to %v",
taskPersisted.State, taskQueued.State)
result.Error = err #6
}
return result #7
}
#1 Calls the Dequeue() method
#2 Converts the task to the proper type
#3 Attempts to retrieve the same task from the Db
#4 If there is a valid state transition and a task from the queue has a state of Scheduled, calls the StartTask method
#5 If the task from the queue has a state of Completed, calls the StopTask method
#6 If there is no valid transition, sets the Error field of the result variable
#7 Returns the result
Inspect the complete worker method to see queue retrieval, first-write persistence, transition validation, and StartTask/StopTask dispatch in one control flow. section 4.6.4
Starting and then stopping a web task
Create a worker with a queue and task store, enqueue a Scheduled task using the strm/helloworld-http image, and call runTask to start its container. Save the returned container ID on the task, wait, change the same task to Completed, enqueue it again, and call runTask again to stop and remove the container. The same task ID lets stored state distinguish the update from a first submission. section 4.7 section 4.6.4
Storage can evolve without changing runTask’s role. A later implementation replaces the worker’s direct map access with a Store interface: it writes the queued task first, then reads it back. This ordering distinguishes storage failures from a task simply not being present, allowing runTask to return a meaningful error rather than treating every read failure as a new task. section 11.6
