fix(auth): re-establish auth guard in fork child; suppress EPIPE on peer-gone write
The deferred-message fork path (processDeferredMessages -> forkWithSocketPair) never re-runs authenticateConnection, so the child inherited $connection->user (passing the need_auth gate) but a cleared Auth guard — auth()->user() / User::auth() returned null, crashing handlers that read the guard (learn-atc GlitchTip #529/#532/#535/#531/#530). Sync the guard from the mock's user per fork with Auth::setUser() (setUser, not login, to avoid re-firing the Login event on every message). Also guard SocketPairIpc::sendToParent with @socket_write: when the WS client disconnects mid-response the parent tears down its read end, so the child's next write races a closed pipe (EPIPE / "Broken pipe"). That benign warning was promoted to a reported ErrorException and flooded GlitchTip (#461). Mirrors the existing @fwrite in Broadcast/BroadcastClient.php. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1bd747835f
commit
ed15ef8c23
|
|
@ -151,7 +151,13 @@ class SocketPairIpc
|
||||||
|
|
||||||
// Newline-delimited framing
|
// Newline-delimited framing
|
||||||
$message = $data . "\n";
|
$message = $data . "\n";
|
||||||
$written = socket_write($this->sockets[1], $message, strlen($message));
|
// Suppress the write warning: when the WS client disconnects mid-response
|
||||||
|
// the parent tears down its read end, so the child's next write races a
|
||||||
|
// closed pipe (EPIPE / "Broken pipe"). That warning is otherwise promoted
|
||||||
|
// to a reported ErrorException and floods GlitchTip for a benign, expected
|
||||||
|
// disconnect. The false-return is still handled by the caller. Mirrors the
|
||||||
|
// existing @fwrite in Broadcast/BroadcastClient.php.
|
||||||
|
$written = @socket_write($this->sockets[1], $message, strlen($message));
|
||||||
|
|
||||||
return $written === strlen($message);
|
return $written === strlen($message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -619,6 +619,29 @@ class Handler implements MessageComponentInterface
|
||||||
// Create mock that sends via socket pair
|
// Create mock that sends via socket pair
|
||||||
$mock = new MockConnectionSocketPair($connection, $ipc);
|
$mock = new MockConnectionSocketPair($connection, $ipc);
|
||||||
|
|
||||||
|
// Re-establish this fork's Laravel auth guard from the
|
||||||
|
// connection's user. authenticateConnection() logs the guard in,
|
||||||
|
// but it runs in the PARENT before forking — deferred messages
|
||||||
|
// fork later via processDeferredMessages() WITHOUT re-authenticating,
|
||||||
|
// and the parent's guard may since have been cleared (scheduleLogout)
|
||||||
|
// or reassigned to another connection. The mock still proxies the
|
||||||
|
// correct $connection->user (so the need_auth gate passes), yet
|
||||||
|
// auth()->user() / User::auth() would be null in this child —
|
||||||
|
// crashing any handler that reads the guard instead of ->user.
|
||||||
|
// Sync the guard to the connection here so every handler, deferred
|
||||||
|
// or not, sees a consistent authenticated user. setUser() (not
|
||||||
|
// login()) avoids re-firing the Login event on every message.
|
||||||
|
try {
|
||||||
|
if ($mock->user) {
|
||||||
|
Auth::setUser($mock->user);
|
||||||
|
} elseif (Auth::hasUser()) {
|
||||||
|
Auth::logout();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $authSyncError) {
|
||||||
|
// Best-effort: the need_auth gate in controll_message() is
|
||||||
|
// still the hard authorization check.
|
||||||
|
}
|
||||||
|
|
||||||
$this->executeControllerWithDbResilience(
|
$this->executeControllerWithDbResilience(
|
||||||
$mock,
|
$mock,
|
||||||
$channel,
|
$channel,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue