mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
fix(traffic): show live Speed for AmneziaWG and MTProto inbounds/clients
The Speed column showed "--" for AmneziaWG (and MTProto, which has the identical gap) even while cumulative traffic totals were correct. XrayTrafficJob drives live speed by querying xray-core's own stats API and broadcasting the delta over websocket -- but AmneziaWG/MTProto never run inside xray-core's own runtime inbounds, so they're invisible to that API. Their own jobs already compute the same per-poll delta shape (that's what keeps cumulative totals correct) but never broadcast it. Reusing the existing "traffics"/"clientTraffics" broadcast would have two real bugs: the frontend's existing scope/replace logic would let each side clobber the other's speed on its next unrelated tick, and the websocket hub's per-message-type throttle is keyed only by message type, not caller -- since both sidecar jobs run on identical "@every 10s" grids registered milliseconds apart, one would silently lose almost every broadcast if both protocols were ever configured together. Fixed with a small unthrottled broadcast path (both sidecar jobs are already self-rate-limited by their own cron cadence) and protocol- namespaced wire keys, tracked in their own frontend state and merged into the existing inboundSpeed/clientSpeed only at read time -- so every existing consumer needs zero changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,77 @@ func TestHub_ShouldThrottle_DistinctTypesIndependent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHub_BroadcastUnthrottled_DeliversDespiteHotThrottleWindow(t *testing.T) {
|
||||
h := NewHub()
|
||||
defer h.Stop()
|
||||
go h.Run()
|
||||
|
||||
c := NewClient("c1")
|
||||
h.Register(c)
|
||||
waitClientCount(t, h, 1)
|
||||
|
||||
// Consume the throttle window for MessageTypeTraffic, as
|
||||
// TestHub_ShouldThrottle already does directly.
|
||||
if h.shouldThrottle(MessageTypeTraffic) {
|
||||
t.Fatal("first call should not throttle")
|
||||
}
|
||||
if !h.shouldThrottle(MessageTypeTraffic) {
|
||||
t.Fatal("second immediate call should throttle -- window not hot as expected")
|
||||
}
|
||||
|
||||
h.BroadcastUnthrottled(MessageTypeTraffic, map[string]string{"k": "v"})
|
||||
|
||||
select {
|
||||
case raw := <-c.Send:
|
||||
var m Message
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatalf("payload is not valid JSON: %v\n%s", err, raw)
|
||||
}
|
||||
if m.Type != MessageTypeTraffic {
|
||||
t.Fatalf("Type = %q, want %q", m.Type, MessageTypeTraffic)
|
||||
}
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
t.Fatal("BroadcastUnthrottled should deliver even with a hot throttle window")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHub_BroadcastUnthrottled_DropsWhenNoClients(t *testing.T) {
|
||||
h := NewHub()
|
||||
defer h.Stop()
|
||||
go h.Run()
|
||||
|
||||
h.BroadcastUnthrottled(MessageTypeTraffic, "payload")
|
||||
|
||||
select {
|
||||
case <-h.broadcast:
|
||||
t.Fatal("BroadcastUnthrottled should drop when client count is zero")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestHub_BroadcastUnthrottled_DropsNilPayload(t *testing.T) {
|
||||
h := NewHub()
|
||||
defer h.Stop()
|
||||
go h.Run()
|
||||
|
||||
c := NewClient("c1")
|
||||
h.Register(c)
|
||||
waitClientCount(t, h, 1)
|
||||
|
||||
h.BroadcastUnthrottled(MessageTypeTraffic, nil)
|
||||
|
||||
select {
|
||||
case <-c.Send:
|
||||
t.Fatal("nil payload should be dropped, not delivered")
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestHub_BroadcastUnthrottled_NilReceiverDoesNotPanic(t *testing.T) {
|
||||
var h *Hub
|
||||
h.BroadcastUnthrottled(MessageTypeTraffic, "anything")
|
||||
}
|
||||
|
||||
func TestTrySend_SucceedsWithRoom(t *testing.T) {
|
||||
c := &Client{ID: "c", Send: make(chan []byte, 1)}
|
||||
if !trySend(c, []byte("hi")) {
|
||||
|
||||
Reference in New Issue
Block a user