Align native Xray with xray-core; drop dead knobs; split admin app.js

Fix the two reliability problems in the in-process Xray emulator by matching
XTLS/Xray-core's transport semantics:

- XHTTP upload queue: rewrite as a faithful port of xray-core's uploadQueue
  (bounded channel + sequence reorder heap). Packet-up POSTs are now acked
  immediately on buffering instead of blocking until the tunnel reader consumes
  them. The old block-until-consumed behavior throttled the uplink to the
  reassembly rate and deadlocked against the client's concurrent-POST limit,
  which showed up as "download a burst, stall, repeat" on video/large downloads.
- Mux: dial the backend and pump uplink on a per-session goroutine fed by a
  bounded channel (mirrors xray-core's per-session buffered pipe). Previously the
  dial and backend writes ran inline in the shared read loop, so one slow target
  or backpressured session stalled every other muxed session.
- XHTTP download writer: flush every write (matches httpServerConn.Write) instead
  of batching behind a 2ms/32KB window.
- XHTTP: enforce a single download (stream-down) per session to stop two GETs
  from splitting the decoded stream and corrupting the tunnel.
- Fix a close-of-closed-channel race in the mux session teardown (sync.Once).

Remove the now-inert XHTTP tuning knobs (xhttp_queue_timeout_ms, xhttp_flush_ms,
xhttp_flush_bytes) from the backend struct and the admin panel UI.

Split admin/assets/app.js into ordered classic-script modules under
admin/assets/js/ for maintainability. The concatenation is byte-identical to the
old file and load order is preserved via defer, so behavior is unchanged.

Add regression tests for the mux head-of-line stall and the out-of-order
packet-up burst-stall; add golang.org/x/text to go.mod so tests build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 23:27:11 -03:00
co-authored by Claude Opus 4.8
parent aa676eb081
commit 4b9f6c123a
21 changed files with 3859 additions and 5409 deletions
+21 -30
View File
@@ -1,6 +1,7 @@
package main
import (
"runtime"
"sync/atomic"
"time"
)
@@ -9,6 +10,9 @@ import (
// from the admin panel and saved in config.json under xray.native_tuning.
// Values are process/runtime settings, not generated Xray JSON settings.
type XrayNativeTuning struct {
// RuntimeGOMAXPROCS controls Go CPU parallelism for the in-process native Xray.
// 0 or negative means use all detected CPU cores.
RuntimeGOMAXPROCS int `json:"runtime_gomaxprocs,omitempty"`
MuxMaxSessions int `json:"mux_max_sessions,omitempty"`
MuxGlobalSessions int `json:"mux_global_sessions,omitempty"`
MuxUDPIdleMS int `json:"mux_udp_idle_ms,omitempty"`
@@ -16,9 +20,6 @@ type XrayNativeTuning struct {
MuxUDPWriteBuffer int `json:"mux_udp_write_buffer,omitempty"`
XHTTPMaxSessions int `json:"xhttp_max_sessions,omitempty"`
XHTTPBufferedPosts int `json:"xhttp_buffered_posts,omitempty"`
XHTTPQueueTimeoutMS int `json:"xhttp_queue_timeout_ms,omitempty"`
XHTTPFlushMS int `json:"xhttp_flush_ms,omitempty"`
XHTTPFlushBytes int `json:"xhttp_flush_bytes,omitempty"`
H2MaxConcurrentStreams int `json:"h2_max_concurrent_streams,omitempty"`
H2UploadBufferConn int `json:"h2_upload_buffer_conn,omitempty"`
H2UploadBufferStream int `json:"h2_upload_buffer_stream,omitempty"`
@@ -26,22 +27,21 @@ type XrayNativeTuning struct {
}
const (
defaultNativeRuntimeGOMAXPROCS = 0
defaultNativeMuxMaxSessions = 128
defaultNativeMuxGlobalSessions = 32768
defaultNativeMuxUDPIdleMS = 15000
defaultNativeMuxUDPIdleMS = 120000
defaultNativeMuxUDPReadBuffer = 256 * 1024
defaultNativeMuxUDPWriteBuffer = 256 * 1024
defaultNativeXHTTPMaxSessions = 16384
defaultNativeXHTTPBufferedPosts = 64
defaultNativeXHTTPQueueTimeoutMS = 250
defaultNativeXHTTPFlushMS = 5
defaultNativeXHTTPFlushBytes = 128 * 1024
defaultNativeXHTTPBufferedPosts = 256
defaultNativeH2MaxConcurrentStreams = 1024
defaultNativeH2UploadBufferConn = 1 * 1024 * 1024
defaultNativeH2UploadBufferStream = 256 * 1024
)
var (
nativeTuneRuntimeGOMAXPROCS atomic.Int64
nativeTuneMuxMaxSessions atomic.Int64
nativeTuneMuxGlobalSessions atomic.Int64
nativeTuneMuxUDPIdleMS atomic.Int64
@@ -49,9 +49,6 @@ var (
nativeTuneMuxUDPWriteBuffer atomic.Int64
nativeTuneXHTTPMaxSessions atomic.Int64
nativeTuneXHTTPBufferedPosts atomic.Int64
nativeTuneXHTTPQueueTimeoutMS atomic.Int64
nativeTuneXHTTPFlushMS atomic.Int64
nativeTuneXHTTPFlushBytes atomic.Int64
nativeTuneH2MaxConcurrentStreams atomic.Int64
nativeTuneH2UploadBufferConn atomic.Int64
nativeTuneH2UploadBufferStream atomic.Int64
@@ -67,6 +64,9 @@ func normalizeNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
t = &XrayNativeTuning{}
}
out := *t
if out.RuntimeGOMAXPROCS < 0 {
out.RuntimeGOMAXPROCS = defaultNativeRuntimeGOMAXPROCS
}
if out.MuxMaxSessions <= 0 {
out.MuxMaxSessions = defaultNativeMuxMaxSessions
}
@@ -88,15 +88,6 @@ func normalizeNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
if out.XHTTPBufferedPosts <= 0 {
out.XHTTPBufferedPosts = defaultNativeXHTTPBufferedPosts
}
if out.XHTTPQueueTimeoutMS <= 0 {
out.XHTTPQueueTimeoutMS = defaultNativeXHTTPQueueTimeoutMS
}
if out.XHTTPFlushMS <= 0 {
out.XHTTPFlushMS = defaultNativeXHTTPFlushMS
}
if out.XHTTPFlushBytes <= 0 {
out.XHTTPFlushBytes = defaultNativeXHTTPFlushBytes
}
if out.H2MaxConcurrentStreams <= 0 {
out.H2MaxConcurrentStreams = defaultNativeH2MaxConcurrentStreams
}
@@ -111,6 +102,15 @@ func normalizeNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
func applyNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
out := normalizeNativeXrayTuning(t)
gomax := out.RuntimeGOMAXPROCS
if gomax <= 0 {
gomax = runtime.NumCPU()
}
if gomax < 1 {
gomax = 1
}
runtime.GOMAXPROCS(gomax)
nativeTuneRuntimeGOMAXPROCS.Store(int64(gomax))
nativeTuneMuxMaxSessions.Store(int64(out.MuxMaxSessions))
nativeTuneMuxGlobalSessions.Store(int64(out.MuxGlobalSessions))
nativeTuneMuxUDPIdleMS.Store(int64(out.MuxUDPIdleMS))
@@ -118,9 +118,6 @@ func applyNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
nativeTuneMuxUDPWriteBuffer.Store(int64(out.MuxUDPWriteBuffer))
nativeTuneXHTTPMaxSessions.Store(int64(out.XHTTPMaxSessions))
nativeTuneXHTTPBufferedPosts.Store(int64(out.XHTTPBufferedPosts))
nativeTuneXHTTPQueueTimeoutMS.Store(int64(out.XHTTPQueueTimeoutMS))
nativeTuneXHTTPFlushMS.Store(int64(out.XHTTPFlushMS))
nativeTuneXHTTPFlushBytes.Store(int64(out.XHTTPFlushBytes))
nativeTuneH2MaxConcurrentStreams.Store(int64(out.H2MaxConcurrentStreams))
nativeTuneH2UploadBufferConn.Store(int64(out.H2UploadBufferConn))
nativeTuneH2UploadBufferStream.Store(int64(out.H2UploadBufferStream))
@@ -128,6 +125,7 @@ func applyNativeXrayTuning(t *XrayNativeTuning) XrayNativeTuning {
return out
}
func nativeRuntimeGOMAXPROCS() int { return int(nativeTuneRuntimeGOMAXPROCS.Load()) }
func nativeMuxMaxSessionLimit() int { return int(nativeTuneMuxMaxSessions.Load()) }
func nativeMuxGlobalSessionLimit() int { return int(nativeTuneMuxGlobalSessions.Load()) }
func nativeMuxUDPIdleTimeout() time.Duration {
@@ -137,13 +135,6 @@ func nativeMuxUDPReadBufferSize() int { return int(nativeTuneMuxUDPReadBuffer.
func nativeMuxUDPWriteBufferSize() int { return int(nativeTuneMuxUDPWriteBuffer.Load()) }
func nativeXHTTPMaxSessionLimit() int { return int(nativeTuneXHTTPMaxSessions.Load()) }
func nativeXHTTPBufferedPostLimit() int { return int(nativeTuneXHTTPBufferedPosts.Load()) }
func nativeXHTTPQueuePushTimeoutDuration() time.Duration {
return time.Duration(nativeTuneXHTTPQueueTimeoutMS.Load()) * time.Millisecond
}
func nativeXHTTPFlushIntervalDuration() time.Duration {
return time.Duration(nativeTuneXHTTPFlushMS.Load()) * time.Millisecond
}
func nativeXHTTPFlushByteLimit() int { return int(nativeTuneXHTTPFlushBytes.Load()) }
func nativeH2MaxConcurrentStreams() int { return int(nativeTuneH2MaxConcurrentStreams.Load()) }
func nativeH2UploadBufferConn() int { return int(nativeTuneH2UploadBufferConn.Load()) }
func nativeH2UploadBufferStream() int { return int(nativeTuneH2UploadBufferStream.Load()) }