fix(context): make ApplicationContext.start() idempotent (release 26.09.02) - #162
Merged
Conversation
The context already tracked `_started` — set at the end of start(), cleared at the end of stop() — but nothing ever read it. A second start() therefore re-ran the entire pipeline instead of returning: auto-configurations registered again, @configuration classes were processed again, and a second fully-initialised set of singletons was created AND STARTED beside the first. The duplicates are live, not inert: a second Kafka consumer joins the same group and steals partitions from the first, a second scheduler fires every @scheduled task twice, a second connection pool opens. Nothing owns them, so stop() disposes one set and leaks the other. A double start is easy to reach — an ASGI server that runs the lifespan twice, a reload, a test harness sharing one module-level application across files — and it failed silently. Every adapter in this codebase already guards itself the same way (KafkaEventBus.start() opens with `if self._started: return`); the context now follows its own convention. stop() still clears the flag, so a stopped context restarts and rebuilds normally. Found by building a service on 26.09.01, where two test modules sharing one module-level app produced two EventPublishers in one container. Release 26.09.02.
This was referenced Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ApplicationContextalready tracked_started— set at the end ofstart(), cleared at the end ofstop()— but nothing ever read it. A secondstart()therefore re-ran the entire pipeline insteadof returning.
That does not refresh the context; it builds a second one beside it. Auto-configurations register again,
@configurationclasses are processed again, and a second fully-initialised set of singletons is createdand started:
@scheduledtask twiceNothing owns the duplicates, so
stop()disposes one set and leaks the other.A double start is easy to reach — an ASGI server that runs the lifespan twice, a reload, or a test
harness sharing one module-level application across files — and it failed silently, which is the
worst property a lifecycle bug can have.
Every adapter in this codebase already guards itself this way;
KafkaEventBus.start()opens withif self._started: return. The context now follows its own convention.stop()still clears the flag,so a stopped context restarts and rebuilds normally — asserted by a second test.
Found by building a service on
26.09.01: two test modules sharing one module-level app produced twoEventPublisherinstances in a single container.Verification
pytest tests/(CI's ignore set)mypy src/pyfly --strictruff check/ruff format --check