Skip to content

Fix potential race condition in shm_broadcast.py and unify socket cre… - #15903

Open
Baidu-AIAK wants to merge 2 commits into
sgl-project:mainfrom
Baidu-AIAK:community-0.5.6-zmqerror-fix
Open

Baidu-AIAK wants to merge 2 commits into
sgl-project:mainfrom
Baidu-AIAK:community-0.5.6-zmqerror-fix

Conversation

@Baidu-AIAK

@Baidu-AIAK Baidu-AIAK commented Dec 26, 2025 •

Copy link
Copy Markdown
Contributor

Motivation

Fixes #15885
This PR addresses a potential race condition (TOCTOU) in python/sglang/srt/distributed/device_communicators/shm_broadcast.py.
Currently, the code uses get_open_port() to find an available port and subsequently binds to it. This approach is not atomic: another process could claim the port between the time it is returned by get_open_port() and the time socket.bind() is called.
To resolve this, I have refactored the code to use the existing get_zmq_socket helper function, which leverages ZeroMQ's native bind_to_random_port method. This method is atomic and safer.
Additionally, to maintain the original behavior of binding strictly to localhost (127.0.0.1) rather than all interfaces (0.0.0.0), I extended the get_zmq_socket helper to support a customizable host parameter.

Modifications

  1. python/sglang/srt/utils/common.py:
  • Refactored get_zmq_socket to fix critical logic errors:

    • Corrected IPv6 URI formatting (added missing brackets [] for IPv6 hosts).
    • Ensured ZMQ_IPV6 option is set before binding/connecting, complying with ZMQ requirements.
    • Added try-except block for better resource cleanup on failure.
  • Removed the redundant get_zmq_socket_on_host function. Its functionality is now fully covered by get_zmq_socket (following DRY principle).

  1. python/sglang/srt/distributed/device_communicators/shm_broadcast.py:
  • Removed the usage of get_open_port().
  • Replaced the manual socket configuration block with a call to get_zmq_socket(..., host="127.0.0.1").
  1. python/sglang/srt/disaggregation/common/conn.py
  • Updated calls from get_zmq_socket_on_host to get_zmq_socket to align with the refactor.

Accuracy Tests

This is a refactoring of the networking/infrastructure layer (socket initialization). It does not affect the model kernel, forward pass, or logical output. Therefore, accuracy remains unchanged.

Benchmarking and Profiling

This change only affects the initialization phase of the router. It does not impact the inference critical path or runtime throughput. No performance regression is expected.

Checklist

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @Baidu-AIAK, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a potential race condition in the distributed communication layer by enhancing the port binding mechanism. It transitions from a non-atomic get_open_port approach to a more secure and atomic ZeroMQ-based method, ensuring reliable and specific interface binding for sockets. This change improves the robustness of the system's inter-process communication without impacting performance or accuracy.

Highlights

  • Race Condition Fix: Addressed a Time-of-Check-to-Time-of-Use (TOCTOU) race condition in shm_broadcast.py related to finding and binding to an available network port.
  • Unified Socket Creation: Refactored the port binding mechanism to utilize the existing get_zmq_socket helper function, leveraging ZeroMQ's atomic bind_to_random_port method for safer and more reliable operation.
  • Customizable Host Binding: Extended the get_zmq_socket function to accept an optional host parameter, allowing explicit binding to specific network interfaces (e.g., 127.0.0.1) rather than the default of all interfaces (0.0.0.0).

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request aims to fix a potential race condition by refactoring socket creation to use ZeroMQ's atomic bind_to_random_port method. The approach is sound, but the implementation has a critical bug in python/sglang/srt/utils/common.py where a missing f-string would cause bind_to_random_port to fail. Additionally, the handling of IPv6 addresses is incorrect, as the ZMQ_IPV6 socket option is set after the socket is bound. I've provided a suggestion in common.py to fix both issues by also moving the IPv6 logic into the get_zmq_socket helper function. This also allows for simplifying the code in shm_broadcast.py by removing the now-redundant IPv6 check.

Comment thread python/sglang/srt/utils/common.py Outdated
Comment on lines 1357 to 1359
config_socket(socket, socket_type)
port = socket.bind_to_random_port("tcp://*")
port = socket.bind_to_random_port("tcp://{host}")
return port, socket

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

This block contains a critical bug and a logic error. First, the call to bind_to_random_port is missing an f for the f-string, so it will try to bind to the literal address tcp://{host}. Second, the ZMQ_IPV6 socket option must be set before binding for IPv6 hosts. The calling code in shm_broadcast.py attempts this after binding, which is incorrect. Let's fix both issues here by moving the IPv6 check into this function and correcting the f-string.

Suggested change
config_socket(socket, socket_type)
port = socket.bind_to_random_port("tcp://*")
port = socket.bind_to_random_port("tcp://{host}")
return port, socket
config_socket(socket, socket_type)
if is_valid_ipv6_address(host):
socket.setsockopt(zmq.IPV6, 1)
port = socket.bind_to_random_port(f"tcp://{host}")
return port, socket

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I've fixed the missing f-string prefix. I also followed your suggestion to move the IPv6 configuration logic inside get_zmq_socket to ensure ZMQ_IPV6 is set before binding. The changes are pushed.

Comment on lines 234 to 235
if is_valid_ipv6_address(connect_ip):
self.remote_socket.setsockopt(IPV6, 1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This IPv6 check is performed after the socket is already bound within get_zmq_socket, which is too late for the ZMQ_IPV6 option to take effect. With the suggested change in common.py to handle IPv6 configuration inside get_zmq_socket, this block becomes both incorrect and redundant and can be removed.

@Baidu-AIAK
Baidu-AIAK force-pushed the community-0.5.6-zmqerror-fix branch 2 times, most recently from 3c1ea64 to 2cdfc80 Compare December 26, 2025 16:43
@Baidu-AIAK
Baidu-AIAK requested a review from imReese December 29, 2025 03:37
@Baidu-AIAK

Copy link
Copy Markdown
Contributor Author

@yizhang2077 @ShangmingCai

@Baidu-AIAK

Copy link
Copy Markdown
Contributor Author

Hi maintainers,
I have fixed the unused import error caught by the linter/pre-commit hooks.
Could you please help run /rerun-failed-ci to trigger the tests? Thanks!

@Baidu-AIAK

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@ShangmingCai

Copy link
Copy Markdown
Collaborator

/tag-and-rerun-ci

@github-actions github-actions Bot added the run-ci CI: run the baseline test suite on this PR label Dec 29, 2025
@Baidu-AIAK
Baidu-AIAK force-pushed the community-0.5.6-zmqerror-fix branch 2 times, most recently from 1994949 to 02183b6 Compare December 29, 2025 08:03
@Baidu-AIAK

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a potential race condition in shm_broadcast.py by refactoring the socket creation to use ZeroMQ's atomic bind_to_random_port method. The unification of get_zmq_socket and the removal of get_zmq_socket_on_host improve code clarity and maintainability. The changes also enhance IPv6 support and add better error handling. Overall, this is a solid improvement.

I've found one critical issue where the refactored get_zmq_socket function is being used incorrectly in python/sglang/srt/disaggregation/common/conn.py, which would lead to failures when using IPv6. I've provided a comment with details on how to resolve this.

Comment on lines +76 to 78
self.rank_port, self.server_socket = get_zmq_socket(
context, zmq.PULL, host=zmq_bind_host
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

While this change correctly replaces the deprecated function, it exposes a bug due to how the host parameter is prepared on the preceding line. The new get_zmq_socket function expects a raw IP address for the host parameter and handles IPv6 address wrapping internally. However, on line 75, maybe_wrap_ipv6_address is called, which passes a pre-wrapped address (e.g., [::1]) to get_zmq_socket. This will cause is_valid_ipv6_address inside get_zmq_socket to fail and prevent the ZMQ_IPV6 socket option from being set correctly, leading to errors when binding to an IPv6 address.

To fix this, you should remove the call to maybe_wrap_ipv6_address on line 75:

zmq_bind_host = self.local_ip

You may also want to adjust the logging on line 79 to ensure the address is displayed correctly for IPv6.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I didn't realize get_zmq_socket handles IPv6 wrapping internally. I've removed the call to maybe_wrap_ipv6_address on line 75 to pass the raw IP. I also updated the logging statement on line 79 as suggested.

@Baidu-AIAK
Baidu-AIAK force-pushed the community-0.5.6-zmqerror-fix branch 5 times, most recently from 2ebaa92 to dea9d77 Compare December 31, 2025 08:38
@Baidu-AIAK

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a potential race condition in shm_broadcast.py by replacing the non-atomic get_open_port() and socket.bind() sequence with a unified get_zmq_socket helper that leverages ZeroMQ's atomic bind_to_random_port. The refactoring of get_zmq_socket is well-executed, improving IPv6 handling and adding robust error handling with resource cleanup. The consolidation of get_zmq_socket_on_host into the main helper function is a good application of the DRY principle, simplifying the codebase. Overall, these changes significantly enhance the robustness and maintainability of the socket creation logic. I have a couple of minor suggestions to further improve the new utility function.

Comment on lines +209 to 215
local_subscribe_port, self.local_socket = get_zmq_socket(
context, XPUB, endpoint=None, host="127.0.0.1"
)
# set the verbose option so that we can receive every subscription
# message. otherwise, we will only receive the first subscription
# see http://api.zeromq.org/3-3:zmq-setsockopt for more details
self.local_socket.setsockopt(XPUB_VERBOSE, True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

I see that you're setting XPUB_VERBOSE on the socket after it's created by get_zmq_socket. This pattern is repeated for the remote socket as well. To improve encapsulation, you could consider extending get_zmq_socket to accept an optional dictionary of socket options. This would centralize socket configuration and make its usage cleaner. For example:

# In common.py
def get_zmq_socket(..., sockopts: Optional[Dict[int, Any]] = None):
    # ...
    if sockopts:
        for opt, val in sockopts.items():
            socket.setsockopt(opt, val)
    # ...

# In shm_broadcast.py
local_subscribe_port, self.local_socket = get_zmq_socket(
    context, XPUB, endpoint=None, host="127.0.0.1",
    sockopts={XPUB_VERBOSE: True}
)

This is a suggestion for a future improvement and not critical for this PR.

Comment thread python/sglang/srt/utils/common.py Outdated
@@ -1345,54 +1346,49 @@ def get_zmq_socket(
socket_type: Type of ZeroMQ socket to create.
endpoint: Optional endpoint to bind/connect to. If None, binds to a random TCP port.
bind: Whether to bind (True) or connect (False) to the endpoint. Ignored if endpoint is None.
host: Host to bind/connect to, without "tcp://" prefix.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The docstring for the host parameter is slightly misleading. It's only used for binding to a random port when endpoint is None, not for connecting. Clarifying this would improve readability and prevent potential misuse.

Suggested change
host: Host to bind/connect to, without "tcp://" prefix.
host: Host to bind to when `endpoint` is None. Defaults to "*" (all interfaces).

@Baidu-AIAK
Baidu-AIAK force-pushed the community-0.5.6-zmqerror-fix branch 2 times, most recently from cc2cd18 to b00cf9d Compare January 6, 2026 11:31
@ShangmingCai

Copy link
Copy Markdown
Collaborator

/rerun-and-failed-ci

@Baidu-AIAK
Baidu-AIAK force-pushed the community-0.5.6-zmqerror-fix branch 4 times, most recently from fc7cf25 to bb3edda Compare January 14, 2026 02:57
@Baidu-AIAK

Copy link
Copy Markdown
Contributor Author
image

@Baidu-AIAK
Baidu-AIAK force-pushed the community-0.5.6-zmqerror-fix branch 4 times, most recently from f00027c to b5695e6 Compare February 9, 2026 06:19
…ation

Change-Id: I6f7037e98b04f06e23e20a7b817c5e2e9621a885
@Baidu-AIAK
Baidu-AIAK force-pushed the community-0.5.6-zmqerror-fix branch from b5695e6 to 004f0c7 Compare February 9, 2026 06:19

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor/Bug] Fix potential race condition in shm_broadcast.py and unify socket creation

4 participants