Technology · PRACTICAL GUIDE
Python warns “resource_tracker: leaked semaphore objects”? Find the dead owner before hiding the warning
The shutdown warning is a cleanup symptom, not a root cause. Preserve the first exception, map every process-owned semaphore, and prove normal close, join, and unlink behavior before suppressing anything.

The warning is printed during interpreter shutdown after the resource tracker still has registered names. It does not by itself tell you which library created them or whether the program’s real failure happened earlier. A child killed by a signal, a failed worker initializer, an unclosed pool, a queue whose feeder thread was not drained, or mismatched unlink bookkeeping can all leave the same last line.
Start with the first exception and an ownership inventory. Record the Python build and start method, reduce the run to one resource constructor at a time, prove how every child exits, and verify the parent’s close, shutdown, join, and unlink sequence. Warning suppression is a reporting choice only after the lifecycle is demonstrably correct.
Preserve the first failure, not only the last warning
Capture the complete stderr stream and exit status. Find the earliest traceback, signal, failed initializer, timeout, cancellation, or forced termination before the resource-tracker line. The last warning may be secondary cleanup after that event.
Record Python version and build, operating system, start method, parent and child PIDs, library versions, and whether the process returned normally, raised, was cancelled, or was killed. Do not begin by filtering UserWarning.
Inventory constructors and assign each resource an owner
Search the application and dependency boundary for multiprocessing.Pool, Process, Queue, Lock, Semaphore, ProcessPoolExecutor, SharedMemory, joblib or ML data-loader workers. Record where each object is constructed, which process owns it, and which scope is responsible for closing or unlinking it.
Move process creation behind the main-module guard where the platform requires spawn-safe imports, but do not treat that guard as a universal cure. A resource created at import time, a failing initializer, or a library-owned pool can still need a different fix.
Prove the normal exit path before testing crashes
Use context managers where supported. Otherwise close a pool to stop accepting work, join it, shut down executors, close queues after producers finish, and join queue feeder threads when the documented lifecycle requires it. For SharedMemory, close each handle and let one agreed owner unlink exactly once.
Avoid terminate, kill, os._exit, abrupt container stops, and test-runner timeouts during the baseline. Python documents that forced termination can leave shared resources, locks, pipes, and queues in a broken or unreleased state.
Separate a real leaked object from tracker bookkeeping
Reduce to the smallest constructor and one child. Run with explicit lifecycle logging so creation, child exit, close, join, unlink, and interpreter shutdown are ordered. If the warning disappears, reintroduce one library layer at a time until ownership changes.
On Linux only, /dev/shm can provide supporting evidence for POSIX named objects, but its contents are not a safe deletion list and other platforms expose them differently. Never delete a name you cannot tie to the stopped process and a documented cleanup owner.
Test supported-version and race branches
If lifecycle proof is clean, reproduce on the current supported patch release and compare a minimal script. CPython issues show that initializer failure, reentrant cleanup, and double unlink races have produced similar warnings in specific versions or tests.
Do not paste a private unregister call into production merely to quiet the final count. First determine whether application and tracker disagree because cleanup happened twice, cleanup never ran, or a version-specific bug remains reproducible.
Close with a no-warning, no-stale-object test
The closure test is one minimal run in which every child has a known exit, the parent closes and joins all owned resources, SharedMemory has one unlink owner, and interpreter shutdown adds no new warning. Repeat the test under the same service manager or CI cancellation path that originally failed.
Only after that proof should you decide whether a known harmless third-party warning merits a narrow filter. Document the exact version, reproducer, upstream issue, and removal condition so suppression cannot conceal a future leak.
