You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A user sync stuck on an unresponsive Xray core blocks the health-check restart #128
Found by code review, then reproduced in a lab (macOS arm64, a real node built from dev 9ccd263, real Xray 26.3.27, node_bridge_py main c5632b65a10a). It was never seen in production.
SyncUser and UpdateUsers hold syncMu for the whole sync, including the AlterInbound calls to the core (backend/xray/user.go:237 and :367 at 9ccd263). Those calls use the request context and add no deadline of their own. Since #113, the health-check restart takes syncMu first (backend/xray/jobs.go:100). Restart() does the same (backend/xray/xray.go:155), but it has no production caller today; only tests call it.
When the core process stops answering (in the lab: SIGSTOP of the xray process), a sync in flight blocks on AlterInbound and keeps syncMu. After 10 failed probes, about 50 s, the health loop decides to restart and then waits for syncMu. The node stays in this state until the stuck sync ends, which happens when:
the caller's context ends. With node_bridge_py c5632b65 at its default internal_timeout of 15 s, the bridge gave up on the chunked stream after 15 s, the node logged context canceled for each user and answered Internal, and the health loop then took syncMu, failed its 3 s re-probe and replaced the core. A caller with a longer deadline, or none, should keep the core wedged for that long (read from the code, not reproduced).
the backend is stopped. In the lab, the node's keep-alive timeout stopped the backend and released a stuck chunked sync with Internal ... the client connection is closing.
the core answers again. In the lab, SIGCONT 5 s into a stuck sync let it finish with success, and no restart followed.
Reproduction: the tests in #133 (unix only). On dev 9ccd263, with a fake core whose AlterInbound blocks until its context ends, Restart() had not started a new core after 10 s, and the health loop had not replaced the core after 60 s (the test bounds).
Expected: a restart can always proceed, also when more syncs are queued on syncMu behind the stuck one. An interrupted sync returns an error the caller can retry, never success, and the new core is built from the current config, including the accounts the interrupted syncs were applying.
Checked against node dev 9ccd263 (the code is the same on v0.5.7 739eab9 for these lines).
changed the title [-]A user sync stuck on an unresponsive core blocks Restart() and the health-check restart[/-][+]A user sync stuck on an unresponsive Xray core blocks the health-check restart[/+]on Nov 7, 2026
Activity
[-]A user sync stuck on an unresponsive core blocks Restart() and the health-check restart[/-][+]A user sync stuck on an unresponsive Xray core blocks the health-check restart[/+]