Ghost Downloader

Task System

State machine rules and scheduling mechanism.

Architecture Overview describes the complete task lifecycle. This page digs into the internals of the state machine.

Source: app/services/task_service.py, app/models/task.py.

State Derivation

The task state is not a directly set value; it is aggregated from the states of all steps.

Task state machine

TransitionTrigger
→ WAITINGEnqueue, restore saved task, restart
WAITING → RUNNINGScheduler assigns to an async thread
RUNNING → COMPLETEDAll steps finish
RUNNING → FAILEDAny step permanent error
RUNNING → PAUSEDUser pauses
PAUSED / FAILED → WAITINGUser restarts
COMPLETED → WAITINGCompleted task has newly selected incomplete files

Derivation rules (in priority order): any step FAILED → FAILED. All COMPLETED → COMPLETED. Any RUNNING → RUNNING. All PAUSED → PAUSED. Otherwise → WAITING.

When setting a task state, first push the state to all incomplete steps, then re-derive from the steps. Directly writing the task's state field has no effect; it is overwritten on the next step state change.

For a three-step task (download video → download audio → mux), when the second step fails: the first step is COMPLETED (stays COMPLETED), the second step FAILED, and the third step is still WAITING. The derived result is FAILED. When the user restarts, the first step is skipped, and the second step resets its progress to zero and executes again.

Step Iteration

During execution, iteration proceeds in step-number order. The iterator re-checks the task state before fetching the next step. If a previous step failure caused the task state to become FAILED, iteration terminates immediately. This is a single error boundary: failure of any step prevents subsequent steps from executing.

Completed and unselected steps are skipped; only steps in the WAITING state execute.

Cancellation (pause) and failure have different exception paths. Cancellation propagates through CancelledError; steps catch it internally and mark themselves as PAUSED. Failure propagates through TaskError; steps catch it internally, mark themselves as FAILED, and record the error message. Both exceptions eventually abort step iteration, but they affect subsequent recovery differently: a PAUSED step resumes from a breakpoint, while a FAILED step executes from the beginning.

Scheduling

The waiting queue is an ordered list. The scheduler loops and checks: when the number of RUNNING tasks is less than the maximum concurrency, it takes a task from the queue head and submits it to an async thread.

When the user adjusts the maximum concurrency, if the number of RUNNING tasks exceeds the new limit, the excess tasks are cancelled and return to the waiting queue; only pausable steps are cancelled. Non-pausable steps (such as FFmpeg muxing) continue running until completion.

Operation Chains

The core pattern for all mutation operations: cancel the run → callback → follow-up action. Callback timing depends on whether the task is running; this is an inevitable result of async bridging. See Architecture Overview.

OperationWhat happens after cancellation
Re-downloadDelete output files and progress files → Reset all steps to WAITING → Reschedule
EditOptionally delete files → Replace parameters (URL / steps / file sizes; task ID unchanged) → Apply new options → Reschedule
Selection changeUpdate selection list → If a COMPLETED task has newly selected incomplete files: Reschedule
DeleteOptionally delete output files → Remove from memory and persistence

On this page