Add configurable session garbage collection (gcProbability/gcDivisor) (#258)

Make session GC probability configurable via gcProbability and gcDivisor
parameters, mirroring PHP's session.gc_probability/session.gc_divisor.
Exposed through both SessionManager constructor and Builder::setSession().

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Hertel 2026-05-15 08:14:38 +02:00 • committed by GitHub
commit 5e0731f706
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 155 additions and 7 deletions

View file

@ -79,6 +79,10 @@ final class Builder
private ?SessionStoreInterface $sessionStore = null;
private int $gcProbability = 1;
private int $gcDivisor = 100;
private int $paginationLimit = 50;
private ?string $instructions = null;
@ -336,12 +340,22 @@ final class Builder
return $this;
}
/**
* Configures the session layer.
*
* @param int $gcProbability The numerator of the GC probability fraction (like PHP's session.gc_probability). Set to 0 to disable GC.
* @param int $gcDivisor The denominator of the GC probability fraction (like PHP's session.gc_divisor). Probability = gcProbability/gcDivisor.
*/
public function setSession(
?SessionStoreInterface $sessionStore = null,
?SessionManagerInterface $sessionManager = null,
int $gcProbability = 1,
int $gcDivisor = 100,
): self {
$this->sessionStore = $sessionStore;
$this->sessionManager = $sessionManager;
$this->gcProbability = $gcProbability;
$this->gcDivisor = $gcDivisor;
if (null !== $sessionManager && null !== $sessionStore) {
throw new InvalidArgumentException('Cannot set both SessionStore and SessionManager. Set only one or the other.');
@ -534,6 +548,8 @@ final class Builder
$sessionManager = $this->sessionManager ?? new SessionManager(
$this->sessionStore ?? new InMemorySessionStore(),
$logger,
$this->gcProbability,
$this->gcDivisor,
);
if (null !== $this->discoveryBasePath) {

View file

@ -11,6 +11,7 @@
namespace Mcp\Server\Session;
use Mcp\Exception\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Uid\Uuid;
@ -22,10 +23,22 @@ use Symfony\Component\Uid\Uuid;
*/
class SessionManager implements SessionManagerInterface
{
/**
* @param int $gcProbability The probability (numerator) that GC will run on any given request. Combined with $gcDivisor to calculate the actual probability. Set to 0 to disable GC. Similar to PHP's session.gc_probability.
* @param int $gcDivisor The divisor used with $gcProbability to calculate GC probability. The probability is gcProbability/gcDivisor (e.g. 1/100 = 1%). Similar to PHP's session.gc_divisor.
*/
public function __construct(
private readonly SessionStoreInterface $store,
private readonly LoggerInterface $logger = new NullLogger(),
private readonly int $gcProbability = 1,
private readonly int $gcDivisor = 100,
) {
if ($gcProbability < 0) {
throw new InvalidArgumentException('gcProbability must be greater than or equal to 0.');
}
if ($gcDivisor < 1) {
throw new InvalidArgumentException('gcDivisor must be greater than or equal to 1.');
}
}
public function create(): SessionInterface
@ -54,7 +67,11 @@ class SessionManager implements SessionManagerInterface
*/
public function gc(): void
{
if (random_int(0, 100) > 1) {
if (0 === $this->gcProbability) {
return;
}
if (random_int(1, $this->gcDivisor) > $this->gcProbability) {
return;
}