From aabe29befd79efa121fb01a036b4aaa821bbd304 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 14 Oct 2026 01:32:43 +0200 Subject: [PATCH 1/3] [Docs] Explain the cost of FileSessionStore GC and how to run it out of band --- docs/run/sessions.md | 30 +++++++++++++++++++++++++ src/Server/Session/FileSessionStore.php | 1 + 2 files changed, 31 insertions(+) diff --git a/docs/run/sessions.md b/docs/run/sessions.md index 200869db..978f5770 100644 --- a/docs/run/sessions.md +++ b/docs/run/sessions.md @@ -81,6 +81,36 @@ $server = Server::builder() ->build(); ``` +**Running GC Out of Band:** + +GC runs inside a request, before the server handles the message. `FileSessionStore::gc()` scans every file in +the session directory, so its cost grows with the number of sessions. On a busy server, a random request then +waits for that scan. + +In that case, disable GC in the server and call `gc()` from a cron job or a console command. Use the same +directory and TTL as the server: + +```php +// bin/session-gc.php, e.g. run every 10 minutes from cron +use Mcp\Server\Session\FileSessionStore; + +require __DIR__ . '/../vendor/autoload.php'; + +$store = new FileSessionStore(__DIR__ . '/../sessions', ttl: 3600); +$deleted = $store->gc(); // returns the IDs of the deleted sessions + +echo count($deleted) . " expired sessions deleted\n"; +``` + +```php +$server = Server::builder() + ->setSession(new FileSessionStore(__DIR__ . '/sessions', ttl: 3600), gcProbability: 0) + ->build(); +``` + +`Psr16SessionStore` doesn't scan anything: it stores each session with the TTL, and its `gc()` does nothing. +With a cache like Redis, the cache expires the keys itself. + **Parameters:** - `$gcProbability` (int): The numerator of the GC probability fraction (default: `1`). Set to `0` to disable GC. diff --git a/src/Server/Session/FileSessionStore.php b/src/Server/Session/FileSessionStore.php index 713f6e82..e7d015d1 100644 --- a/src/Server/Session/FileSessionStore.php +++ b/src/Server/Session/FileSessionStore.php @@ -21,6 +21,7 @@ /** * File-based session store. * Stores each session as a file named by the RFC4122 UUID, with the payload. + * gc() scans the whole directory, so its cost grows with the number of sessions. */ class FileSessionStore implements SessionStoreInterface { From a8ed7006aa02a5a48439267a52dd51cf8f2995e2 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 14 Oct 2026 01:37:48 +0200 Subject: [PATCH 2/3] [Docs] Say when to move from FileSessionStore to a shared cache --- docs/run/sessions.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/run/sessions.md b/docs/run/sessions.md index 978f5770..fe418f73 100644 --- a/docs/run/sessions.md +++ b/docs/run/sessions.md @@ -49,6 +49,11 @@ With HTTP, PHP often serves each request in a different process (e.g. PHP-FPM wo doesn't find them. Use a store that all processes share, like `FileSessionStore` on a shared directory or `Psr16SessionStore` with a shared cache such as Redis. +`FileSessionStore` suits a single host. Once requests are spread over several hosts, or sessions number in the +tens of thousands, use `Psr16SessionStore` with Redis or a similar shared cache: it is shared across hosts, +expires sessions on its own and needs no garbage collection (see below). A directory shared over a network +filesystem is no good substitute, file locking is unreliable there. + ## Garbage Collection Configuration The SDK periodically runs garbage collection to clean up expired sessions, similar to PHP's native From ddd0c960874ee13f86859248b4e7b63ecb27fbb3 Mon Sep 17 00:00:00 2001 From: Christopher Hertel Date: Wed, 14 Oct 2026 01:44:28 +0200 Subject: [PATCH 3/3] Potential fix for pull request finding 'Clarify rationale for omitting file locking in FileSessionStore' Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/run/sessions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/run/sessions.md b/docs/run/sessions.md index fe418f73..48266e09 100644 --- a/docs/run/sessions.md +++ b/docs/run/sessions.md @@ -51,8 +51,8 @@ directory or `Psr16SessionStore` with a shared cache such as Redis. `FileSessionStore` suits a single host. Once requests are spread over several hosts, or sessions number in the tens of thousands, use `Psr16SessionStore` with Redis or a similar shared cache: it is shared across hosts, -expires sessions on its own and needs no garbage collection (see below). A directory shared over a network -filesystem is no good substitute, file locking is unreliable there. +expires sessions on its own and needs no garbage collection (see below). A network-mounted directory adds latency and +makes session availability depend on the shared filesystem. ## Garbage Collection Configuration