Flask init_app() overwrites the previous handle without disposing it
Severity: Medium
Affected repos: middleware-python
Component boundary: middleware-python Flask adapter
Symptom
The Flask extension's init_app() pattern allows the same ReCost() instance to bind to multiple Flask apps. Each call to init_app() invokes init(config) and stores the result in self._handle, overwriting the previous handle. The overwritten handle is never disposed: its flush timer keeps running, its transport thread keeps living, its interceptor patches stay installed.
Evidence
middleware-python/recost/frameworks/flask.py — init_app() does self._handle = init(config) with no prior self._handle.dispose().
Impact
- Tests that register the extension twice (common pattern in pytest) leak threads.
- Apps that re-init the extension on config reload leak timers and may emit interleaved telemetry for the same project.
Fix recommendation
def init_app(self, app: Flask, config: Optional[RecostConfig] = None, **kwargs: Any) -> None:
if self._handle is not None:
self._handle.dispose()
self._handle = None
if config is None:
config = RecostConfig(**kwargs)
self._handle = init(config)
Document: "Calling init_app() again disposes the previous instance. Only one app is active per ReCost instance at a time."
Verification
- Test: call
init_app(app1), then init_app(app2), assert threading.active_count() does not grow.
Related
Relates to existing #4 (Module-level state races: _handle, install/uninstall, init-vs-dispose). This is the specific Flask-adapter symptom of that broader concern.
Flask
init_app()overwrites the previous handle without disposing itSeverity: Medium
Affected repos:
middleware-pythonComponent boundary: middleware-python Flask adapter
Symptom
The Flask extension's
init_app()pattern allows the sameReCost()instance to bind to multiple Flask apps. Each call toinit_app()invokesinit(config)and stores the result inself._handle, overwriting the previous handle. The overwritten handle is never disposed: its flush timer keeps running, its transport thread keeps living, its interceptor patches stay installed.Evidence
middleware-python/recost/frameworks/flask.py—init_app()doesself._handle = init(config)with no priorself._handle.dispose().Impact
Fix recommendation
Document: "Calling
init_app()again disposes the previous instance. Only one app is active perReCostinstance at a time."Verification
init_app(app1), theninit_app(app2), assertthreading.active_count()does not grow.Related
Relates to existing #4 (Module-level state races:
_handle, install/uninstall, init-vs-dispose). This is the specific Flask-adapter symptom of that broader concern.