McpClientSession/McpServerSession: pending response entries leak when a request times out or is cancelled

Ouverte
#1,133 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Évaluation

Difficulté
3/5
Temps estimé
1-2 jours
Accessibilité débutants
35/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
java
Domaine
api, backend, testing

Piste de recherche

Commencez par McpClientSession.sendRequest et McpServerSession.sendRequest, puis comparez-les à McpStreamableServerSession.McpStreamableServerSessionStream.sendRequest. Exécutez le test de régression McpClientSessionTests indiqué et ajoutez une couverture équivalente pour les chemins d’expiration du délai et d’annulation ; c’est terminé lorsque pendingResponses est vide après l’une ou l’autre condition.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

area/client area/server area/transport bug P2

Bug description

McpClientSession.sendRequest and McpServerSession.sendRequest put an entry into pendingResponses when a request is sent, but only remove it when a response arrives or the send itself fails. If the downstream .timeout(...) fires — or the caller cancels the Mono — no cleanup hook runs, so the entry stays in pendingResponses forever (request IDs are unique per request, so it is never overwritten).

The streamable variant already cleans up after its timeout (McpStreamableServerSession.McpStreamableServerSessionStream.sendRequest calls this.pendingResponses.remove(requestId) in a doOnError after .timeout(requestTimeout)), which suggests the legacy paths missing this is an oversight.

Amplifier

KeepAliveScheduler pings every session periodically via sendRequest(PING, ...). A dead or hung session therefore accumulates one leaked entry per ping interval on both the client and the server side; long-lived sessions grow unboundedly until close().

Suggested fix

Mirror the streamable pattern in both sendRequest methods:

.timeout(this.requestTimeout)
.doOnError(e -> this.pendingResponses.remove(requestId))
.doOnCancel(() -> this.pendingResponses.remove(requestId))
.handle(...)

Removal is idempotent, and the late-response path already tolerates a missing key.

I have the fix plus a regression test (testRequestTimeoutRemovesPendingResponse in McpClientSessionTests, asserting the map is empty after a timeout) ready and will open a PR referencing this issue.

Langage dominant
Java
Étoiles
3.7k
Forks
1.1k
Merge moyen
1 j 15 h
PR mergées (30 j)
9

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de modelcontextprotocol/java-sdk

Toutes les issues de modelcontextprotocol/java-sdk

Issues similaires

Plus d'issues Java

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.