Skip to content

[Server] Windows ProactorEventLoop WinError 64 kills IPv4 accept loop permanently, breaking Unity plugin reconnection #853

@CrowSoda

Description

@CrowSoda

Problem

On Windows, the MCP server uses Python's default ProactorEventLoop (IOCP-based). A transient Windows networking error, WinError 64: "The specified network name is no longer available", permanently kills the IPv4 socket accept loop while the IPv6 listener survives. Since the Unity plugin connects via ws://127.0.0.1:8080/hub/plugin (IPv4), all reconnect attempts fail and the plugin stays permanently disconnected.

MCP clients connecting over IPv6 (e.g., Claude Code via [::1]:8080) continue working fine, masking the issue.

How I Identified It

Server logs showed the IOCP accept failure:

asyncio - ERROR - Task exception was never retrieved
future: <Task finished coro=<IocpProactor.accept.<locals>.accept_coro() done>
  exception=OSError(22, 'The specified network name is no longer available', None, 64, None)>

asyncio - ERROR - Accept failed on a socket
socket: <asyncio.TransportSocket fd=980, family=2, type=1, proto=0, laddr=('127.0.0.1', 8080)>
OSError: [WinError 64] The specified network name is no longer available

After this error, netstat confirmed only the IPv6 listener survived:

TCP    [::1]:8080    [::]:0    LISTENING    (server PID)

The 127.0.0.1:8080 listener was gone. The Unity plugin disconnected (WebSocket close code 1005), exhausted all 6 reconnect attempts against the dead IPv4 address, and gave up permanently.

Reproduction

  • OS: Windows 11, Python 3.12
  • Server version: 9.4.7
  • Transport: HTTP (--transport http --http-url http://localhost:8080)

Happens intermittently. The IOCP WinError 64 is a known class of transient errors on Windows that the ProactorEventLoop doesn't recover from gracefully on the accept path.

Fix

One-line fix in Server/src/main.py. Force SelectorEventLoop on Windows, which uses select() instead of IOCP and is immune to this bug:

import sys
import asyncio

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

Place this early in main.py, after the asyncio import and before any event loop is created. SelectorEventLoop fully supports HTTP + WebSocket serving. The only features it lacks vs ProactorEventLoop are subprocess pipes and named pipes, which the MCP server doesn't use.

After applying this, both IPv4 and IPv6 listeners survive indefinitely and the Unity plugin reconnects normally.

Secondary Issue: Plugin Reconnect Gives Up Permanently

WebSocketTransportClient.AttemptReconnectAsync tries only 6 times (0s, 1s, 3s, 5s, 10s, 30s) then sets state to "Failed to reconnect" with no further retry. If the server has any transient issue lasting longer than ~49 seconds, the plugin is permanently dead until manually restarted. Consider adding an infinite retry tail (e.g., every 30s after the initial schedule).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions