bastion 0.53.2
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// TestKeepaliveUnblocksParkedReaderOnDeadPeer is the regression guard for the worker-lane
|
||||
// hang: a parked ReadMessage must return (not block forever) once the peer stops answering
|
||||
// pings. It stands up a WS server that upgrades, reads the BEARER, then goes SILENT — never
|
||||
// reads again, so gorilla's server auto-pong never fires (no pong) and it sends no pings.
|
||||
// The client arms keepalive with short timings and parks on ReadMessage; the read deadline
|
||||
// must fire and unblock it.
|
||||
func TestKeepaliveUnblocksParkedReaderOnDeadPeer(t *testing.T) {
|
||||
up := websocket.Upgrader{}
|
||||
hold := make(chan struct{})
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := up.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Read exactly the BEARER hello, then stop reading entirely: no auto-pong, no
|
||||
// ping, no close frame — the "silently dead tunnel" the fix targets.
|
||||
if _, _, err := c.ReadMessage(); err != nil {
|
||||
return
|
||||
}
|
||||
<-hold // keep the socket open but utterly unresponsive
|
||||
_ = c.Close()
|
||||
}))
|
||||
defer srv.Close()
|
||||
defer close(hold)
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
|
||||
ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial: %v", err)
|
||||
}
|
||||
defer ws.Close()
|
||||
if err := ws.WriteMessage(websocket.TextMessage, []byte("BEARER test")); err != nil {
|
||||
t.Fatalf("write bearer: %v", err)
|
||||
}
|
||||
|
||||
// 40ms ping, 120ms read timeout: a healthy pong would keep resetting the deadline,
|
||||
// but this server never pongs, so the parked read must error within ~120ms.
|
||||
stop := armWSKeepaliveEvery(ws, 40*time.Millisecond, 120*time.Millisecond)
|
||||
defer stop()
|
||||
|
||||
readErr := make(chan error, 1)
|
||||
go func() { _, _, e := ws.ReadMessage(); readErr <- e }()
|
||||
|
||||
select {
|
||||
case e := <-readErr:
|
||||
if e == nil {
|
||||
t.Fatal("parked read returned nil error; expected a timeout/close on the dead peer")
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("parked ReadMessage did NOT unblock — the worker lane would hang forever (regression)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestKeepaliveHealthyStaysUp is the other half: on a live peer that answers pings with
|
||||
// pongs, the parked read must NOT be killed by the deadline. A gorilla server that keeps
|
||||
// ReadMessage-ing auto-pongs the client's pings, so the deadline keeps resetting.
|
||||
func TestKeepaliveHealthyStaysUp(t *testing.T) {
|
||||
up := websocket.Upgrader{}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer wg.Done()
|
||||
c, err := up.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
// Keep reading so gorilla auto-pongs the client's pings (the healthy case). Runs
|
||||
// until the client hangs up.
|
||||
for {
|
||||
if _, _, err := c.ReadMessage(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
|
||||
ws, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("dial: %v", err)
|
||||
}
|
||||
if err := ws.WriteMessage(websocket.TextMessage, []byte("BEARER test")); err != nil {
|
||||
t.Fatalf("write bearer: %v", err)
|
||||
}
|
||||
|
||||
stop := armWSKeepaliveEvery(ws, 40*time.Millisecond, 120*time.Millisecond)
|
||||
|
||||
readErr := make(chan error, 1)
|
||||
go func() { _, _, e := ws.ReadMessage(); readErr <- e }()
|
||||
|
||||
// Over 500ms (~12 ping intervals, > 4 read timeouts) the pongs must keep the read
|
||||
// alive. A premature error means we'd be tearing down healthy lanes.
|
||||
select {
|
||||
case e := <-readErr:
|
||||
t.Fatalf("healthy parked read errored early (%v) — keepalive is killing live lanes", e)
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
// still parked & alive — correct
|
||||
}
|
||||
|
||||
stop()
|
||||
_ = ws.Close()
|
||||
wg.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user