docs(handler/tunnel): add architecture documentation and fix observeStats event loss
- Add package-level architecture doc in handler.go covering: - NAT traversal reverse proxy architecture - CmdBind/CmdConnect roles and data flow - Connector lifecycle and waitClose semantics - Entrypoint protocol dispatch (first-byte sniffing) - SD fallback behavior - Add data-flow-oriented doc comments to all business files: - entrypoint.go: protocol dispatch, dial flow - connector.go: Connector/ConnectorPool semantics - dialer.go: two-phase dial strategy - tunnel.go: MaxWeight semantics, selection algorithm - bind.go: 6-step CmdBind flow - connect.go: CmdConnect flow with relay framing - ephttp.go, eptls.go, eprelay.go: per-protocol entrypoint flow - Fix observeStats: after successful error retry, also flush new events instead of skipping the current tick (handler.go:261-271) - Add test for observeStats retry-then-flush (handler_test.go)
This commit is contained in:
@@ -3,6 +3,7 @@ package tunnel
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
@@ -269,18 +270,58 @@ func TestHandler_observeStats(t *testing.T) {
|
||||
h.observeStats(ctx)
|
||||
// Should return without blocking
|
||||
})
|
||||
|
||||
t.Run("retry after observer error flushes new events", func(t *testing.T) {
|
||||
observeC := make(chan []observer.Event, 4)
|
||||
callCount := 0
|
||||
h := &tunnelHandler{
|
||||
md: metadata{
|
||||
observerPeriod: 50 * time.Millisecond,
|
||||
observerResetTraffic: false,
|
||||
},
|
||||
stats: stats_util.NewHandlerStats("test", false),
|
||||
}
|
||||
h.options.Observer = &fakeObserver{
|
||||
observeC: observeC,
|
||||
errFunc: func() bool {
|
||||
callCount++
|
||||
// Fail on first call, succeed on subsequent calls
|
||||
return callCount == 1
|
||||
},
|
||||
}
|
||||
|
||||
// Trigger stats events
|
||||
s := h.stats.Stats("client1")
|
||||
s.Add(stats.KindTotalConns, 1)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go h.observeStats(ctx)
|
||||
|
||||
// First tick: Observe fails, events are stored as pending.
|
||||
// Second tick: Observe succeeds on pending events, then also flushes new events.
|
||||
<-observeC // first call (fails)
|
||||
<-observeC // second call (pending retry succeeds)
|
||||
<-observeC // third call (new events flushed in same tick)
|
||||
|
||||
cancel()
|
||||
})
|
||||
}
|
||||
|
||||
// fakeObserver implements the observer.Observer interface for testing.
|
||||
type fakeObserver struct {
|
||||
observeC chan []observer.Event
|
||||
err error
|
||||
errFunc func() bool
|
||||
}
|
||||
|
||||
func (o *fakeObserver) Observe(ctx context.Context, events []observer.Event, opts ...observer.Option) error {
|
||||
if o.observeC != nil {
|
||||
o.observeC <- events
|
||||
}
|
||||
if o.errFunc != nil && o.errFunc() {
|
||||
return errors.New("simulated observer error")
|
||||
}
|
||||
return o.err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user