fix(fork): fully isolate inherited Redis connections in the message child
The per-message fork child purged cache/redis manager singletons but never reset
the session bindings, and never actively disconnected the fds it inherited. Once an
app puts session + cache on Redis, the parent's authenticateConnection() opens Redis
sockets before forking; a child touching any un-reset connection interleaves on the
shared fd and desyncs the predis protocol on BOTH sides — flooding
"unserialize(): Error at offset 0 of N bytes" and
"Predis ConnectionException: Error while reading line from the server".
In the child, before purging: disconnect() every configured redis connection (closes
only the child's fd copy — predis disconnect fcloses without sending a command, so
the parent socket is untouched; any later reuse reconnects fresh instead of
desyncing), then also forgetInstance('session') + 'session.store' so a Redis-backed
session store rebuilds lazily too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b678a25a3c
commit
2a9986d209
|
|
@ -590,13 +590,44 @@ class Handler implements MessageComponentInterface
|
||||||
// This saves ~5-15ms for methods that don't use the database
|
// This saves ~5-15ms for methods that don't use the database
|
||||||
DB::disconnect();
|
DB::disconnect();
|
||||||
|
|
||||||
// Purge inherited Redis/cache connections from parent process.
|
// Purge inherited Redis/cache/session connections from the parent.
|
||||||
// After fork(), child inherits parent's Redis socket fd — using it
|
// After fork(), the child inherits the parent's Redis socket fds; using
|
||||||
// would corrupt parent's protocol state. Purging forces fresh
|
// any of them interleaves requests on the shared socket and desyncs the
|
||||||
// connections on next cache() call (predis connects lazily).
|
// predis protocol on BOTH the child AND the parent (unserialize "offset 0
|
||||||
|
// of N bytes" / "Error while reading line"). Once session + cache live on
|
||||||
|
// Redis, the parent's authenticateConnection() opens Redis fds before it
|
||||||
|
// forks, so the child must drop EVERY inherited connection.
|
||||||
|
//
|
||||||
|
// 1) Actively disconnect each configured connection — this closes ONLY the
|
||||||
|
// child's fd copy (predis disconnect just fcloses, it sends no command,
|
||||||
|
// so the parent's socket is untouched), turning any accidental later
|
||||||
|
// reuse into a fresh reconnect instead of a desync.
|
||||||
|
try {
|
||||||
|
$redisManager = app('redis');
|
||||||
|
foreach (config('database.redis', []) as $redisName => $redisConf) {
|
||||||
|
// Skip non-connection entries (client, options, clusters, …).
|
||||||
|
if (! is_array($redisConf) || ! isset($redisConf['host'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redisManager->connection($redisName)->disconnect();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// per-connection best-effort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// best-effort; the forgetInstance() calls below still force fresh managers
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Forget the singletons so the next cache()/session()/Redis() call
|
||||||
|
// lazily rebuilds with a fresh socket. session/session.store were the
|
||||||
|
// gap: once the session store is Redis-backed they hold their OWN
|
||||||
|
// inherited Redis connection, which the cache/redis purge never reset.
|
||||||
app()->forgetInstance('cache');
|
app()->forgetInstance('cache');
|
||||||
app()->forgetInstance('cache.store');
|
app()->forgetInstance('cache.store');
|
||||||
app()->forgetInstance('redis');
|
app()->forgetInstance('redis');
|
||||||
|
app()->forgetInstance('session');
|
||||||
|
app()->forgetInstance('session.store');
|
||||||
|
|
||||||
// Configure DB reconnect-on-lost-connection for this child.
|
// Configure DB reconnect-on-lost-connection for this child.
|
||||||
// If MySQL returns "Too many connections" or "server has gone away",
|
// If MySQL returns "Too many connections" or "server has gone away",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue