fix: sync protocol and discussion stability fixes
This commit is contained in:
parent
9f73dc20da
commit
aa21bd04e1
43 changed files with 7258 additions and 503 deletions
116
internal/mtprotoedge/duplicate_admission_test.go
Normal file
116
internal/mtprotoedge/duplicate_admission_test.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package mtprotoedge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gotd/td/bin"
|
||||
"github.com/gotd/td/proto"
|
||||
"github.com/gotd/td/tg"
|
||||
)
|
||||
|
||||
type blockingDuplicateRPC struct {
|
||||
started chan struct{}
|
||||
release chan struct{}
|
||||
calls atomic.Int32
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func newBlockingDuplicateRPC() *blockingDuplicateRPC {
|
||||
return &blockingDuplicateRPC{
|
||||
started: make(chan struct{}, 4),
|
||||
release: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *blockingDuplicateRPC) Dispatch(ctx context.Context, _ [8]byte, _ int64, _ *bin.Buffer) (bin.Encoder, error) {
|
||||
h.calls.Add(1)
|
||||
h.started <- struct{}{}
|
||||
select {
|
||||
case <-h.release:
|
||||
return &tg.Config{ThisDC: 2}, nil
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (*blockingDuplicateRPC) NegotiatedLayer([8]byte, int64) (int, bool) { return 227, true }
|
||||
|
||||
func (h *blockingDuplicateRPC) unblock() { h.once.Do(func() { close(h.release) }) }
|
||||
|
||||
// TestPendingSameConnectionDuplicateDoesNotBlockFreshRequest models the core
|
||||
// Android startup failure: an RPC is executing, salt correction makes the client
|
||||
// resend the same msg_id, then initConnection assigns a fresh msg_id. A local
|
||||
// duplicate must not synchronously join the old owner on the socket read loop,
|
||||
// otherwise the fresh request remains unread until the old result/replay storm
|
||||
// has drained.
|
||||
func TestPendingSameConnectionDuplicateDoesNotBlockFreshRequest(t *testing.T) {
|
||||
const dc = 2
|
||||
handler := newBlockingDuplicateRPC()
|
||||
defer handler.unblock()
|
||||
addr, pub, server := startTestServer(t, Options{
|
||||
DC: dc,
|
||||
RPC: handler,
|
||||
RPCMaxInflight: 2,
|
||||
RPCGlobalWorkers: 2,
|
||||
RPCQueueSize: 8,
|
||||
})
|
||||
conn, auth, cipher := dialHandshake(t, addr, dc, pub)
|
||||
|
||||
ids := proto.NewMessageIDGen(time.Now)
|
||||
oldID := ids.New(proto.MessageFromClient)
|
||||
newID := ids.New(proto.MessageFromClient)
|
||||
|
||||
sendEncryptedWithSeq(t, conn, cipher, auth, oldID, 1, &tg.HelpGetConfigRequest{})
|
||||
waitDuplicateHandlerStarts(t, handler.started, "original request")
|
||||
|
||||
// Same ID/seq is a retransmission, not a second business operation.
|
||||
sendEncryptedWithSeq(t, conn, cipher, auth, oldID, 1, &tg.HelpGetConfigRequest{})
|
||||
// A client-side init/layer rewrap legitimately assigns a new ID and next seq.
|
||||
sendEncryptedWithSeq(t, conn, cipher, auth, newID, 3, &tg.HelpGetConfigRequest{})
|
||||
waitDuplicateHandlerStarts(t, handler.started, "fresh request behind duplicate")
|
||||
if got := handler.calls.Load(); got != 2 {
|
||||
t.Fatalf("handler calls before release = %d, want original + fresh only", got)
|
||||
}
|
||||
|
||||
handler.unblock()
|
||||
frames := collectReplyFrames(t, conn, cipher, auth.AuthKey, map[uint32]int{
|
||||
proto.ResultTypeID: 2,
|
||||
})
|
||||
results := make(map[int64]int)
|
||||
for _, frame := range frames {
|
||||
if frame.TypeID != proto.ResultTypeID {
|
||||
continue
|
||||
}
|
||||
var result proto.Result
|
||||
if err := result.Decode(frame.Plain); err != nil {
|
||||
t.Fatalf("decode rpc_result: %v", err)
|
||||
}
|
||||
results[result.RequestMessageID]++
|
||||
}
|
||||
if results[oldID] != 1 || results[newID] != 1 || len(results) != 2 {
|
||||
t.Fatalf("rpc_result counts = %+v, want one for old and one for fresh id", results)
|
||||
}
|
||||
if got := handler.calls.Load(); got != 2 {
|
||||
t.Fatalf("final handler calls = %d, want 2", got)
|
||||
}
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for server.rpcResults.flightLimit.snapshot() != 0 && time.Now().Before(deadline) {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
if got := server.rpcResults.flightLimit.snapshot(); got != 0 {
|
||||
t.Fatalf("duplicate admission leaked flight slots: %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func waitDuplicateHandlerStarts(t *testing.T, started <-chan struct{}, what string) {
|
||||
t.Helper()
|
||||
select {
|
||||
case <-started:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatalf("%s did not reach handler; socket read loop is likely blocked on a duplicate", what)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue