Repository navigation
Conversation
The MCP server reads one request from stdin, awaits handle_request() for it and only then reads the next one. So a query that scans the whole database, grep_functions over every function body or find_implementors, holds up every other client of the server until it is done, and they all run into the idle timeout of the MCP client at the same time. Twenty review agents on one server stalled for the better part of an hour behind a few such scans, while the CLI, which runs the same query code in a process of its own, kept answering in under a second. So hand each request to a task of its own and let the responses go out as they finish. JSON-RPC matches them by id, the order doesn't matter, and the stdout lock already serializes the writes with the notification task. The handlers borrow their arguments across awaits in a way rustc can't prove Send for, so the task drives the request on a thread of the blocking pool with Handle::block_on() instead of running it on a worker. That also keeps a CPU bound scan off the runtime's workers. The number of requests in flight is capped at the number of CPUs with a semaphore, so that any number of scans can't thrash the machine. Notifications are still handled inline, there is nothing to wait for there. A task that can't write its response gives up on it and logs that, the loop ends on its own when stdin is closed. A find_function sent right after a grep_functions over all function bodies of a kernel tree used to be answered after the scan, 26 seconds in. Now it comes back in 4 seconds while the scan is still running. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The MCP server reads one request from stdin, awaits handle_request()
for it and only then reads the next one. So a query that scans the
whole database, grep_functions over every function body or
find_implementors, holds up every other client of the server until it
is done, and they all run into the idle timeout of the MCP client at
the same time. Twenty review agents on one server stalled for the
better part of an hour behind a few such scans, while the CLI, which
runs the same query code in a process of its own, kept answering in
under a second.
So hand each request to a task of its own and let the responses go
out as they finish. JSON-RPC matches them by id, the order doesn't
matter, and the stdout lock already serializes the writes with the
notification task. The handlers borrow their arguments across awaits
in a way rustc can't prove Send for, so the task drives the request on
a thread of the blocking pool with Handle::block_on() instead of
running it on a worker. That also keeps a CPU bound scan off the
runtime's workers. The number of requests in flight is capped at the
number of CPUs with a semaphore, so that any number of scans can't
thrash the machine. Notifications are still handled inline, there is
nothing to wait for there. A task that can't write its response gives
up on it and logs that, the loop ends on its own when stdin is closed.
A find_function sent right after a grep_functions over all function
bodies of a kernel tree used to be answered after the scan, 26 seconds
in. Now it comes back in 4 seconds while the scan is still running.