To cancel a media workflow, first record that your application no longer wants new work, then request cancellation of active provider tasks and reconcile their outcomes. Keep the user's stop request separate from the media job's observed status. A late completion should not silently restart transcription, dubbing or another downstream step.
A useful cancel button controls a workflow with several boundaries: queued application work, a remote media job, delivered files and any consumer already running. Each boundary needs an explicit outcome rather than one optimistic “cancelled” flag.
Understand the cancellation boundary
Tornado's cancel-job reference documents DELETE /jobs/{id} with API-key authentication. A queued cancellation returns cancelled; an in-flight request returns cancelling and needs follow-up observation. Those lowercase response values describe the cancellation request, not the PascalCase status vocabulary used by job lookups.
The documented operation stops work; it does not remove the job record or delete a stored file. An artifact already delivered is not recalled. During an in-flight abort, an observation can briefly show Cancelled before the job ends as Completed. Do not treat the first cancellation-looking observation as proof that delivery never happened. Already-final jobs cannot be cancelled through this endpoint.
Keep request receipt, later observations and delivery evidence separate. Never infer a refund or zero usage from the label on an application button.
Persist intent before calling a remote service
In your database, record a stop request with its time, actor and workflow attempt. Stop admitting new work for that attempt. Then issue any required remote cancellation request using the existing job identifier. If the request times out, retain an unresolved cancellation attempt; do not replace it with “nothing happened” or restart the media job.
A compact application record can separate these facts:
| Field | Meaning |
|---|---|
stop_requested_at | Your application accepted a request to stop this attempt |
| Media job ID and observation time | The remote task being reconciled and when its state was read |
| Cancellation request outcome | Receipt, rejection or unknown transport outcome |
| Downstream task ID | Any consumer already claimed or submitted |
| Artifact disposition | Retain, review or separately approved cleanup |
These are application fields, not a proposed Tornado response schema. Scope them to the correct tenant and attempt. A later deliberate restart should create a clearly identified new attempt instead of clearing historical evidence on the old one.
Gate new work even when completion arrives late
The following Python example makes a local policy decision. It deliberately keeps the stop flag independent of the observed media status. It does not send cancellation requests, delete files or launch a consumer.
KNOWN = {"Pending", "Processing", "Completed", "Failed", "Warning",
"Skipped", "Cancelled", "CancelledByAdmin"}
def downstream_decision(stop_requested, observed_status, already_claimed):
if type(stop_requested) is not bool or type(already_claimed) is not bool:
raise ValueError("Application flags must be explicit booleans")
if already_claimed:
return "reconcile_existing_downstream"
if stop_requested:
return "suppress_new_downstream"
if not isinstance(observed_status, str) or observed_status not in KNOWN:
return "review_observation"
if observed_status == "Completed":
return "inspect_delivery_before_claim"
return "do_not_start"
assert downstream_decision(True, "Completed", False) == "suppress_new_downstream"
assert downstream_decision(True, "Processing", True) == "reconcile_existing_downstream"
assert downstream_decision(False, "Completed", False) == "inspect_delivery_before_claim"
assert downstream_decision(False, "FutureStatus", False) == "review_observation"
# A late completion never clears the application-owned stop request.
observations = ["Processing", "Cancelled", "Completed"]
assert [downstream_decision(True, state, False) for state in observations] == [
"suppress_new_downstream", "suppress_new_downstream", "suppress_new_downstream"
]
The five inline assertions and sixteen additional checks were executed with synthetic inputs. They cover the late-completion sequence, other known statuses, unknown or malformed observations and invalid boolean flags. No live job was cancelled.
already_claimed means your application has durably reserved or submitted a downstream action. It does not mean that action definitely began remotely. An existing action needs reconciliation even if a stop request is present. The function also returns only an instruction to inspect delivery before claiming work; it does not declare the artifact usable.
This helper is not a concurrency mechanism. In a real worker, check the current stop flag while atomically reserving the next action. A stop handler and a completion handler must coordinate through a transaction or another appropriate shared-state mechanism. Otherwise both can read old state and act on incompatible decisions. Recheck before dispatch where possible, and handle the remaining window after a remote request has been sent.
The local outbox guide covers durable acceptance. Add stop-aware dispatch and recovery to that design; an outbox by itself does not cancel a request that another service has already accepted.
Reconcile each service independently
If downstream transcription or dubbing has already been submitted, inspect that service's cancellation contract and track its own task ID. Stopping your local polling loop does not establish that its remote task stopped. A local worker shutdown also needs a recovery path for unresolved remote activity.
This separation appears in other orchestration systems too. AWS documents that aborting a Step Functions .sync task triggers a best-effort cancellation of integrated work, which can fail because of permissions or service availability. See the Step Functions integration patterns. That is an AWS-specific contract, not a guarantee about Tornado or your downstream consumer.
For your application, preserve separate outcomes for media acquisition and downstream processing. Continue reconciliation with a bounded schedule, and surface an unresolved stop when evidence is missing. The worker restart guide explains how durable identifiers let that process survive interruption.
Make cleanup a separate decision
A request to stop future processing is not necessarily a request to erase every output. A completed file might be shared with another approved workflow, retained for audit or still being read by a consumer. Define retention and cleanup rules before deleting anything.
When cleanup is required, identify the precise artifact and all dependent tasks, authorize the deletion separately in your application, and record its result. Do not equate an expired signed URL with deletion. The storage and retention guide explains that distinction.
Keep the interface honest: “Stop requested; outcome pending,” “Further processing suppressed,” and “Media completed before cancellation” describe different facts. Avoid displaying a single green cancellation result while remote work or cleanup remains unresolved.
Exercise the race conditions before rollout
Use fixtures for a completion arriving before the stop, a completion arriving afterward, a lost cancellation response and a downstream task whose submission outcome is unknown. Then test your actual persistence boundary with concurrent stop and completion handlers. The pure helper above cannot prove that database coordination works.
Verify that late notifications update observations without clearing stop intent, that a process restart preserves that intent, and that deliberate resumption has a new attempt identity. Keep ordinary cancellation out of automatic failure-retry loops. Use the offline test guide to distinguish local policy checks from live behavior.
Build your first media workflow, then give each transition an explicit owner: who admits new work, who requests cancellation, who reconciles the result and who decides what happens to delivered artifacts.