fix(mcp): do not close browser while tool call is running - #42696
Anurag Singh (Anurag-M1) wants to merge 3 commits into
Conversation
|
@microsoft-github-policy-service agree |
|
Reporter of #42693 here. Checked this out and ran the exact reproducer from the issue against it, and it fixes the case I filed. Before, on On this branch: which matches what Glad to see that test come back specifically. It existed for this, was removed in #42666 while consolidating to one test per scenario, and #42676 then landed without it, which is how the guard went missing quietly. Nothing further from me, just wanted to confirm the fix from the reporting side. |
| if (this._running > 0) | ||
| --this._running; | ||
| if (this._running === 0) | ||
| this._timer = setTimeout(this._onIdle, this._timeout).unref(); |
There was a problem hiding this comment.
this can restart the timer after browser_close, potentially keeping the closed browser alive longer than needed
There was a problem hiding this comment.
Good catch! Pushed a fix:
- Added a
_disposedflag toIdleTimerand separated clearing the active timer during calls (_clearTimer) from permanent disposal (dispose). - Guarded
callStarted(),callFinished(), andpoke()so that once disposed, the timer is never re-armed. - Explicitly dispose
_idleTimeronbrowser_close/ disconnect so the closed browser instance is not retained. - Added regression test
does not restart the idle timer after browser_closeintests/mcp/idle-timeout.spec.ts.
This comment has been minimized.
This comment has been minimized.
| } | ||
| if (this._disconnected || responseObject.isClose) { | ||
| delete responseObject.isClose; | ||
| this._idleTimer?.dispose(); |
There was a problem hiding this comment.
with --isolated clients share this timer so browser_close from one client permanently disables idle cleanup for the remaining clients
There was a problem hiding this comment.
Good point. Removed this._idleTimer?.dispose() from BrowserBackend. In --isolated mode, clients share the same info.idleTimer, so closing one client's context/connection shouldn't disable idle cleanup for remaining clients. The timer is already disposed when the root browser disconnects via info.browser.once('disconnected', () => info.idleTimer?.dispose()) in browserFactory.ts, which sets _disposed = true and prevents callFinished() from restarting the timer.
| // Outlast the idle timeout to ensure the timer is not re-armed after browser_close. | ||
| await new Promise(f => setTimeout(f, 1000)); | ||
|
|
||
| expect(formatLog(stderr())).toEqual({ |
There was a problem hiding this comment.
a restarted timer does not emit another close browser log so this wont detect a regression
There was a problem hiding this comment.
Removed this test since formatLog(stderr()) cannot observe whether an already closed browser attempted to close again.
Test results for "MCP"3 failed 8587 passed, 1446 skipped Merge workflow run. |
|
Reporter of #42693 here. Since the last MCP run came back with three failures, I ran them against your branch locally to see which ones are actually in reach of this diff. Short version: none of the three reproduce for me, and two of them cannot be caused by this change. macOS arm64, chrome, at The diff touches only
One thing that cost me twenty minutes and might save you some: this branch predates the chromium roll in Separately, thanks for doing this one. The repro in my issue was the easy half; restoring the deleted test is the part that stops it coming back. |
Summary
callStarted()andcallFinished()onIdleTimerwith reference counting (_running), ensuring the timer is cleared during tool execution and only re-armed once all active calls complete.poke()guarded so that daemon or client initialization does not arm the timer while calls are in progress, and restores.unref()so pending idle timeouts do not hold the Node event loop open.does not close the browser while a tool call is runningintests/mcp/idle-timeout.spec.ts.Fixes #42693