Ctrl K
ComunidadGuías…

internal/mtprotoedge/rpc_delivery_hook_executor_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package mtprotoedge

import (
	"errors"
	"sync/atomic"
	"testing"
	"time"
)

func hookTestMessage(reqMsgID int64, coordinator *rpcResultDeliveryCoordinator, fn func()) *encodedOutboundMessage {
	msg := &encodedOutboundMessage{delivery: newRPCResultDelivery(reqMsgID, coordinator)}
	msg.setDeliveryHook(fn)
	return msg
}

func TestRPCDeliveryHookExecutorBoundsAdmissionWithoutBlockingDelivery(t *testing.T) {
	executor := newRPCDeliveryHookExecutor(1, 1)
	started := make(chan struct{})
	// The hook signals that it is running and then occupies the single worker
	// for a guaranteed 50ms. The sleep is what keeps the "completes" timing
	// honest: host monotonic clocks tick coarsely (a Windows VM in CI can
	// quantise back-to-back time.Now reads to the same tick), so a hook that
	// only waits on a channel a test closes microseconds later reliably
	// measures as 0ns duration and the snapshot assertion below would flake.
	first := hookTestMessage(1, nil, func() {
		close(started)
		time.Sleep(50 * time.Millisecond)
	})
	if err := first.prepareDeliveryHook(executor); err != nil {
		t.Fatalf("reserve first hook: %v", err)
	}
	delivered := make(chan struct{})
	go func() {
		first.markDelivered()
		close(delivered)
	}()
	select {
	case <-delivered:
	case <-time.After(100 * time.Millisecond):
		t.Fatal("physical delivery blocked on hook execution")
	}
	select {
	case <-started:
	case <-time.After(time.Second):
		t.Fatal("first hook did not start")
	}

	second := hookTestMessage(2, nil, func() {})
	if err := second.prepareDeliveryHook(executor); !errors.Is(err, ErrRPCDeliveryHookCapacity) {
		t.Fatalf("second reservation = %v, want capacity error", err)
	}
	// The first hook is still running (its 50ms window has not elapsed yet, and
	// admission is what matters, not how long the worker is held). Wait for it
	// to finish and drain the capacity token.
	deadline := time.Now().Add(time.Second)
	for len(executor.slots) != 0 && time.Now().Before(deadline) {
		time.Sleep(time.Millisecond)
	}
	if got := len(executor.slots); got != 0 {
		t.Fatalf("executor retained %d capacity slots", got)
	}
	snapshot := executor.runtimeSnapshot()
	if snapshot.workers != 1 || snapshot.capacity != 1 || snapshot.completed != 1 || snapshot.rejected != 1 ||
		snapshot.reserved != 0 || snapshot.queued != 0 || snapshot.running != 0 || snapshot.durationSeconds < 0.05 {
		t.Fatalf("executor snapshot = %#v", snapshot)
	}
}

func TestRPCDeliveryHookExecutorIsolatesPanicsAndContinues(t *testing.T) {
	executor := newRPCDeliveryHookExecutor(1, 2)
	first := hookTestMessage(1, nil, func() { panic("hook boom") })
	done := make(chan struct{})
	second := hookTestMessage(2, nil, func() { close(done) })
	if err := first.prepareDeliveryHook(executor); err != nil {
		t.Fatalf("reserve panic hook: %v", err)
	}
	if err := second.prepareDeliveryHook(executor); err != nil {
		t.Fatalf("reserve following hook: %v", err)
	}
	first.markDelivered()
	second.markDelivered()
	select {
	case <-done:
	case <-time.After(time.Second):
		t.Fatal("worker stopped after a hook panic")
	}
	deadline := time.Now().Add(time.Second)
	for executor.panics.Load() != 1 && time.Now().Before(deadline) {
		time.Sleep(time.Millisecond)
	}
	if got := executor.panics.Load(); got != 1 {
		t.Fatalf("recorded hook panics = %d, want 1", got)
	}
}

func TestEquivalentRPCDeliveryAttemptsShareExactlyOnceCoordinator(t *testing.T) {
	executor := newRPCDeliveryHookExecutor(1, 2)
	var calls atomic.Int32
	first := hookTestMessage(11, nil, func() { calls.Add(1) })
	second := hookTestMessage(22, first.delivery.coordinator, nil)
	if err := first.prepareDeliveryHook(executor); err != nil {
		t.Fatalf("reserve first attempt: %v", err)
	}
	if err := second.prepareDeliveryHook(executor); err != nil {
		t.Fatalf("reserve equivalent attempt: %v", err)
	}
	first.markDelivered()
	second.markDelivered()
	deadline := time.Now().Add(time.Second)
	for calls.Load() != 1 && time.Now().Before(deadline) {
		time.Sleep(time.Millisecond)
	}
	if got := calls.Load(); got != 1 {
		t.Fatalf("equivalent delivery hooks = %d, want 1", got)
	}
	deadline = time.Now().Add(time.Second)
	for len(executor.slots) != 0 && time.Now().Before(deadline) {
		time.Sleep(time.Millisecond)
	}
	if got := len(executor.slots); got != 0 {
		t.Fatalf("equivalent attempts leaked %d tickets", got)
	}
}

func TestRPCDeliveryHookExecutorStopRejectsNewAndDrainsReserved(t *testing.T) {
	executor := newRPCDeliveryHookExecutor(1, 2)
	ticket, ok := executor.reserve()
	if !ok {
		t.Fatal("reserve ticket")
	}
	stopped := make(chan bool, 1)
	go func() { stopped <- executor.stop(time.Second) }()
	deadline := time.Now().Add(time.Second)
	for time.Now().Before(deadline) {
		executor.mu.Lock()
		stopping := executor.stopping
		executor.mu.Unlock()
		if stopping {
			break
		}
		time.Sleep(time.Millisecond)
	}
	if _, ok := executor.reserve(); ok {
		t.Fatal("executor accepted a reservation after stop")
	}
	select {
	case <-stopped:
		t.Fatal("stop returned while a reserved ticket was still owned")
	default:
	}
	ticket.release()
	select {
	case ok := <-stopped:
		if !ok {
			t.Fatal("executor did not drain before timeout")
		}
	case <-time.After(time.Second):
		t.Fatal("executor stop did not finish after ticket release")
	}
	snapshot := executor.runtimeSnapshot()
	if snapshot.reserved != 0 || snapshot.queued != 0 || snapshot.running != 0 || snapshot.rejected < 1 {
		t.Fatalf("stopped executor snapshot = %#v", snapshot)
	}
}

func TestRPCDeliveryHookExecutorIsServerScoped(t *testing.T) {
	first := New(Options{RPCDeliveryHookWorkers: 2, RPCDeliveryHookMaxPending: 7})
	second := New(Options{RPCDeliveryHookWorkers: 3, RPCDeliveryHookMaxPending: 9})
	if first.rpcDeliveryHooks == nil || second.rpcDeliveryHooks == nil || first.rpcDeliveryHooks == second.rpcDeliveryHooks {
		t.Fatal("servers did not receive isolated delivery-hook executors")
	}
	firstSnapshot := first.RuntimeSnapshot()
	secondSnapshot := second.RuntimeSnapshot()
	if firstSnapshot.RPCDeliveryHookWorkers != 2 || firstSnapshot.RPCDeliveryHookCapacity != 7 ||
		secondSnapshot.RPCDeliveryHookWorkers != 3 || secondSnapshot.RPCDeliveryHookCapacity != 9 {
		t.Fatalf("server delivery-hook limits = %#v / %#v", firstSnapshot, secondSnapshot)
	}
}