298 lines
9.0 KiB
Go
298 lines
9.0 KiB
Go
|
|
package notify
|
||
|
|
|
||
|
|
import (
|
||
|
|
"b612.me/stario"
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"math"
|
||
|
|
"net"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestServerStopMonitorChanUsesRuntimeStopCtx(t *testing.T) {
|
||
|
|
server := NewServer().(*ServerCommon)
|
||
|
|
runtimeCtx, runtimeCancel := context.WithCancel(context.Background())
|
||
|
|
defer runtimeCancel()
|
||
|
|
queue := stario.NewQueueCtx(runtimeCtx, 4, math.MaxUint32)
|
||
|
|
server.setServerSessionRuntime(&serverSessionRuntime{
|
||
|
|
stopCtx: runtimeCtx,
|
||
|
|
stopFn: runtimeCancel,
|
||
|
|
queue: queue,
|
||
|
|
})
|
||
|
|
|
||
|
|
ch := server.StopMonitorChan()
|
||
|
|
if ch == nil {
|
||
|
|
t.Fatal("StopMonitorChan should return runtime stop channel")
|
||
|
|
}
|
||
|
|
|
||
|
|
runtimeCancel()
|
||
|
|
select {
|
||
|
|
case <-ch:
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("StopMonitorChan should close after runtime context cancel")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestServerSendEnvelopeUsesRuntimeUDPListener(t *testing.T) {
|
||
|
|
server := NewServer().(*ServerCommon)
|
||
|
|
UseLegacySecurityServer(server)
|
||
|
|
|
||
|
|
receiverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ResolveUDPAddr receiver failed: %v", err)
|
||
|
|
}
|
||
|
|
receiver, err := net.ListenUDP("udp", receiverAddr)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ListenUDP receiver failed: %v", err)
|
||
|
|
}
|
||
|
|
defer receiver.Close()
|
||
|
|
|
||
|
|
senderAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ResolveUDPAddr sender failed: %v", err)
|
||
|
|
}
|
||
|
|
sender, err := net.ListenUDP("udp", senderAddr)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("ListenUDP sender failed: %v", err)
|
||
|
|
}
|
||
|
|
defer sender.Close()
|
||
|
|
|
||
|
|
runtimeCtx, runtimeCancel := context.WithCancel(context.Background())
|
||
|
|
defer runtimeCancel()
|
||
|
|
queue := stario.NewQueueCtx(runtimeCtx, 4, math.MaxUint32)
|
||
|
|
server.setServerSessionRuntime(&serverSessionRuntime{
|
||
|
|
stopCtx: runtimeCtx,
|
||
|
|
stopFn: runtimeCancel,
|
||
|
|
queue: queue,
|
||
|
|
udpListener: sender,
|
||
|
|
})
|
||
|
|
server.markSessionStarted()
|
||
|
|
defer server.markSessionStopped("test done", nil)
|
||
|
|
// Ensure send path depends on runtime snapshot, not stale owner field reads.
|
||
|
|
server.udpListener = nil
|
||
|
|
|
||
|
|
client := newServerCodecClientConnForTest(server)
|
||
|
|
client.ClientAddr = receiver.LocalAddr()
|
||
|
|
client.setClientConnMaxWriteTimeout(100 * time.Millisecond)
|
||
|
|
if err := server.sendEnvelope(client, newSignalAckEnvelope(1001)); err != nil {
|
||
|
|
t.Fatalf("sendEnvelope failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
_ = receiver.SetReadDeadline(time.Now().Add(time.Second))
|
||
|
|
buf := make([]byte, 4096)
|
||
|
|
n, _, err := receiver.ReadFromUDP(buf)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("receiver ReadFromUDP failed: %v", err)
|
||
|
|
}
|
||
|
|
if n == 0 {
|
||
|
|
t.Fatal("receiver should get runtime udp payload")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestServerSendEnvelopeUsesClientConnRuntimeTransport(t *testing.T) {
|
||
|
|
server := NewServer().(*ServerCommon)
|
||
|
|
UseLegacySecurityServer(server)
|
||
|
|
|
||
|
|
runtimeLeft, runtimeRight := net.Pipe()
|
||
|
|
defer runtimeLeft.Close()
|
||
|
|
defer runtimeRight.Close()
|
||
|
|
|
||
|
|
runtimeCtx, runtimeCancel := context.WithCancel(context.Background())
|
||
|
|
defer runtimeCancel()
|
||
|
|
queue := stario.NewQueueCtx(runtimeCtx, 4, math.MaxUint32)
|
||
|
|
server.setServerSessionRuntime(&serverSessionRuntime{
|
||
|
|
stopCtx: runtimeCtx,
|
||
|
|
stopFn: runtimeCancel,
|
||
|
|
queue: queue,
|
||
|
|
})
|
||
|
|
server.markSessionStarted()
|
||
|
|
defer server.markSessionStopped("test done", nil)
|
||
|
|
|
||
|
|
client, _, _ := newStartedClientConnForTest(t, "", server, runtimeLeft, runtimeCtx, runtimeCancel)
|
||
|
|
client.setClientConnMaxWriteTimeout(100 * time.Millisecond)
|
||
|
|
client.applyClientConnAttachmentProfile(0, 100*time.Millisecond, server.defaultMsgEn, server.defaultMsgDe, server.handshakeRsaKey, server.SecretKey)
|
||
|
|
|
||
|
|
recvCh := make(chan []byte, 1)
|
||
|
|
errCh := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
_ = runtimeRight.SetReadDeadline(time.Now().Add(time.Second))
|
||
|
|
reader := stario.NewFrameReader(runtimeRight, nil)
|
||
|
|
payload, err := reader.Next()
|
||
|
|
if err != nil {
|
||
|
|
errCh <- err
|
||
|
|
return
|
||
|
|
}
|
||
|
|
recvCh <- payload
|
||
|
|
}()
|
||
|
|
|
||
|
|
if err := server.sendEnvelope(client, newSignalAckEnvelope(1001)); err != nil {
|
||
|
|
t.Fatalf("sendEnvelope failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
select {
|
||
|
|
case err := <-errCh:
|
||
|
|
t.Fatalf("runtime conn read failed: %v", err)
|
||
|
|
case got := <-recvCh:
|
||
|
|
if len(got) == 0 {
|
||
|
|
t.Fatal("runtime conn should receive framed payload")
|
||
|
|
}
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("runtime conn did not receive payload")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestServerListenFailureCleansRuntimeAndAllowsRetry(t *testing.T) {
|
||
|
|
server := NewServer().(*ServerCommon)
|
||
|
|
UseLegacySecurityServer(server)
|
||
|
|
|
||
|
|
err := server.Listen("tcp", "127.0.0.1:bad")
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("Listen should fail for invalid address")
|
||
|
|
}
|
||
|
|
if status := server.Status(); status.Alive {
|
||
|
|
t.Fatalf("server should remain stopped after failed Listen: %+v", status)
|
||
|
|
}
|
||
|
|
rt := server.serverSessionRuntimeSnapshot()
|
||
|
|
if rt == nil {
|
||
|
|
t.Fatal("runtime snapshot should still expose stop context after cleanup")
|
||
|
|
}
|
||
|
|
if rt.queue != nil || rt.listener != nil || rt.udpListener != nil {
|
||
|
|
t.Fatalf("runtime transport artifacts should be cleaned after failed Listen: %+v", rt)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := server.Listen("tcp", "127.0.0.1:0"); err != nil {
|
||
|
|
t.Fatalf("Listen should succeed after failed attempt cleanup: %v", err)
|
||
|
|
}
|
||
|
|
if status := server.Status(); !status.Alive {
|
||
|
|
t.Fatalf("server should be alive after successful retry Listen: %+v", status)
|
||
|
|
}
|
||
|
|
if err := server.Stop(); err != nil {
|
||
|
|
t.Fatalf("Stop failed: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestServerMarkSessionStoppedUsesRuntimeStopFn(t *testing.T) {
|
||
|
|
server := NewServer().(*ServerCommon)
|
||
|
|
if !server.beginServerSessionStart() {
|
||
|
|
t.Fatal("beginServerSessionStart should succeed")
|
||
|
|
}
|
||
|
|
server.markSessionStarted()
|
||
|
|
|
||
|
|
runtimeCtx, runtimeCancel := context.WithCancel(context.Background())
|
||
|
|
defer runtimeCancel()
|
||
|
|
server.setServerSessionRuntime(&serverSessionRuntime{
|
||
|
|
stopCtx: runtimeCtx,
|
||
|
|
stopFn: runtimeCancel,
|
||
|
|
})
|
||
|
|
|
||
|
|
fallbackCtx, fallbackCancel := context.WithCancel(context.Background())
|
||
|
|
defer fallbackCancel()
|
||
|
|
server.stopCtx = fallbackCtx
|
||
|
|
server.stopFn = fallbackCancel
|
||
|
|
|
||
|
|
server.markSessionStopped("runtime stop", nil)
|
||
|
|
|
||
|
|
select {
|
||
|
|
case <-runtimeCtx.Done():
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("runtime stop context should be canceled by markSessionStopped")
|
||
|
|
}
|
||
|
|
select {
|
||
|
|
case <-fallbackCtx.Done():
|
||
|
|
t.Fatal("fallback owner stop context should not be canceled when runtime stopFn is active")
|
||
|
|
default:
|
||
|
|
}
|
||
|
|
rt := server.serverSessionRuntimeSnapshot()
|
||
|
|
if rt == nil {
|
||
|
|
t.Fatal("runtime snapshot should remain available after stop")
|
||
|
|
}
|
||
|
|
if rt.listener != nil || rt.udpListener != nil || rt.queue != nil {
|
||
|
|
t.Fatalf("runtime transport should be cleared after stop: %+v", rt)
|
||
|
|
}
|
||
|
|
if rt.stopCtx == nil {
|
||
|
|
t.Fatalf("runtime stop context should be preserved after stop: %+v", rt)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestServerClearSessionRuntimeTransportKeepsLogicalStopContext(t *testing.T) {
|
||
|
|
server := NewServer().(*ServerCommon)
|
||
|
|
stopCtx, stopFn := context.WithCancel(context.Background())
|
||
|
|
defer stopFn()
|
||
|
|
queue := stario.NewQueueCtx(stopCtx, 4, math.MaxUint32)
|
||
|
|
server.setServerSessionRuntime(&serverSessionRuntime{
|
||
|
|
stopCtx: stopCtx,
|
||
|
|
stopFn: stopFn,
|
||
|
|
queue: queue,
|
||
|
|
listener: &stubListener{},
|
||
|
|
})
|
||
|
|
|
||
|
|
transportStopCtx := server.serverTransportStopContextSnapshot()
|
||
|
|
server.clearServerSessionRuntimeTransport()
|
||
|
|
|
||
|
|
rt := server.serverSessionRuntimeSnapshot()
|
||
|
|
if rt == nil {
|
||
|
|
t.Fatal("runtime snapshot should remain after transport clear")
|
||
|
|
}
|
||
|
|
if rt.listener != nil || rt.udpListener != nil {
|
||
|
|
t.Fatalf("runtime transport should be cleared: %+v", rt)
|
||
|
|
}
|
||
|
|
if rt.queue != queue {
|
||
|
|
t.Fatalf("runtime queue should be preserved across pure transport clear: got %v want %v", rt.queue, queue)
|
||
|
|
}
|
||
|
|
select {
|
||
|
|
case <-transportStopCtx.Done():
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("transport stop context should be canceled after clearing transport")
|
||
|
|
}
|
||
|
|
select {
|
||
|
|
case <-server.serverStopContextSnapshot().Done():
|
||
|
|
t.Fatal("logical stop context should remain active after clearing transport")
|
||
|
|
default:
|
||
|
|
}
|
||
|
|
if server.serverTransportAttachedSnapshot() {
|
||
|
|
t.Fatal("server transport should be marked detached after clearing transport")
|
||
|
|
}
|
||
|
|
if got := server.serverQueueSnapshot(); got != queue {
|
||
|
|
t.Fatalf("server queue snapshot should be preserved after transport clear: got %v want %v", got, queue)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestServerClearSessionRuntimeTransportPreservesQueueForEncoding(t *testing.T) {
|
||
|
|
server := NewServer().(*ServerCommon)
|
||
|
|
UseLegacySecurityServer(server)
|
||
|
|
|
||
|
|
stopCtx, stopFn := context.WithCancel(context.Background())
|
||
|
|
defer stopFn()
|
||
|
|
queue := stario.NewQueueCtx(stopCtx, 4, math.MaxUint32)
|
||
|
|
server.setServerSessionRuntime(&serverSessionRuntime{
|
||
|
|
stopCtx: stopCtx,
|
||
|
|
stopFn: stopFn,
|
||
|
|
queue: queue,
|
||
|
|
listener: &stubListener{},
|
||
|
|
})
|
||
|
|
server.markSessionStarted()
|
||
|
|
defer server.markSessionStopped("test done", nil)
|
||
|
|
|
||
|
|
server.clearServerSessionRuntimeTransport()
|
||
|
|
|
||
|
|
data, err := server.encodeEnvelope(newServerCodecClientConnForTest(server), newSignalAckEnvelope(1002))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("encodeEnvelope failed after pure transport clear: %v", err)
|
||
|
|
}
|
||
|
|
if len(data) == 0 {
|
||
|
|
t.Fatal("encodeEnvelope should still return framed payload after pure transport clear")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type stubListener struct{}
|
||
|
|
|
||
|
|
func (stubListener) Accept() (net.Conn, error) { return nil, errors.New("stub") }
|
||
|
|
func (stubListener) Close() error { return nil }
|
||
|
|
func (stubListener) Addr() net.Addr { return stubAddr("stub") }
|
||
|
|
|
||
|
|
type stubAddr string
|
||
|
|
|
||
|
|
func (a stubAddr) Network() string { return string(a) }
|
||
|
|
func (a stubAddr) String() string { return string(a) }
|