Skip to content

fix: make Close() synchronously close xray instances#86

Merged
cnlangzi merged 3 commits intomainfrom
fix/sync-close
Apr 22, 2026
Merged

fix: make Close() synchronously close xray instances#86
cnlangzi merged 3 commits intomainfrom
fix/sync-close

Conversation

@cnlangzi
Copy link
Copy Markdown
Owner

@cnlangzi cnlangzi commented Apr 22, 2026

Summary

Change Close() to immediately close xray instances instead of marking them as draining with a 30-second delayed sweep.

Problem

The previous behavior marked xray instances as "draining" but only closed them after DrainTimeout (30 seconds). When testing high volumes of vless/vmess/trojan proxies, this caused:

  • Goroutine accumulation (5000+ goroutines stuck waiting)
  • Memory growth (1.3GB+ RSS over time)
  • Server timeouts and crashes

Solution

Make Close() synchronously close the xray instance and remove it from the servers map immediately.

Test plan

  • Deploy to mux-ny-us with pprof enabled
  • Memory stays stable at ~200MB instead of growing to 1.3GB+
  • Goroutine count stays stable at ~5000 instead of accumulating

Summary by Sourcery

Bug Fixes:

  • Eliminate goroutine and memory leaks caused by delayed draining of xray instances by making Close perform an immediate shutdown.

Xiage added 2 commits April 22, 2026 11:38
Add CloseImmediately() function that synchronously closes xray instances
instead of relying on the delayed sweeper mechanism. This prevents goroutine
and memory leaks when testing high volumes of vless/vmess/trojan proxies.

The sweeper's 30-second DrainTimeout caused accumulation of xray instances
when tests completed faster than the timeout, leading to resource exhaustion.
Change Close() to immediately close xray instances instead of marking them
as draining with a 30-second delayed sweep. This prevents goroutine and memory
leaks when testing high volumes of vless/vmess/trojan proxies where the
previous delayed-close behavior caused resource accumulation.
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 22, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

This PR changes the xray Close() behavior from deferred, drain-based shutdown to an immediate, synchronous close and removal of instances from the servers map to prevent goroutine and memory leaks under high load.

Sequence diagram for synchronous xray Close behavior

sequenceDiagram
    participant Caller
    participant XrayPackage as XrayPackage
    participant ServersMap as ServersMap
    participant Server
    participant Instance

    Caller->>XrayPackage: Close(proxyURL)
    XrayPackage->>ServersMap: lookup proxyURL
    alt server_found
        ServersMap-->>XrayPackage: srv
        XrayPackage->>Instance: Close()
        Instance-->>XrayPackage: (returns)
        XrayPackage->>ServersMap: delete proxyURL
        ServersMap-->>XrayPackage: (removed)
    else server_not_found
        ServersMap-->>XrayPackage: not_found
        XrayPackage-->>Caller: return
    end
    XrayPackage-->>Caller: return
Loading

Class diagram for updated xray Close and Server

classDiagram
    class XrayPackage {
        +map[string]*Server servers
        +Mutex mu
        +Close(proxyURL string)
        +tryCloseAndDelete(url string, srv *Server)
    }

    class Server {
        +Instance *XrayInstance
        +DrainedAt time_Time
    }

    class XrayInstance {
        +Close() error
    }

    XrayPackage --> "*" Server : manages
    Server --> XrayInstance : has

    %% Highlight of Close behavior change
    class CloseOld {
        +Close(proxyURL string)
        - set DrainedAt
        - startSweeper()
    }

    class CloseNew {
        +Close(proxyURL string)
        - srv.Instance.Close()
        - delete from servers
    }

    XrayPackage ..> CloseOld : replaced_by
    XrayPackage ..> CloseNew : current_impl
Loading

State diagram for xray instance lifecycle before and after Close change

stateDiagram-v2
    state OldBehavior {
        [*] --> Active
        Active --> Draining: Close(proxyURL)
        Draining --> Closed: sweeper_after_DrainTimeout
        Closed --> [*]
    }

    state NewBehavior {
        [*] --> Active
        Active --> Closed: Close(proxyURL)
        Closed --> [*]
    }
Loading

File-Level Changes

Change Details Files
Make Close() synchronously close and remove xray instances instead of marking them as draining for a later sweeper.
  • Remove sweeper startup from Close(), so Close() no longer relies on a background goroutine and DrainTimeout-based cleanup.
  • Change Close() to acquire the servers mutex, look up the server by proxy URL, and return early if it does not exist.
  • If a matching server exists and has a non-nil Instance, call Instance.Close() (ignoring any error) within the critical section.
  • Immediately delete the server entry from the servers map after closing the instance, ensuring no lingering references remain.
  • Update the Close() function comment to document the new synchronous close semantics and rationale about preventing goroutine and memory leaks.
xray/xray.go

Possibly linked issues

  • #(none provided): They target the same xray goroutine/memory leak from server lifecycle, though with opposite design approaches.

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

Copy link
Copy Markdown

@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 left some high level feedback:

  • Consider cleaning up now-unused draining/sweeper logic (e.g., startSweeper, tryCloseAndDelete, DrainTimeout config, and related comments like CloseAll) so the lifecycle model is consistent and easier to reason about.
  • With Close now synchronously closing and deleting the server, double-check that any callers that still hold references to srv.Instance are properly synchronized to avoid use-after-close races or unexpected panics.
  • If there are scenarios where in-flight operations need a grace period, it may be worth making the synchronous vs. delayed close behavior explicit via separate methods or a parameter rather than hard-switching the existing Close semantics.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider cleaning up now-unused draining/sweeper logic (e.g., `startSweeper`, `tryCloseAndDelete`, `DrainTimeout` config, and related comments like `CloseAll`) so the lifecycle model is consistent and easier to reason about.
- With `Close` now synchronously closing and deleting the server, double-check that any callers that still hold references to `srv.Instance` are properly synchronized to avoid use-after-close races or unexpected panics.
- If there are scenarios where in-flight operations need a grace period, it may be worth making the synchronous vs. delayed close behavior explicit via separate methods or a parameter rather than hard-switching the existing `Close` semantics.

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.

Update TestCloseRevivesServer to TestCloseRemovesServer since
Close() now immediately removes the server from the map instead of
marking it as draining.

Update TestCloseIdempotent to check that server is removed from map
rather than checking DrainedAt.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 71.42857% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 11.85%. Comparing base (1f1d127) to head (24f5368).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
xray/xray.go 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #86      +/-   ##
==========================================
- Coverage   12.71%   11.85%   -0.87%     
==========================================
  Files          27       27              
  Lines        1565     2194     +629     
==========================================
+ Hits          199      260      +61     
- Misses       1351     1918     +567     
- Partials       15       16       +1     
Flag Coverage Δ
Tests 11.85% <71.42%> (-0.87%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cnlangzi cnlangzi merged commit 4feb5e5 into main Apr 22, 2026
6 of 7 checks passed
@cnlangzi cnlangzi deleted the fix/sync-close branch April 22, 2026 03:54
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.

1 participant