Skip to content

Commit c945688

Browse files
committed
fix(windows/lib.rs): implemented single global Tokio runtime to avoid EnterGuard values to be dropped out of order
1 parent 31f4451 commit c945688

File tree

1 file changed

+29
-26
lines changed

1 file changed

+29
-26
lines changed

src/lib.rs

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,25 @@ use rmcp::transport::{SseClientTransport, StreamableHttpClientTransport};
1414
use rmcp::{ServiceExt, RoleClient};
1515
use rmcp::model::{ClientInfo, ClientCapabilities, Implementation};
1616

17-
// Global client instance (simplified approach - one client per process)
17+
// Global runtime instance - single runtime for entire process
18+
static GLOBAL_RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
19+
20+
// Global client instance
1821
static GLOBAL_CLIENT: OnceLock<Mutex<Option<McpClient>>> = OnceLock::new();
1922

23+
/// Get or create the global Tokio runtime
24+
fn get_runtime() -> &'static tokio::runtime::Runtime {
25+
GLOBAL_RUNTIME.get_or_init(|| {
26+
tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime")
27+
})
28+
}
29+
2030
/// Initialize the MCP library
2131
/// Returns 0 on success, non-zero on error
2232
#[no_mangle]
2333
pub extern "C" fn mcp_init() -> i32 {
24-
// Initialize any global state here
34+
// Initialize the runtime early
35+
let _ = get_runtime();
2536
0
2637
}
2738

@@ -51,7 +62,6 @@ type RunningClient = rmcp::service::RunningService<RoleClient, ClientInfo>;
5162

5263
/// Opaque handle for MCP client
5364
pub struct McpClient {
54-
runtime: tokio::runtime::Runtime,
5565
service: Mutex<Option<RunningClient>>,
5666
server_url: Mutex<Option<String>>,
5767
}
@@ -60,17 +70,14 @@ pub struct McpClient {
6070
/// Returns NULL on error
6171
#[no_mangle]
6272
pub extern "C" fn mcp_client_new() -> *mut McpClient {
63-
match tokio::runtime::Runtime::new() {
64-
Ok(runtime) => {
65-
let client = Box::new(McpClient {
66-
runtime,
67-
service: Mutex::new(None),
68-
server_url: Mutex::new(None),
69-
});
70-
Box::into_raw(client)
71-
}
72-
Err(_) => ptr::null_mut(),
73-
}
73+
// Initialize runtime early
74+
let _ = get_runtime();
75+
76+
let client = Box::new(McpClient {
77+
service: Mutex::new(None),
78+
server_url: Mutex::new(None),
79+
});
80+
Box::into_raw(client)
7481
}
7582

7683
/// Free an MCP client
@@ -137,24 +144,18 @@ pub extern "C" fn mcp_connect(
137144
}
138145
};
139146

140-
// Create a new McpClient with runtime
147+
// Create a new McpClient
141148
let new_client = McpClient {
142-
runtime: match tokio::runtime::Runtime::new() {
143-
Ok(r) => r,
144-
Err(e) => {
145-
let error = format!(r#"{{"error": "Failed to create runtime: {}"}}"#, e);
146-
return CString::new(error).unwrap_or_default().into_raw();
147-
}
148-
},
149149
service: Mutex::new(None),
150150
server_url: Mutex::new(None),
151151
};
152152

153153
let use_sse = legacy_sse != 0;
154+
let runtime = get_runtime();
154155

155156
let (result, maybe_service) = if use_sse {
156157
// Use SSE transport (legacy) with optional custom headers
157-
new_client.runtime.block_on(async {
158+
runtime.block_on(async {
158159
// Create HTTP client with optional custom headers
159160
let mut client_builder = reqwest::Client::builder();
160161
if let Some(ref headers_map) = headers_map {
@@ -240,7 +241,7 @@ pub extern "C" fn mcp_connect(
240241
})
241242
} else {
242243
// Use streamable HTTP transport (default) with optional custom headers
243-
new_client.runtime.block_on(async {
244+
runtime.block_on(async {
244245
// For Streamable HTTP, we need to extract the Authorization header specifically
245246
// since it has a dedicated field, and we'll use a custom HTTP client for other headers
246247
let auth_header_value = headers_map.as_ref().and_then(|m| m.get("Authorization")).map(|s| s.clone());
@@ -368,7 +369,8 @@ pub extern "C" fn mcp_list_tools(_client_ptr: *mut McpClient) -> *mut c_char {
368369
}
369370
};
370371

371-
let result = client.runtime.block_on(async {
372+
let runtime = get_runtime();
373+
let result = runtime.block_on(async {
372374
let service_guard = client.service.lock().unwrap();
373375
let service = match service_guard.as_ref() {
374376
Some(s) => s,
@@ -462,7 +464,8 @@ pub extern "C" fn mcp_call_tool(
462464
}
463465
};
464466

465-
let result = client.runtime.block_on(async {
467+
let runtime = get_runtime();
468+
let result = runtime.block_on(async {
466469
let service_guard = client.service.lock().unwrap();
467470
let service = match service_guard.as_ref() {
468471
Some(s) => s,

0 commit comments

Comments
 (0)