198 lines
5.7 KiB
Go
198 lines
5.7 KiB
Go
|
|
package notify
|
||
|
|
|
||
|
|
import (
|
||
|
|
"b612.me/stario"
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
netpkg "net"
|
||
|
|
"sync/atomic"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type gatedDedicatedReadConn struct {
|
||
|
|
netpkg.Conn
|
||
|
|
total int
|
||
|
|
read int
|
||
|
|
reached chan struct{}
|
||
|
|
release chan struct{}
|
||
|
|
closed bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *gatedDedicatedReadConn) Read(p []byte) (int, error) {
|
||
|
|
n, err := c.Conn.Read(p)
|
||
|
|
c.read += n
|
||
|
|
if !c.closed && c.read >= c.total {
|
||
|
|
c.closed = true
|
||
|
|
close(c.reached)
|
||
|
|
<-c.release
|
||
|
|
}
|
||
|
|
return n, err
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestControlMessageErrorPreservesTransportDetachedSentinel(t *testing.T) {
|
||
|
|
for name, decode := range map[string]func(string) error{
|
||
|
|
"bulk": bulkControlMessageError,
|
||
|
|
"stream": streamControlMessageError,
|
||
|
|
} {
|
||
|
|
t.Run(name, func(t *testing.T) {
|
||
|
|
err := decode("transport detached: stale transport generation=7")
|
||
|
|
if !errors.Is(err, errTransportDetached) {
|
||
|
|
t.Fatalf("decoded error = %v, want transport detached sentinel", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSendDedicatedBulkAttachRequestRejectsNilDialResult(t *testing.T) {
|
||
|
|
client := &ClientCommon{}
|
||
|
|
bulk := newBulkHandle(context.Background(), newBulkRuntime("cblk"), clientFileScope(), BulkOpenRequest{
|
||
|
|
BulkID: "nil-dial",
|
||
|
|
}, 0, nil, nil, 0, nil, nil, nil, nil, nil)
|
||
|
|
if _, err := client.sendDedicatedBulkAttachRequest(context.Background(), nil, bulk); !errors.Is(err, errTransportDetached) {
|
||
|
|
t.Fatalf("nil dial result error = %v, want transport detached", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDedicatedSidecarCloseWaitsForAttachment(t *testing.T) {
|
||
|
|
left, right := netpkg.Pipe()
|
||
|
|
defer left.Close()
|
||
|
|
defer right.Close()
|
||
|
|
sidecar := newBulkDedicatedSidecar(left, 1)
|
||
|
|
entered := make(chan struct{})
|
||
|
|
release := make(chan struct{})
|
||
|
|
attachDone := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
attachDone <- sidecar.withConn(func(conn netpkg.Conn) error {
|
||
|
|
close(entered)
|
||
|
|
<-release
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
}()
|
||
|
|
select {
|
||
|
|
case <-entered:
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("sidecar attachment did not start")
|
||
|
|
}
|
||
|
|
closeDone := make(chan struct{})
|
||
|
|
go func() {
|
||
|
|
sidecar.close()
|
||
|
|
close(closeDone)
|
||
|
|
}()
|
||
|
|
select {
|
||
|
|
case <-closeDone:
|
||
|
|
t.Fatal("sidecar closed while attachment still held")
|
||
|
|
case <-time.After(25 * time.Millisecond):
|
||
|
|
}
|
||
|
|
close(release)
|
||
|
|
if err := <-attachDone; err != nil {
|
||
|
|
t.Fatalf("sidecar attachment error = %v", err)
|
||
|
|
}
|
||
|
|
select {
|
||
|
|
case <-closeDone:
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("sidecar close did not finish after attachment release")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBulkAcceptDispatchRejectsResetBeforeHandler(t *testing.T) {
|
||
|
|
bulk := newBulkHandle(context.Background(), newBulkRuntime("sblk"), serverFileDomain+":test", BulkOpenRequest{
|
||
|
|
BulkID: "stale-dispatch",
|
||
|
|
}, 0, nil, nil, 0, nil, nil, nil, nil, nil)
|
||
|
|
bulk.markReset(errTransportDetached)
|
||
|
|
var calls atomic.Int32
|
||
|
|
err := dispatchBulkAccept(func(BulkAcceptInfo) error {
|
||
|
|
calls.Add(1)
|
||
|
|
return nil
|
||
|
|
}, bulk, BulkAcceptInfo{Bulk: bulk})
|
||
|
|
if !errors.Is(err, errTransportDetached) {
|
||
|
|
t.Fatalf("stale dispatch error = %v, want transport detached", err)
|
||
|
|
}
|
||
|
|
if got := calls.Load(); got != 0 {
|
||
|
|
t.Fatalf("stale dispatch handler calls = %d, want 0", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestClientDedicatedSidecarDropsFrameAfterTransportReattach(t *testing.T) {
|
||
|
|
client := NewClient().(*ClientCommon)
|
||
|
|
UseLegacySecurityClient(client)
|
||
|
|
stopCtx, stopFn := context.WithCancel(context.Background())
|
||
|
|
defer stopFn()
|
||
|
|
queue := stario.NewQueueCtx(stopCtx, 4, ^uint32(0))
|
||
|
|
oldLeft, oldRight := netpkg.Pipe()
|
||
|
|
defer oldRight.Close()
|
||
|
|
epoch := client.beginClientSessionEpoch()
|
||
|
|
client.setClientSessionRuntime(newClientSessionRuntime(oldLeft, stopCtx, stopFn, queue, epoch))
|
||
|
|
client.markSessionStarted()
|
||
|
|
defer client.markSessionStopped("test done", nil)
|
||
|
|
oldRoute := client.clientSessionRouteSnapshot()
|
||
|
|
|
||
|
|
sidecarLeft, sidecarRight := netpkg.Pipe()
|
||
|
|
defer sidecarRight.Close()
|
||
|
|
payload, err := client.encodeDedicatedBulkBatchPayload(101, []bulkDedicatedSendRequest{{
|
||
|
|
Type: bulkFastPayloadTypeData,
|
||
|
|
Seq: 1,
|
||
|
|
Payload: []byte("stale"),
|
||
|
|
}})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("encode stale sidecar payload: %v", err)
|
||
|
|
}
|
||
|
|
frameRead := make(chan struct{})
|
||
|
|
releaseFrame := make(chan struct{})
|
||
|
|
readConn := &gatedDedicatedReadConn{
|
||
|
|
Conn: sidecarLeft,
|
||
|
|
total: bulkDedicatedRecordHeaderLen + len(payload),
|
||
|
|
reached: frameRead,
|
||
|
|
release: releaseFrame,
|
||
|
|
}
|
||
|
|
sidecar := newBulkDedicatedSidecar(readConn, 1)
|
||
|
|
loopDone := make(chan struct{})
|
||
|
|
go func() {
|
||
|
|
client.readDedicatedSidecarLoopAtRoute(sidecar, oldRoute)
|
||
|
|
close(loopDone)
|
||
|
|
}()
|
||
|
|
|
||
|
|
writeDone := make(chan error, 1)
|
||
|
|
go func() { writeDone <- writeBulkDedicatedRecord(sidecarRight, payload) }()
|
||
|
|
select {
|
||
|
|
case <-frameRead:
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("sidecar loop did not read the complete frame")
|
||
|
|
}
|
||
|
|
|
||
|
|
newLeft, newRight := netpkg.Pipe()
|
||
|
|
defer newRight.Close()
|
||
|
|
if err := client.attachClientSessionTransport(newLeft); err != nil {
|
||
|
|
t.Fatalf("attach replacement client transport: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
runtime := client.getBulkRuntime()
|
||
|
|
bulk := newBulkHandle(stopCtx, runtime, clientFileScope(), BulkOpenRequest{
|
||
|
|
BulkID: "stale-sidecar-frame",
|
||
|
|
DataID: 101,
|
||
|
|
Range: BulkRange{Length: 32},
|
||
|
|
}, oldRoute.epoch, nil, nil, 0, nil, nil, nil, nil, nil)
|
||
|
|
bulk.setClientSnapshotOwner(client)
|
||
|
|
bulk.setClientSessionRoute(oldRoute)
|
||
|
|
if err := runtime.registerInbound(clientFileScope(), bulk); err != nil {
|
||
|
|
t.Fatalf("register stale-route bulk: %v", err)
|
||
|
|
}
|
||
|
|
defer bulk.markReset(errors.New("test cleanup"))
|
||
|
|
|
||
|
|
close(releaseFrame)
|
||
|
|
if err := <-writeDone; err != nil {
|
||
|
|
t.Fatalf("write stale sidecar payload: %v", err)
|
||
|
|
}
|
||
|
|
select {
|
||
|
|
case <-loopDone:
|
||
|
|
case <-time.After(time.Second):
|
||
|
|
t.Fatal("stale-route sidecar loop did not stop after reattach")
|
||
|
|
}
|
||
|
|
|
||
|
|
bulk.mu.Lock()
|
||
|
|
defer bulk.mu.Unlock()
|
||
|
|
if len(bulk.readQueue) != 0 || len(bulk.readBuf.data) != 0 || bulk.resetErr != nil {
|
||
|
|
t.Fatalf("stale sidecar frame mutated bulk: queued=%d buffered=%d reset=%v", len(bulk.readQueue), len(bulk.readBuf.data), bulk.resetErr)
|
||
|
|
}
|
||
|
|
}
|