Make sync wrapper fork-safe#8
Conversation
|
Thanks for the submission! I wouldn't have found this without your heads-up. I'll try and get it tested and merged by Monday. |
I only noticed it today while porting our codebase from postmarker to postmark-python. Everything was fine in staging and all tests passed, but the issue appeared after deploying to our multi-worker production server. It was a useful experiment for me. |
|
Your fix is right on. I added one assertion to the fork test that verifies |
Applied via direct merge to main (7411d92). Thanks for the good catch, and making this better for everyone. Patch release incoming... |

Summary
Fix
postmark.syncso its background event-loop thread is safe to use after a process fork.The sync wrapper currently creates a module-level event-loop thread at import time. In prefork servers, such as Odoo/Gunicorn-style worker deployments, importing
postmark.syncbefore worker fork leaves child processes with a reference to an event loop whose thread no longer exists. A later sync call schedules a coroutine onto that stale loop and waits forever.This change makes
_EventLoopThreadlazy and fork-aware. Before each run it checks the current PID and thread liveness, and starts a fresh event loop thread when needed.Root Cause
asyncio.run_coroutine_threadsafe(...).result()blocks indefinitely if the target loop is not running. Afterfork(), the background loop thread created before the fork is gone in the child process, but the module-level_loopobject still points to the old loop.Changes
fork()and verifies the child can still run a sync call.Validation
UV_CACHE_DIR=/tmp/uv-cache uv run --with-editable . --with pytest --with pytest-asyncio --with httpx --with pydantic --with email-validator --with tenacity --with respx pytest417 passed, 1 warningThe warning is Python's expected
os.fork()deprecation warning for multithreaded processes, which is exactly the deployment class this regression protects.