You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Failed LDClient construction leaks the event processor's threads and HTTP pool #497
If LDClient construction fails partway through, everything already started keeps running, and the caller has no way to release it.
__start_up() (ldclient/client.py) creates the event processor at line 320 — starting a dispatcher thread, a pool of five flush workers, two repeating timer threads, and an HTTP connection pool — and only then calls __register_plugins() and self._data_system.start(). If the data system fails to start, the exception propagates out of the constructor, the caller never receives a client object, and there is no handle on which to call close().
Found while auditing the shutdown path after #493, a production incident where close() hung and left worker pods alive for hours to days. I have not observed this particular leak in our own production, but it reproduces reliably.
The threads are daemons, so this does not prevent process exit. It does leak steadily in any application that retries client construction, and postfork() re-runs this same path.
To reproduce
PR #500 adds a failing test (marked xfail(strict=True); run with --runxfail to see it fail). It configures an update_processor_class that raises in start() — a documented configuration hook — and reports:
AssertionError: construction failed but left these threads running:
['ldclient.events.context-flush.repeating', 'ldclient.events.flush.repeating',
'ldclient.events.processor', 'ldclient.flush.1', 'ldclient.flush.2',
'ldclient.flush.3', 'ldclient.flush.4', 'ldclient.flush.5']
Expected behavior
A constructor that raises should leave nothing running.
Related, and not covered by the test: postfork() calls __start_up() again on an existing client, rebinding _event_processor, _data_system and __big_segment_store_manager to fresh objects without stopping the previous ones. In the child the old threads do not exist, but the old urllib3 pools' inherited socket file descriptors are never released. I read this rather than tested it, so please treat it as worth a look rather than an established defect.
No fix proposed — where the teardown belongs (a try/except in __start_up, or restructuring so the event processor starts last) is a design call.
Activity
jsonbailey commentedon Aug 21, 2026
@aviadr1 Thank you for raising the issue and the PRs. We will look at this and provide an update as soon as we can.