From 4da9873b838d15cefd5c6285ce3bddbd21aa10cb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 17 Apr 2026 22:27:28 +1200 Subject: [PATCH] (fix): purge cached user document before Realtime session verification Cross-process read-after-write race: the HTTP worker writes a new session into the user document on /account login endpoints, then the client sends an authentication frame over a different Swoole process (Realtime). Cache propagation between processes is not guaranteed to be observed on the very next read, so sessionVerify() occasionally fails with a stale user document whose sessions array does not yet contain the just-created one. Purge the cached user locally before the read so sessionVerify sees the freshly-written session deterministically. Overhead is bounded: one DEL on cache + one extra primary-key read, executed once per WebSocket authentication frame (not per message). Surfaces on PR appwrite-labs/cloud#3214 as an intermittent 'Session is not valid.' failure in RealtimeConsoleClientTest + RealtimeCustomClientTest manual-authentication cases in dedicated mode. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/realtime.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/realtime.php b/app/realtime.php index 955832e93a..9e5486e638 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -916,8 +916,19 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re $store->decode($message['data']['session']); + $userId = $store->getProperty('id', ''); + + // Read-after-write across processes: the HTTP worker writes the session + // to the user document on /account endpoints, then the client sends an + // authentication frame here over a different Swoole process. Cache-layer + // propagation isn't guaranteed to be observed on the next read — purge + // locally so sessionVerify sees the freshly-written session. + if (!empty($userId)) { + $database->purgeCachedDocument('users', $userId); + } + /** @var User $user */ - $user = $database->getDocument('users', $store->getProperty('id', '')); + $user = $database->getDocument('users', $userId); /** * TODO: