swoole-bundle/resetter-bundle
Resetter bundle for Swoole Bundle that clears Symfony service and container state between requests in long-running workers, preventing state from leaking across requests so applications stay consistent under Swoole.
Architecture fit. The package addresses a real and growing failure mode in long-running PHP: stale TCP sockets, dropped DB connections, expired Redis/AMQP sessions, and leaked request state after fork() or idle periods. Architecturally, a "resetter" abstraction (a registry of reset() callables invoked at worker lifecycle boundaries) is the correct shape for this problem — it is small, composable, and orthogonal to business logic.
The critical caveat for a TPM: this is a Symfony bundle, not a Laravel package. It is part of the symfony-swoole ecosystem and will wire itself through Symfony's DI container, EventDispatcher, and compiler passes. Our stack is Laravel. Nothing here is drop-in; the value is the design pattern and the individual resetter implementations, not the package itself.
Update (2.1.1). The patch is a targeted correctness fix in the Doctrine DBAL resetter: it now unwinds every nesting level of a leftover transaction and, when a connection cannot be rolled back, closes it outright rather than returning it to the pool in an indeterminate state. Two things follow. First, upstream is demonstrably maintained and is reasoning about the exact failure class we flagged as our top risk — that is a positive signal for the reference implementation's quality. Second, it confirms that the naive version of this resetter (the one we would most likely have written ourselves) is genuinely wrong in a way that is invisible in normal testing. The pattern is still correct; the implementation detail is where the hazard lives.
Integration feasibility. Low as a dependency, moderate as a reference implementation. With 3 stars, minimal dependents, and a single-vendor namespace, taking a hard Composer dependency on it in a production Laravel app is not defensible. What is feasible is:
ping(), Redis PING, AMQP heartbeat, possibly PDO handle validation) and port them into a Laravel service provider that hooks Octane worker events (WorkerStarting, WorkerStopping, RequestTerminated) and queue events (JobProcessing, JobProcessed, WorkerLooping).DB::purge() one-liner. Faithful porting requires reading DB::transactionLevel(), rolling back to level 0, and disconnecting when that fails — plus tests that deliberately strand a nested transaction and assert the connection is discarded, not reused.Technical risk.
config/ publishing, no facades; Symfony-specific glue must be discarded.PING-based liveness checks can mask pool exhaustion. 2.1.1's "close if not rollbackable" is exactly the escalation behavior we should copy; anything that swallows a failed rollback is a bug.Key questions.
ResetterInterface) stable enough to depend on if we fork?symfony-swoole releases on a cadence and that this is not an internal extraction published for convenience.Stack fit. Treat this as pattern source, not a dependency. Our target surfaces are Laravel Octane (Swoole/RoadRunner/FrankenPHP workers) and Laravel Horizon/queue workers — both are exactly the long-running contexts the package was designed for. Laravel gives us the lifecycle hooks; we supply the reset logic ourselves.
Migration path.
DB::purge(), Redis::connection()->client()->ping(), Queue::connection()->reconnect(), HTTP client pool flush). Port specifically from 2.1.1 or later.acme/long-running-resetters with a ResetterInterface, a tagged registry, and a config file declaring which connections to guard. Credit upstream under MIT. Include the transaction-depth handling as a first-class requirement, not a follow-up: roll back to level 0, then disconnect() on failure.WorkerStarting/RequestTerminated and on Horizon JobProcessing (guarded by a "resetter ran within N seconds" throttle so we don't pay a round-trip per job).Compatibility. Laravel 10/11/12 differ in Octane event naming and queue connection APIs; the internal package should target the two versions we actually run and declare that explicitly. Because we control the code, driver variance (MySQL vs. Postgres vs. Redis Cluster) becomes our test matrix rather than an unowned upstream risk. Note that nested-transaction semantics differ between MySQL and Postgres (savepoints), so the rollback-to-level-0 logic needs a per-driver test, not a single happy-path test.
Sequencing. Do not couple this to an Octane migration. Ship it after the app is running on Octane and has demonstrated at least one stale-connection incident — that gives us a reproduction and a measurable success criterion before we add lifecycle machinery. The 2.1.1 fix is a useful forcing function: it tells us the test we must write (strand a nested transaction, assert the connection is dropped).
Maintenance. Owning a fork means we own the reset semantics for every driver version we bump. That is roughly a few hours per quarter, but it is real and should be assigned, not assumed. The low code surface keeps this cheap — the risk is not complexity, it is neglect. 2.1.1 is a reminder that upstream can change semantics in a patch release; we should re-read the changelog before any re-sync, not merge it blind.
Support. No vendor SLA, no community. Escalation is "read the source," which is acceptable at this size but must be a conscious decision. If a resetter is subtly wrong and corrupts a connection state, we debug it ourselves.
Scaling. Resetters do not scale with traffic — they scale with worker count and connection pool size. At high worker counts, naive per-job PING checks add meaningful round-trip load; the throttle/backoff policy matters more than the reset logic itself. Note that 2.1.1 introduces a path that closes connections rather than recycling them; if that path triggers broadly (e.g. during a database failover), it converts a partial degradation into a full pool drain. Verify that reconnect storms don't create a thundering herd against the database.
Failure modes to design for.
finally block.Ramp-up. Low. A mid-level engineer can absorb the concept in a day. The onboarding cost is not the code, it is the operational intuition: when to reset, how often, and how to tell a reset apart from a genuine outage. Budget for a short written runbook and one dashboard panel tracking reset frequency, connection-close count, and post-reset error rate — that is what turns this from a hack into an operable component.
Recommendation. Do not adopt as a dependency. Extract the resetter designs — from 2.1.1 or later — into an internal Laravel package, scoped to Octane and queue workers, gated behind a lifecycle-event throttle, with explicit transaction-unwinding and connection-close behavior. Net cost is small; net risk of not doing it is intermittent, hard-to-reproduce production failures as our long-running workload grows.
How can I help you explore Laravel packages today?