You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
StdioClientTransport.sendMessage() fails with concurrent calls due to unicast sink race #875
StdioClientTransport.sendMessage() uses tryEmitNext() on a unicast Reactor sink. When two threads call sendMessage() concurrently on the same transport, one call fails with "Failed to enqueue message" due to FAIL_NON_SERIALIZED.
This happens because Sinks.many().unicast().onBackpressureBuffer() is wrapped by Reactor's SinkManySerialized, which uses a CAS-based guard in tryEmitNext(). When two threads race on the CAS, the loser immediately gets FAIL_NON_SERIALIZED — the method does not retry.
When this occurs
Any time two threads call McpSyncClient.callTool() (or any other method that calls sendMessage()) concurrently on the same client instance. This is a normal scenario when an MCP server proxies tool calls from parallel requests to a downstream MCP server via stdio transport.
Current code
// StdioClientTransport.java line 229publicMono<Void> sendMessage(JSONRPCMessagemessage) {
if (this.outboundSink.tryEmitNext(message).isSuccess()) {
returnMono.empty();
}
else {
returnMono.error(newRuntimeException("Failed to enqueue message"));
}
}
The existing TODO comment at line 230 acknowledges this limitation:
"we delegate the retry and the backpressure onto the caller. This might be enough for most cases."
Proposed fix
Replace tryEmitNext with emitNext + busyLooping, which spin-retries on FAIL_NON_SERIALIZED until the competing thread finishes its CAS (microseconds):
Activity
Add test reproducing concurrent sendMessage race condition
Fix concurrent sendMessage race by retrying on FAIL_NON_SERIALIZED
chemicL commentedon Sep 4, 2026
Solved via #935.