PolicyEngine / PolicyEngine/policyengine-observability
Queued log transport should detect (and ideally survive) fork/snapshot-restore without consumer intervention
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 13m
- Merged PRs (30d)
- 2
Description
Problem
QueuedLogDestination starts its listener thread at construction and deliberately carries no fork hooks or pid checks (documented in destinations/queued.py). That is the right simplicity trade-off inside the package, but it moves a correctness obligation onto every consumer whose process forks or restores from a memory snapshot: they must know to call restart_observability() at the right lifecycle moment, after credentials are re-materialized.
The failure mode when a consumer misses this is the worst kind: the service serves traffic normally while every Google-bound record silently drops (reason="full" once the queue fills, dead listener thread never drains). Nothing crashes, nothing alerts on the request path.
We just paid this cost in practice: PolicyEngine/policyengine-household-api#1597 adopts 1.4.0 on Modal, whose workers restore from memory snapshots (@modal.enter(snap=True) builds the app — and with it the queued transport — at snapshot creation). The fix was a restart_observability() call in the post-restore hook, plus a unit test pinning the ordering. The next consumer with a forking or snapshotting runtime (gunicorn --preload with fork, celery prefork, another snapshot platform) has to rediscover all of this from scratch.
Ask
Make dead-listener state detectable — and ideally self-healing — inside the package, so consumers are safe by default rather than safe by convention.
Two tiers, smallest first:
-
Detect and report loudly (minimum).
QueuedLogDestination.emit()already counts drops with a throttled report. Add a cheap staleness check — e.g. compareos.getpid()against the pid captured at construction — and when it differs, report through the internal-error channel with an actionable message namingrestart_observability()and a distinct drop reason (reason="stale-process"rather than"full"). This turns a silent outage into a diagnosable one for the cost of one integer comparison per emit. -
Self-heal (stretch, only if it stays simple). On detecting a stale pid, rebuild the destination the way
restart_log_destinations()does. Constraints that killed the previous attempt at this problem space (#23) still apply and are worth restating: fail-open above all, rebuild fresh clients rather than resurrect old ones, no locking or state machine on the hot path, and no rebuild-inside-emit races — if the rebuild can't be made trivially safe (e.g. singleos.register_at_forkhook rather than emit-path mutation), tier 1 alone is still a big win.
Related prior art: #22 (accept/emit split), #24 (half-open recovery for disabled destinations, closed as deferred), and the consumer-side integration in PolicyEngine/policyengine-household-api#1597.
🤖 Generated with Claude Code
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading destinations/queued.py, then trace restart_log_destinations() and restart_observability(), including the consumer-side post-restore ordering described in PolicyEngine/policyengine-household-api#1597. Review the existing drop-reporting path and relevant unit coverage. Done means stale-process state is reported with restart guidance and a distinct drop reason; self-healing is optional only if it remains fail-open and race-free.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, observability
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100