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.
The resetter bundle is small and purpose-built: it ships a handful of ResetterInterface implementations (DBAL connection, Redis/\Redis, Memcached, PDO, possibly HTTP client) and wires them into a central service you call between "units of work" — an HTTP request handled by Swoole, or a message consumed by a Messenger worker. Nothing here changes your application behavior on a normal FPM request; it is only relevant once a process outlives a single request.
As of 2.1.1 the package is past 1.0 and actively patched (the DBAL resetter was just hardened — see Gotchas), so a ^2.1 constraint is reasonable, but re-read the DBAL resetter source on each minor bump: the reset semantics there are still being refined.
Minimal steps:
composer require symfony-swoole/resetter-bundle:^2.1 (the repo is symfony-swoole/resetter-bundle; confirm the published Composer name and the bundle alias before trusting the README).ResetterBundle in config/bundles.php (Flex may not know this one — verify, because the bundle's alias may differ from the repo name).php bin/console debug:container --tag=kernel.reset
php bin/console debug:container --tag=resetter.resetter # tag name may vary
If you see no entries, the bundle's compiler pass did not find the services it expects (see Gotchas).foreach ($this->bus->pull() as $envelope) {
$this->bus->dispatch($envelope);
$this->resetter->reset(); // service id: check debug:container
}
Where to look first: the bundle's DependencyInjection/ directory and any *.xml/*.php service file, then Resetter/Dbal/. Everything the bundle does is visible there — there is no runtime magic beyond tags and one compiler pass. If the README is thin, the service definitions are your documentation, and for connection semantics the DBAL resetter class is authoritative.
Use the tagged-resetter aggregator, not individual services. The value of the package is that it collects resetters behind one interface so you can extend the list without changing call sites. Inject the aggregator (commonly a Resetter or ChainResetter service) and call reset() once.
Custom resetter for your own stateful service. Anything that caches per-request data — a tenant context, a request-scoped ID generator, an auth token holder, a ClockMock-style mutable state — should implement the package's resetter interface (or Symfony's ResetInterface if the bundle's aggregator accepts it) and be tagged:
final class TenantContextResetter implements ResetterInterface
{
public function __construct(private TenantContext $context) {}
public function reset(): void
{
$this->context->clear();
}
}
services:
App\Resetter\TenantContextResetter:
tags: ['resetter.resetter'] # verify tag name in the bundle source
Doctrine specifically. Resetting the DBAL connection is not the same as resetting the EntityManager. The common, safe pattern for long-running workers is: on reset, if the EM is open, call $em->clear() (or reset()/close() depending on your Doctrine version) before the connection is reset/reconnected. Since 2.1.1 the bundle's DBAL resetter will unwind leftover transaction nesting for you and, if a level cannot be rolled back, close the connection outright — but that is a safety net, not a licence to reset mid-transaction. If the bundle only resets the connection, add your own EM resetter alongside it.
Integration with Symfony Messenger. Use the resetter via a WorkerListener/middleware rather than in consume() directly, so that retried and failed messages also pass through it. Keep retry logic (Doctrine's RetryableException handling) inside the same boundary as the reset, otherwise a deadlock retry can reuse a stale connection. Post-2.1.1 a leftover transaction after an exception is cleaned up by the resetter, which makes the "reset after a failed message" path noticeably safer than before.
Integration with Swoole. Under the Swoole bundle, reset after the response is emitted and before the coroutine is recycled. The reset must not touch coroutine-local state owned by the Swoole runtime — only your connections and caches. If you have any Swoole\Coroutine\Channel or connection pool, let the pool handle its own pooling and reset only the PDO/Redis handles the app holds.
Reset granularity. Prefer "reset everything cheap" per message over conditional resets. A boolean $dirty flag that skips resets is the most common way stale state leaks in; it is also unnecessary — the reset itself should be cheap and idempotent. The 2.1.1 fix makes the DBAL resetter's no-op-ness harder: it now actively walks transaction nesting levels instead of assuming flatness, so do not try to replicate that logic in your own resetter.
reset() will be a no-op. A quick debug:container after install catches this.EntityManager, a repository, a service that captured Connection — must tolerate a lazy reconnect, or you must reset the EM after the connection resetter runs.$connection->isConnected() after reset, re-check it.$em->clear() → connection reset → (optionally) $em->isOpen() check and reopen. Add your own resetter if the bundle doesn't cover this.reset() should be idempotent and exception-safe. A resetter that throws on a half-closed connection will kill the worker. The DBAL resetter now swallows the un-rollbackable case by closing, but other resetters (Redis, Memcached, HTTP) have no such guard: wrap each call in try/catch and log-and-reconnect rather than propagate.\Redis and Predis have different reset semantics. Predis's disconnect() leaves you with a stale client unless you reconnect lazily; phpredis needs close() and a fresh connect(). Check which one the packaged resetter targets, or write yours.BufferHandler/FingersCrossedHandler and Symfony's profiler will accumulate across a long-lived worker. If you run the bundle in dev with the profiler enabled, memory grows regardless of connection resets — disable the profiler in any long-running process.kernel.reset overlap. Symfony's own services_resetter also operates on the kernel.reset tag. If you use both, reset ordering matters, and with 2.1.1's connection-closing behaviour double-resetting a connection is less obviously harmless than it used to be. Pick one mechanism per process; use this bundle's tag for the app-specific resetters and let the framework handle the rest, or don't enable both.enable flag or a service-list option (resetters: [...]) before assuming all resetters are on. Also check whether the bundle respects %kernel.debug% — some bundles skip wiring in debug, which produces the confusing "works in prod, silent no-op in dev" failure.^2.1 and let patch releases in: 2.1.1 is a pure robustness fix to the DBAL resetter with no API change, which is exactly the kind of patch you want. Still read the changelog on minor bumps — the DBAL resetter's reset semantics are the moving part in this package, and a 2.2 could change constructor signatures without deprecation.How can I help you explore Laravel packages today?