-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: abort in-flight request handlers on connection close #1735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
01003ff
3b88756
c277b48
a512684
48bbc87
b77008b
01193f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@modelcontextprotocol/core': patch | ||
| --- | ||
|
|
||
| Abort in-flight request handlers when the connection closes. Previously, request handlers would continue running after the transport disconnected, wasting resources and preventing proper cleanup. Also fixes `InMemoryTransport.close()` firing `onclose` twice on the initiating side. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -494,13 +494,28 @@ export abstract class Protocol<ContextT extends BaseContext> { | |
| this._taskManager.onClose(); | ||
| this._pendingDebouncedNotifications.clear(); | ||
|
|
||
| for (const info of this._timeoutInfo.values()) { | ||
| clearTimeout(info.timeoutId); | ||
| } | ||
| this._timeoutInfo.clear(); | ||
|
|
||
| const requestHandlerAbortControllers = this._requestHandlerAbortControllers; | ||
| this._requestHandlerAbortControllers = new Map(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The map swap here interacts with the pre-existing .finally(() => {
this._requestHandlerAbortControllers.delete(request.id); // live field → targets the NEW map
});Scenario: request Before this PR there was one map instance so the race couldn't happen. Identity check in the .finally(() => {
if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
this._requestHandlerAbortControllers.delete(request.id);
}
}); |
||
|
|
||
| const error = new SdkError(SdkErrorCode.ConnectionClosed, 'Connection closed'); | ||
|
|
||
| this._transport = undefined; | ||
| this.onclose?.(); | ||
|
|
||
| for (const handler of responseHandlers.values()) { | ||
| handler(error); | ||
| try { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This try/finally (and the one in this._transport.onclose = () => {
_onclose?.(); // if preserved transport handler throws…
this._onclose(); // …this never runs, defeating all the cleanup in this PR
};Worth wrapping that in try/finally too so a throwing transport-level |
||
| this.onclose?.(); | ||
| } finally { | ||
| for (const handler of responseHandlers.values()) { | ||
| handler(error); | ||
| } | ||
|
|
||
| for (const controller of requestHandlerAbortControllers.values()) { | ||
| controller.abort(error); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.