Skip to content

Fix issue853#856

Merged
Scriptwonder merged 5 commits intoCoplayDev:betafrom
Scriptwonder:Fix-issue853
Mar 3, 2026
Merged

Fix issue853#856
Scriptwonder merged 5 commits intoCoplayDev:betafrom
Scriptwonder:Fix-issue853

Conversation

@Scriptwonder
Copy link
Collaborator

@Scriptwonder Scriptwonder commented Mar 3, 2026

Description

Fix on issue #853

Type of Change

Save your change type

  • Bug fix (non-breaking change that fixes an issue)

Changes Made

Testing/Screenshots/Recordings

Documentation Updates

  • I have added/removed/modified tools or resources
  • If yes, I have updated all documentation files using:
    • The LLM prompt at tools/UPDATE_DOCS_PROMPT.md (recommended)
    • Manual updates following the guide at tools/UPDATE_DOCS.md

Related Issues

Fixes #853

Additional Notes

Summary by Sourcery

Improve WebSocket client reconnection behavior to handle longer server outages and bump the Unity MCP package version.

Bug Fixes:

  • Prevent the WebSocket transport from remaining permanently disconnected by continuing reconnection attempts after the initial backoff schedule is exhausted.

Enhancements:

  • Update transport state messaging and logging to reflect ongoing reconnect attempts after the initial schedule.
  • Keep the WebSocket transport in a retrying state instead of marking it as permanently disconnected when reconnection fails initially.

Build:

  • Bump the MCP for Unity package version to 9.4.5-beta.5.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed IPv4 connection issues on Windows systems.
  • Improvements

    • Enhanced WebSocket reconnection behavior to retry indefinitely at 30-second intervals instead of failing after initial reconnection attempts.

Copilot AI review requested due to automatic review settings March 3, 2026 03:58
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 3, 2026

Reviewer's Guide

Extends the WebSocket transport client's reconnect behavior to continue retrying at a fixed 30-second interval after the initial backoff schedule is exhausted, ensuring the Unity MCP plugin can recover from longer server outages, and bumps the package version to 9.4.5-beta.5.

Sequence diagram for extended WebSocket reconnection behavior

sequenceDiagram
    actor UnityEditor
    participant WebSocketTransportClient
    participant MCPServer
    participant CancellationTokenSource

    UnityEditor->>WebSocketTransportClient: StartConnection()
    alt Initial connection lost
        MCPServer--xWebSocketTransportClient: ConnectionClosed
        WebSocketTransportClient->>WebSocketTransportClient: AttemptReconnectAsync(token)

        loop Initial_backoff_schedule
            WebSocketTransportClient->>WebSocketTransportClient: Delay(ReconnectSchedule[i])
            alt Token cancelled
                CancellationTokenSource-->>WebSocketTransportClient: token.IsCancellationRequested
                WebSocketTransportClient-->>UnityEditor: Stop reconnect (cancelled)
                Note over WebSocketTransportClient: return
            else Try reconnect
                WebSocketTransportClient->>MCPServer: EstablishConnectionAsync(token)
                alt Reconnect success
                    MCPServer-->>WebSocketTransportClient: Connected
                    WebSocketTransportClient->>WebSocketTransportClient: _state = TransportState.Connected(...)
                    WebSocketTransportClient->>WebSocketTransportClient: _isConnected = true
                    WebSocketTransportClient-->>UnityEditor: Reconnected
                    Note over WebSocketTransportClient: return
                else Reconnect failed
                    MCPServer--xWebSocketTransportClient: Failure
                end
            end
        end

        WebSocketTransportClient->>WebSocketTransportClient: _state = _state.WithError("Server unreachable – retrying every 30 s")
        WebSocketTransportClient->>UnityEditor: Show retrying status

        loop Tail_retry_every_30s
            WebSocketTransportClient->>WebSocketTransportClient: Delay(ReconnectTailInterval)
            alt Token cancelled
                CancellationTokenSource-->>WebSocketTransportClient: token.IsCancellationRequested
                WebSocketTransportClient-->>UnityEditor: Stop reconnect (cancelled)
                Note over WebSocketTransportClient: return
            else Try reconnect
                WebSocketTransportClient->>MCPServer: EstablishConnectionAsync(token)
                alt Reconnect success
                    MCPServer-->>WebSocketTransportClient: Connected
                    WebSocketTransportClient->>WebSocketTransportClient: _state = TransportState.Connected(...)
                    WebSocketTransportClient->>WebSocketTransportClient: _isConnected = true
                    WebSocketTransportClient-->>UnityEditor: Reconnected
                    Note over WebSocketTransportClient: return
                else Reconnect failed
                    MCPServer--xWebSocketTransportClient: Failure
                end
            end
        end
    end
Loading

Class diagram for updated WebSocketTransportClient reconnect logic

classDiagram
    class WebSocketTransportClient {
        - static TimeSpan[] ReconnectSchedule
        - static TimeSpan ReconnectTailInterval
        - static TimeSpan DefaultKeepAliveInterval
        - static TimeSpan DefaultCommandTimeout
        - bool _isConnected
        - TransportState _state
        - int _isReconnectingFlag
        - Guid _sessionId
        - Uri _endpointUri
        + Task AttemptReconnectAsync(CancellationToken token)
        + Task~bool~ EstablishConnectionAsync(CancellationToken token)
    }

    class TransportState {
        + static TransportState Connected(string transportDisplayName, Guid sessionId, string details)
        + TransportState WithError(string message)
    }

    WebSocketTransportClient ..> TransportState : updates_state_using

    note for WebSocketTransportClient "ReconnectTailInterval is used to continue reconnect attempts every 30 seconds after ReconnectSchedule is exhausted"
Loading

File-Level Changes

Change Details Files
Extend WebSocket reconnection logic to keep retrying indefinitely after the initial schedule is exhausted.
  • Introduce a reusable constant for the tail reconnect interval (30 seconds).
  • After exhausting the existing reconnect delay schedule, log a warning and set the transport state to an error state indicating ongoing retries.
  • Enter a loop that delays for the tail interval and repeatedly attempts to re-establish the WebSocket connection until cancellation is requested.
  • On successful reconnection, update the transport state to Connected, mark the client as connected, log success, and exit the loop.
  • Remove the previous behavior that unconditionally set the transport state to Disconnected after a failed reconnect sequence.
MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
Bump Unity MCP package version for the new behavior.
  • Increment the package version from 9.4.5-beta.4 to 9.4.5-beta.5.
MCPForUnity/package.json

Assessment against linked issues

Issue Objective Addressed Explanation
#853 Modify the MCP server on Windows to use asyncio.WindowsSelectorEventLoopPolicy() instead of the default ProactorEventLoop in Server/src/main.py, to prevent transient WinError 64 from permanently killing the IPv4 accept loop. The diff shows no actual changes to Server/src/main.py; the hunk is empty. There is no addition of asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) or any other event loop configuration change, so the Windows server-side accept loop issue remains unaddressed.
#853 Update the Unity WebSocketTransportClient reconnect logic so that after the initial finite backoff schedule (0s, 1s, 3s, 5s, 10s, 30s) is exhausted, the client continues retrying indefinitely (e.g., every 30 seconds) instead of giving up permanently.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

The changes implement an indefinite reconnection retry tail interval in the Unity WebSocket client and configure the MCP server to use SelectorEventLoop on Windows to prevent IPv4 listener crashes caused by transient networking errors.

Changes

Cohort / File(s) Summary
Plugin Reconnection Resilience
MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
Introduces a 30-second tail interval constant and replaces the terminal "Failed to reconnect" state with indefinite retry logic. After the initial exponential backoff schedule exhausts, the client now enters an infinite loop attempting reconnection every 30 seconds until successful or cancelled.
Windows Event Loop Configuration
Server/src/main.py
Adds platform-specific asyncio event loop policy configuration. On Windows, forces WindowsSelectorEventLoopPolicy instead of the default ProactorEventLoop to prevent IPv4 socket accept loop failures caused by transient networking errors.

Poem

🐰 A tail of tails and Windows woes
Where reconnects now never close,
Thirty seconds, tick by tick,
We retry with a magic trick!
IPv4 lives, no more in pain, 🎉
SelectorLoop saves the day again!

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix issue853' is minimally descriptive and generic, using only an issue reference without conveying the actual changes made to the codebase. Revise the title to be more descriptive, such as 'Fix WebSocket reconnection and Windows event loop policy' to better reflect the main changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description includes the required template sections with the issue reference, bug fix classification, and Sourcery summary, but lacks detailed explanation of specific changes in the 'Changes Made' section.
Linked Issues check ✅ Passed The code changes fully address both objectives from issue #853: the Windows event loop policy fix is applied in main.py, and the WebSocket reconnection flow implements indefinite retry with a 30-second tail interval.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #853: WebSocket reconnection behavior and Windows event loop policy fixes, with no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • When the reconnect token is cancelled during the new infinite retry loop, the method returns without updating _state, leaving it stuck in the "Server unreachable – retrying every 30 s" error state; consider explicitly setting a final Disconnected/terminal state on cancellation to better reflect the actual transport status.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When the reconnect token is cancelled during the new infinite retry loop, the method returns without updating `_state`, leaving it stuck in the "Server unreachable – retrying every 30 s" error state; consider explicitly setting a final `Disconnected`/terminal state on cancellation to better reflect the actual transport status.

## Individual Comments

### Comment 1
<location path="MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs" line_range="708-711" />
<code_context>
             TimeSpan.FromSeconds(10),
             TimeSpan.FromSeconds(30)
         };
+        private static readonly TimeSpan ReconnectTailInterval = TimeSpan.FromSeconds(30);

         private static readonly TimeSpan DefaultKeepAliveInterval = TimeSpan.FromSeconds(15);
</code_context>
<issue_to_address>
**suggestion:** Keep log and error messages derived from `ReconnectTailInterval` to avoid hard‑coding 30s in multiple places.

Right now both the warning log and `_state.WithError("Server unreachable – retrying every 30 s")` hard‑code `30 s`. If `ReconnectTailInterval` changes, these messages will become inaccurate. Please format both messages using `ReconnectTailInterval` (e.g., `ReconnectTailInterval.TotalSeconds`) so they always match the configured interval.

```suggestion
                // Schedule exhausted — keep retrying every 30 s indefinitely so a transient
                // server outage longer than ~49 s doesn't leave the plugin permanently dead.
                McpLog.Warn($"[WebSocket] Initial reconnect schedule exhausted. Retrying every {ReconnectTailInterval.TotalSeconds}s until cancelled.");
                _state = _state.WithError($"Server unreachable – retrying every {ReconnectTailInterval.TotalSeconds} s");
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

…sportClient.cs

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Addresses Windows reconnection reliability for the MCP server + Unity plugin by preventing the server’s IPv4 accept loop from dying under ProactorEventLoop and making the Unity WebSocket client keep retrying reconnects indefinitely after the initial backoff schedule.

Changes:

  • Server: On Windows, force asyncio.WindowsSelectorEventLoopPolicy() early during startup to avoid ProactorEventLoop accept-loop failures (Issue #853).
  • Unity plugin: Extend WebSocket reconnect behavior with an infinite “tail” retry interval after the initial reconnect schedule is exhausted.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
Server/src/main.py Sets a Windows-specific asyncio event loop policy to improve IPv4 listener resilience.
MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs Adds an infinite reconnect tail interval to avoid permanently giving up after transient outages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import argparse
import asyncio

# Fix to IPV4 Connection Issue #853
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling/capitalization: the comment says "IPV4"; standard spelling is "IPv4" (and it’s referenced as IPv4 throughout the linked issue). Updating this avoids confusion and improves searchability.

Suggested change
# Fix to IPV4 Connection Issue #853
# Fix to IPv4 Connection Issue #853

Copilot uses AI. Check for mistakes.
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30)
};
private static readonly TimeSpan ReconnectTailInterval = TimeSpan.FromSeconds(30);
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReconnectTailInterval duplicates the last entry in ReconnectSchedule, and the later state/error text hard-codes "30 s" rather than referencing this constant. This can drift if either value changes; consider deriving the tail interval from the schedule and using ReconnectTailInterval when building user-facing messages/logs.

Suggested change
private static readonly TimeSpan ReconnectTailInterval = TimeSpan.FromSeconds(30);
private static readonly TimeSpan ReconnectTailInterval =
ReconnectSchedule[ReconnectSchedule.Length - 1];

Copilot uses AI. Check for mistakes.
@Scriptwonder Scriptwonder merged commit 2d0cfdf into CoplayDev:beta Mar 3, 2026
1 of 2 checks passed
@Scriptwonder Scriptwonder deleted the Fix-issue853 branch March 3, 2026 04:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants