liveBook · Manning

Run task

As explained in Build and Orchestrator in Go (From Scratch)

runTask is a worker method that retrieves a queued task, ensures it is stored, and manages its execution according to its state.

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

How Build and Orchestrator in Go (From Scratch) covers it

How deep it goes
Works through a complete queue-to-storage-to-transition-to-action algorithm, then refactors persistence behind a Store interface.
Examples
Demonstrates a Scheduled strm/helloworld-http task being started, assigned a container ID, changed to Completed, and stopped.
What it assumes
Introduces the worker flow after defining task states, state transitions, and StartTask and StopTask responsibilities.
What you can do after
Build it from scratch by dequeuing tasks, validating transitions, persisting state, and dispatching container start or stop operations.
How it connects
Connects task queues, persistent storage, state transitions, StartTask, StopTask, Docker containers, and the Store interface.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Build and Orchestrator in Go (From Scratch). The section links point to the parts of the book it draws on.