A per-queue distributed semaphore built on Temporal.
One long-lived ResourcePoolWorkflow runs per user queue and owns that queue's
concurrency budget N. Task workflows signal the pool to acquire a slot before
running Activity A and signal it to release afterwards, so at most N copies of
Activity A are ever in flight at once across all task workflows for the queue.
Robustness is built in: lease TTLs reclaim slots leaked by crashed tasks,
continue-as-new bounds the pool's history, and grants to dead requesters are
freed automatically.
workflows/ Temporal workflow definitions
resource_pool.py ResourcePoolWorkflow — the distributed semaphore
task.py TaskWorkflow — one execution per produced task
activities/ Temporal activity definitions
task_activities.py Activity A (rate-capped) and Activity B (optional)
worker.py Runs the Worker (workflows + activities)
starter.py Ensures the pool is running and produces demo tasks
tests/ Pytest suite proving the concurrency guarantee
uv syncStart a local Temporal server (e.g. temporal server start-dev), then in two
terminals:
uv run python worker.py # terminal 1: the Worker
uv run python starter.py # terminal 2: produce tasks and watch the cap holdWith max_concurrency=3 and a ~2s Activity A, at most 3 Activity A calls run at
any instant across all 12 task workflows.
uv run pytestThe suite downloads Temporal's local and time-skipping test servers on first run, so it needs network access.