Future.cancel() can prevent a queued executor call from starting, but cannot interrupt a callable that is already running.
Python Future cancellation: queued work versus running work
Operation contract
A single-worker pool is occupied with one ledger export. A second export remains queued, so cancel() can remove it before a worker begins. Once a callable is running, cancel() returns False; the callable needs its own cooperative stop rule. Worker count limits parallel execution, not retained queue memory.
Failure and ownership boundary
Two events make the transition observable. The first worker signals that it started, then waits until the caller releases it. In application code, release that wait in a finally block so an assertion or shutdown failure cannot strand the pool. A timeout on future.result() stops only the caller's wait. It does not stop the worker.
Working program
from concurrent.futures import ThreadPoolExecutor
from threading import Event
started = Event()
release = Event()
def export_ledger():
started.set()
release.wait()
return "ledger-ready"
with ThreadPoolExecutor(max_workers=1) as workers:
running = workers.submit(export_ledger)
started.wait()
pending = workers.submit(lambda: "second-export")
print("pending cancelled:", pending.cancel())
release.set()
print("running result:", running.result())
print("second cancelled:", pending.cancelled())Output
pending cancelled: True
running result: ledger-ready
second cancelled: TrueCosts and limits
Every queued Future retains a callable and its arguments. Large payloads need admission control before submission.
Common Mistakes
- A result timeout does not stop a running callable.
- cancel() cannot forcibly interrupt a worker in progress.
- A small worker count does not imply a small queue.
Connected lessons
Python thread pools: collect results and observe worker failures, Python Condition: wait for protected state, not for a notification count, Python asyncio Semaphore: bound active work, not queued tasks.
