notify/server_session_runtime_test.go

298 lines
9.0 KiB
Go
Raw Normal View History

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) }