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
198
internal/mtprotoedge/transport_ownership_test.go
Normal file
198
internal/mtprotoedge/transport_ownership_test.go
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
package mtprotoedge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gotd/td/bin"
|
||||
)
|
||||
|
||||
type ownershipTestTransport struct {
|
||||
closeCalls atomic.Int32
|
||||
sendCalls atomic.Int32
|
||||
closed chan struct{}
|
||||
closeOnce sync.Once
|
||||
blockSend bool
|
||||
started chan struct{}
|
||||
startOnce sync.Once
|
||||
}
|
||||
|
||||
func newOwnershipTestTransport(blockSend bool) *ownershipTestTransport {
|
||||
return &ownershipTestTransport{
|
||||
closed: make(chan struct{}),
|
||||
blockSend: blockSend,
|
||||
started: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ownershipTestTransport) Send(context.Context, *bin.Buffer) error {
|
||||
t.sendCalls.Add(1)
|
||||
t.startOnce.Do(func() { close(t.started) })
|
||||
if !t.blockSend {
|
||||
select {
|
||||
case <-t.closed:
|
||||
return io.ErrClosedPipe
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
<-t.closed
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
|
||||
func (*ownershipTestTransport) Recv(context.Context, *bin.Buffer) error { return io.EOF }
|
||||
|
||||
func (t *ownershipTestTransport) Close() error {
|
||||
t.closeOnce.Do(func() {
|
||||
t.closeCalls.Add(1)
|
||||
close(t.closed)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPhysicalTransportTransferFencesStaleLease(t *testing.T) {
|
||||
raw := newOwnershipTestTransport(false)
|
||||
owner, oldLease := newPhysicalTransportOwner(raw)
|
||||
newLease, ok := oldLease.Transfer()
|
||||
if !ok {
|
||||
t.Fatal("transfer failed")
|
||||
}
|
||||
if err := oldLease.Send(context.Background(), &bin.Buffer{}); !errors.Is(err, ErrConnClosed) {
|
||||
t.Fatalf("stale Send error = %v, want ErrConnClosed", err)
|
||||
}
|
||||
if err := oldLease.Close(); err != nil {
|
||||
t.Fatalf("stale Close: %v", err)
|
||||
}
|
||||
if got := raw.closeCalls.Load(); got != 0 {
|
||||
t.Fatalf("stale Close closed raw %d times", got)
|
||||
}
|
||||
if err := newLease.Send(context.Background(), &bin.Buffer{}); err != nil {
|
||||
t.Fatalf("current Send: %v", err)
|
||||
}
|
||||
if err := owner.CloseAny(); err != nil {
|
||||
t.Fatalf("owner CloseAny: %v", err)
|
||||
}
|
||||
if got := raw.closeCalls.Load(); got != 1 {
|
||||
t.Fatalf("raw close calls = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhysicalTransportCloseWinningPreventsTransfer(t *testing.T) {
|
||||
raw := newOwnershipTestTransport(false)
|
||||
owner, lease := newPhysicalTransportOwner(raw)
|
||||
if err := owner.CloseAny(); err != nil {
|
||||
t.Fatalf("CloseAny: %v", err)
|
||||
}
|
||||
if next, ok := lease.Transfer(); ok || next != nil {
|
||||
t.Fatalf("transfer after close = (%p,%v), want nil,false", next, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhysicalTransportCloseAnyInterruptsWriteAndDefeatsTransfer(t *testing.T) {
|
||||
raw := newOwnershipTestTransport(true)
|
||||
owner, lease := newPhysicalTransportOwner(raw)
|
||||
sendDone := make(chan error, 1)
|
||||
go func() { sendDone <- lease.Send(context.Background(), &bin.Buffer{}) }()
|
||||
select {
|
||||
case <-raw.started:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("write did not block")
|
||||
}
|
||||
transferDone := make(chan bool, 1)
|
||||
go func() {
|
||||
_, ok := lease.Transfer()
|
||||
transferDone <- ok
|
||||
}()
|
||||
select {
|
||||
case ok := <-transferDone:
|
||||
t.Fatalf("transfer returned before blocked write ended: %v", ok)
|
||||
case <-time.After(20 * time.Millisecond):
|
||||
}
|
||||
if err := owner.CloseAny(); err != nil {
|
||||
t.Fatalf("CloseAny: %v", err)
|
||||
}
|
||||
select {
|
||||
case err := <-sendDone:
|
||||
if !errors.Is(err, io.ErrClosedPipe) {
|
||||
t.Fatalf("blocked Send error = %v", err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("CloseAny did not interrupt write")
|
||||
}
|
||||
select {
|
||||
case ok := <-transferDone:
|
||||
if ok {
|
||||
t.Fatal("transfer won after physical close")
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("transfer did not finish")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPhysicalCloseFencesActivationPublication(t *testing.T) {
|
||||
raw := newOwnershipTestTransport(false)
|
||||
owner, lease := newPhysicalTransportOwner(raw)
|
||||
s := New(Options{})
|
||||
c := s.newConnWithLease(lease, newTestAuthKey(t), 73001, 1)
|
||||
if !c.beginActivationClaim() {
|
||||
t.Fatal("activation claim failed")
|
||||
}
|
||||
if err := owner.CloseAny(); err != nil {
|
||||
t.Fatalf("CloseAny: %v", err)
|
||||
}
|
||||
if c.publishActivation() {
|
||||
t.Fatal("closed physical transport published active Conn")
|
||||
}
|
||||
if !c.terminal.Load() || c.lifecycleState() != connLifecycleRetired {
|
||||
t.Fatalf("closed Conn terminal=%v lifecycle=%v", c.terminal.Load(), c.lifecycleState())
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func TestPhysicalCloseBitPreventsActivationClaimBeforeLogicalFence(t *testing.T) {
|
||||
raw := newOwnershipTestTransport(false)
|
||||
owner, lease := newPhysicalTransportOwner(raw)
|
||||
s := New(Options{})
|
||||
c := s.newConnWithLease(lease, newTestAuthKey(t), 73002, 1)
|
||||
|
||||
// Hold the binding lock so CloseAny can linearize the physical closed bit but
|
||||
// cannot yet publish c.terminal. beginActivationClaim must inspect the lease
|
||||
// itself and refuse this otherwise-dangerous window.
|
||||
owner.bindingMu.Lock()
|
||||
closeDone := make(chan error, 1)
|
||||
go func() { closeDone <- owner.CloseAny() }()
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for owner.state.Load()&physicalTransportClosedBit == 0 && time.Now().Before(deadline) {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
if owner.state.Load()&physicalTransportClosedBit == 0 {
|
||||
owner.bindingMu.Unlock()
|
||||
t.Fatal("CloseAny did not publish closed bit")
|
||||
}
|
||||
if c.terminal.Load() {
|
||||
owner.bindingMu.Unlock()
|
||||
t.Fatal("logical fence escaped held binding lock")
|
||||
}
|
||||
if c.beginActivationClaim() {
|
||||
owner.bindingMu.Unlock()
|
||||
t.Fatal("closed physical generation entered activation claim")
|
||||
}
|
||||
owner.bindingMu.Unlock()
|
||||
select {
|
||||
case err := <-closeDone:
|
||||
if err != nil {
|
||||
t.Fatalf("CloseAny: %v", err)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("CloseAny did not finish")
|
||||
}
|
||||
if !c.terminal.Load() || c.lifecycleState() != connLifecycleRetired {
|
||||
t.Fatalf("logical fence terminal=%v lifecycle=%v", c.terminal.Load(), c.lifecycleState())
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue