Prepare a controlled experiment
The new lab uses neither HTTP nor SQLite. It runs owned workers in one process, with two admission slots and an Event that holds batch jobs in progress. The main thread waits for each job to signal started before observing active=2. This synchronization avoids assuming that an arbitrary sleep gives jobs enough time to start. The eight-second guard protects the experiment: if the script fails to release the Event, the worker ends with an error. It is not a business SLA. Run the file with a new output path and retain the report alongside the script hash.
The wait ended but the work did not
With both jobs held, the script calls join with a 0.02-second wait on the first worker. It then checks three facts together: the thread remains alive, finished remains unset and active stays at two. This demonstrates the boundary of a local wait without simulating a remote server or a cancellation protocol. At work, a dashboard may show abandoned requests while the dependency remains occupied. Before retrying or releasing resources, identify who owns the operation and who confirms completion. A short caller deadline can reduce visible waiting without increasing the capacity actually available.
Reject before creating work
Admission attempts to acquire a slot without waiting. When both slots are occupied, submit returns rejected-before-worker and the object receives no thread. The script verifies this for the critical request and six manual retry attempts. admitted remains two; rejected rises to seven. There is no automatic queue, backoff or retry mechanism in this example. Rejection has consequences that consumers need to understand: work not yet admitted needs different handling from an operation whose result was lost. In production, the response contract, retry policy and entry limits must be agreed and tested separately.
Account for the resource lifetime
The worker releases its slot in finally after completion or failure. One case injects ValueError and verifies that another job can enter afterwards. A separate case uses only a semaphore to expose an accounting error: releasing early permits a second acquisition before the original operation ends. The bounded counter detects the later excessive release; it does not know the work lifetime. This case starts no additional worker and measures no overload. It supports reviewing where release belongs in the code and understanding why an apparently valid counter does not establish that its associated resource is free.
Read metrics without hiding rejections
The report separates active, peak, admitted and rejected. These are teaching counters protected by a lock, not metrics exported to an observability system. The first held state has two active jobs, two admissions and seven rejections. After actual release, a new job completes and active returns to zero. A success rate calculated only over admitted work would hide rejected consumers. In a support exercise, report both denominators and distinguish admission rejection, execution error and caller abandonment. Also identify the observed pool because an aggregate total can conceal a critical path with no available capacity.
Choose an incident mitigation
In a fictional funds team, report production occupies every slot used by operational queries. The incident lead should confirm occupancy, reduce non-priority admission under an authorized decision and track actual recovery. Increasing the pool may merely move saturation to the database. Retrying every rejection six times creates more attempts without finishing held jobs, as the script shows. Prepare a reversal criterion, communication about degraded functionality and a backlog check. The lab helps explain the decision; safe capacity and the behavior of a particular platform require a representative experiment.
python3 content/labs/micro-capacity/run.py --output /tmp/dr-capacity-new-run.json
# Use a new output path for each run; existing reports are not overwritten.Two batch jobs occupy both slots in a fictional pool. A critical request and six retries are rejected before creating threads.
Common pitfalls
Releasing capacity when the caller gives up; confusing timeout with cancellation; counting only completed requests; creating threads before admission.
Related topics: Timeout after commit · Monitoring and observability
The slot belongs to admitted work until that work ends. Caller waiting and worker lifetime are separate observations.
Reference: threading: bounded semaphores, Event and thread lifecycle · Microservice architecture patterns and scoped platform examples; primary guidance consulted 2026-09-30