Skip to content

Commit 6834372

Browse files
Fix Windows ResourceWarning by properly closing process streams
The new stdio cleanup sequence was causing ResourceWarning on Windows CI about unclosed _ProactorReadPipeTransport objects. This was because we were terminating processes while their stdout streams were still open. Changes: - Close read_stream_writer and write_stream_reader first to signal tasks - Explicitly close process.stdout before terminating the process - This ensures Windows ProactorEventLoop properly cleans up pipe transports The warnings were surfacing in unrelated tests due to garbage collection timing, but the root cause was incomplete cleanup in stdio_client.
1 parent f5a122c commit 6834372

File tree

2 files changed

+15
-5
lines changed

2 files changed

+15
-5
lines changed

src/mcp/client/stdio/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,26 @@ async def stdin_writer():
184184
# 1. Close input stream to server
185185
# 2. Wait for server to exit, or send SIGTERM if it doesn't exit in time
186186
# 3. Send SIGKILL if still not exited
187+
188+
# First, close our streams to signal the reader/writer tasks to stop
189+
await read_stream_writer.aclose()
190+
await write_stream_reader.aclose()
191+
187192
if process.stdin:
188193
try:
189194
await process.stdin.aclose()
190195
except Exception:
191196
# stdin might already be closed, which is fine
192197
pass
193198

199+
# Close stdout to ensure the ProactorReadPipeTransport is properly closed
200+
if process.stdout:
201+
try:
202+
await process.stdout.aclose()
203+
except Exception:
204+
# stdout might already be closed, which is fine
205+
pass
206+
194207
try:
195208
# Give the process time to exit gracefully after stdin closes
196209
with anyio.fail_after(2.0):
@@ -211,8 +224,6 @@ async def stdin_writer():
211224

212225
await read_stream.aclose()
213226
await write_stream.aclose()
214-
await read_stream_writer.aclose()
215-
await write_stream_reader.aclose()
216227

217228

218229
def _get_executable_command(command: str) -> str:

src/mcp/client/stdio/win32.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,7 @@ async def create_windows_process(
158158
except Exception as e:
159159
# If anyio failed for other reasons, try without creation flags
160160
logger.info(
161-
f"anyio.open_process failed with creation flags, "
162-
f"retrying without flags for command: {command}. Error: {e}"
161+
f"anyio.open_process failed with creation flags, retrying without flags for command: {command}. Error: {e}"
163162
)
164163
process = await anyio.open_process(
165164
[command, *args],
@@ -210,7 +209,7 @@ def _create_windows_fallback_process(
210209
except Exception as e:
211210
# If creationflags failed, try without them
212211
logger.info(
213-
f"FallbackProcess with creation flags failed, " f"retrying without flags for command: {command}. Error: {e}"
212+
f"FallbackProcess with creation flags failed, retrying without flags for command: {command}. Error: {e}"
214213
)
215214
popen_obj = subprocess.Popen(
216215
[command, *args],

0 commit comments

Comments
 (0)