Ctrl K
ComunidadGuías…

internal/mtprotoedge/rpc_flight.go

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

Archivo original ↗Preguntar a KiBot
package mtprotoedge

import (
	"context"
	"errors"
	"sync"
	"sync/atomic"

	"github.com/iamxvbaba/td/tlprofile"
)

// A 10,000-session startup can transiently retain more than one scheduled RPC
// per connection while downstream reads drain. Request materialization remains
// bounded independently by RPCGlobalMaxBytes, so this count limit provides
// queue/owner headroom without turning the scheduler into an unbounded queue.
const rpcResultFlightDefaultMaxPending = 32768

var (
	// ErrRPCResultFlightCapacity is returned before installing a new owner when
	// the process-wide in-flight claim table has reached its hard bound.
	ErrRPCResultFlightCapacity     = errors.New("mtproto rpc result in-flight capacity exhausted")
	ErrRPCResultSubscriberCapacity = errors.New("mtproto rpc result subscriber capacity exhausted")
	ErrRPCResultFlightInvalid      = errors.New("mtproto rpc result in-flight claim is invalid")
	ErrRPCResultIdentityMismatch   = errors.New("mtproto rpc result request identity mismatch")
	ErrRPCResultReplayUnavailable  = errors.New("mtproto rpc result replay payload is unavailable")
	ErrRPCAdmissionSeqExhausted    = errors.New("mtproto rpc admission sequence exhausted")
)

// rpcResultIdentityMismatchError carries the winner's immutable admission
// profile from inside the ledger shard critical section. A replacement Conn can
// re-decode the same naked body under that grammar even if the winner aborts
// immediately after the mismatch is returned.
type rpcResultIdentityMismatchError struct {
	profile    tlprofile.Profile
	hasProfile bool
}

func (e *rpcResultIdentityMismatchError) Error() string { return ErrRPCResultIdentityMismatch.Error() }
func (e *rpcResultIdentityMismatchError) Is(target error) bool {
	return target == ErrRPCResultIdentityMismatch
}

func identityMismatch(identity rpcResultRequestIdentity) error {
	return &rpcResultIdentityMismatchError{profile: identity.profile, hasProfile: identity.valid && identity.profile != 0}
}

type rpcResultRequestIdentity struct {
	exact tlprofile.PreparedIdentity
	// profile is retained separately because PreparedIdentity is opaque.
	// It lets a same-msg_id replay be re-admitted with the original request
	// grammar after the session default has moved to another Layer.
	profile tlprofile.Profile
	valid   bool
}

func (i rpcResultRequestIdentity) matches(requested rpcResultRequestIdentity) bool {
	if !requested.valid {
		// Service-message callers carry no API request identity. Exact API callers
		// must always match the winner's full prepared identity.
		return true
	}
	return i.valid && i.exact == requested.exact
}

type rpcResultAcquireState uint8

const (
	rpcResultAcquireCompleted rpcResultAcquireState = iota + 1
	rpcResultAcquireAcknowledged
	rpcResultAcquirePending
	rpcResultAcquireOwner
)

// rpcResultAcquire is the atomic outcome for one
// (auth_key_id, session_id, req_msg_id) claim.
//
// Exactly one state-specific field is non-nil:
//   - completed: encoded contains the immutable completed rpc_result;
//   - acknowledged: ACK won while the owner was still pending, so the duplicate
//     must be ACK-only until that owner completes;
//   - pending: waiter joins the already-running owner;
//   - owner: owner must eventually complete through ledger Complete or Abort.
type rpcResultAcquire struct {
	state          rpcResultAcquireState
	admissionSeq   uint64
	encoded        *encodedOutboundMessage
	waiter         *rpcResultWaiter
	owner          *rpcResultOwnerLease
	executionKnown bool
	executionOK    bool
}

// rpcResultFlight is not part of the completed receipt TTL lifecycle. Its
// done channel is closed exactly once while holding the owning ledger shard lock;
// channel close publishes encoded/ok to all waiters without a waiter goroutine.
type rpcResultFlight struct {
	done                 chan struct{}
	encoded              *encodedOutboundMessage
	ok                   bool
	subscribers          []func(*encodedOutboundMessage, bool)
	executionDone        bool
	executionOK          bool
	executionSubscribers []func(bool)
	// acknowledged is set only from the sole outbound actor's server-msg-id to
	// request-msg-id mapping. It can win the race with asynchronous completion;
	// publication then drops the receipt while still resolving waiters with the
	// immutable result that was already written on the wire.
	acknowledged bool
	// subscriberSlots counts callbacks retained by this pending flight. Result
	// and execution callbacks are charged independently; a replay alias installs
	// both atomically so a capacity failure cannot leave half an alias behind.
	subscriberSlots int
	identity        rpcResultRequestIdentity
	admissionSeq    uint64
	// reservation owns one entry at global, raw-auth and session scopes.
	// Complete transfers it to a receipt/tombstone; Abort releases it.
	reservation *rpcExecutionBudgetReservation
}

type rpcResultWaiter struct {
	ledger *rpcExecutionLedger
	key    rpcExecutionKey
	flight *rpcResultFlight
}

// Wait blocks until the owner publishes through Complete, aborts, or ctx expires.
// ok=false with err=nil means the owner aborted without a result.
func (w *rpcResultWaiter) Wait(ctx context.Context) (encoded *encodedOutboundMessage, ok bool, err error) {
	if w == nil || w.flight == nil || ctx == nil {
		return nil, false, ErrRPCResultFlightInvalid
	}

	// Prefer an already-published result over a concurrently canceled context.
	select {
	case <-w.flight.done:
		return w.flight.encoded, w.flight.ok, nil
	default:
	}

	select {
	case <-w.flight.done:
		return w.flight.encoded, w.flight.ok, nil
	case <-ctx.Done():
		// If completion raced with cancellation, prefer the terminal flight state.
		select {
		case <-w.flight.done:
			return w.flight.encoded, w.flight.ok, nil
		default:
			return nil, false, ctx.Err()
		}
	}
}

// Subscribe registers an event callback without creating a goroutine or
// occupying an RPC worker. The callback is invoked after the ledger shard lock is
// released; it must remain non-blocking.
func (w *rpcResultWaiter) Subscribe(fn func(*encodedOutboundMessage, bool)) error {
	if fn == nil {
		return ErrRPCResultFlightInvalid
	}
	return w.subscribe(fn, nil)
}

// SubscribeExecution registers a non-blocking callback for the terminal
// business outcome. This is deliberately separate from physical rpc_result
// delivery: invokeAfter* ordering depends on handler completion and must not
// occupy a shared RPC worker while a socket write is pending.
func (w *rpcResultWaiter) SubscribeExecution(fn func(bool)) error {
	if fn == nil {
		return ErrRPCResultFlightInvalid
	}
	return w.subscribe(nil, fn)
}

// SubscribeResultAndExecution atomically installs both halves of one pending
// replay alias. Either both callbacks are retained, or neither is; this avoids
// an execution-only closure surviving when the result subscriber hits a hard
// capacity limit.
func (w *rpcResultWaiter) SubscribeResultAndExecution(
	resultFn func(*encodedOutboundMessage, bool),
	executionFn func(bool),
) error {
	if resultFn == nil || executionFn == nil {
		return ErrRPCResultFlightInvalid
	}
	return w.subscribe(resultFn, executionFn)
}

func (w *rpcResultWaiter) subscribe(
	resultFn func(*encodedOutboundMessage, bool),
	executionFn func(bool),
) error {
	if w == nil || w.ledger == nil || w.flight == nil || (resultFn == nil && executionFn == nil) {
		return ErrRPCResultFlightInvalid
	}
	s := w.ledger.shard(w.key)
	var (
		encoded        *encodedOutboundMessage
		resultOK       bool
		resultReady    = resultFn == nil
		executionOK    bool
		executionReady = executionFn == nil
	)
	s.mu.Lock()
	if flight, exists := s.pending[w.key]; exists && flight == w.flight {
		slots := 0
		if resultFn != nil {
			slots++
		}
		if executionFn != nil && !flight.executionDone {
			slots++
		}
		if slots > 0 {
			if flight.subscriberSlots > w.ledger.subscriberPerFlight-slots ||
				!w.ledger.subscriberBudget.reserve(w.key, slots) {
				s.mu.Unlock()
				return ErrRPCResultSubscriberCapacity
			}
			flight.subscriberSlots += slots
		}
		if resultFn != nil {
			flight.subscribers = append(flight.subscribers, resultFn)
		}
		if executionFn != nil {
			if flight.executionDone {
				executionOK, executionReady = flight.executionOK, true
			} else {
				flight.executionSubscribers = append(flight.executionSubscribers, executionFn)
			}
		}
		s.mu.Unlock()
		if executionReady && executionFn != nil {
			executionFn(executionOK)
		}
		return nil
	}
	if resultFn != nil {
		select {
		case <-w.flight.done:
			encoded, resultOK, resultReady = w.flight.encoded, w.flight.ok, true
		default:
		}
	}
	if executionFn != nil && w.flight.executionDone {
		executionOK, executionReady = w.flight.executionOK, true
	}
	s.mu.Unlock()
	// Atomic late subscription also means no callback runs unless every requested
	// terminal state is available.
	if !resultReady || !executionReady {
		return ErrRPCResultFlightInvalid
	}
	if executionFn != nil {
		executionFn(executionOK)
	}
	if resultFn != nil {
		resultFn(encoded, resultOK)
	}
	return nil
}

type rpcResultOwnerLease struct {
	ledger    *rpcExecutionLedger
	key       rpcExecutionKey
	flight    *rpcResultFlight
	delivery  *rpcResultDelivery
	hookMu    sync.Mutex
	abortHook func()
	// handedOff means the inbound worker transferred terminal-result ownership to
	// the bounded egress pipeline. Its ordinary release callback must no longer
	// abort the flight merely because the socket write is still pending.
	handedOff atomic.Bool
}

func (l *rpcResultOwnerLease) SetAbortHook(fn func()) {
	if l == nil {
		return
	}
	l.hookMu.Lock()
	l.abortHook = fn
	l.hookMu.Unlock()
}

// InstallAbortHook installs fn only while this lease still owns the pending
// flight. The shard lock linearizes installation with Abort/Complete so a registry
// cannot publish a candidate after its owner has already disappeared.
func (l *rpcResultOwnerLease) InstallAbortHook(fn func()) bool {
	if l == nil || l.ledger == nil || l.flight == nil || fn == nil {
		return false
	}
	s := l.ledger.shard(l.key)
	s.mu.Lock()
	flight, ok := s.pending[l.key]
	if !ok || flight != l.flight {
		s.mu.Unlock()
		return false
	}
	l.hookMu.Lock()
	l.abortHook = fn
	l.hookMu.Unlock()
	s.mu.Unlock()
	return true
}

func (l *rpcResultOwnerLease) Waiter() *rpcResultWaiter {
	if l == nil || l.ledger == nil || l.flight == nil {
		return nil
	}
	return &rpcResultWaiter{ledger: l.ledger, key: l.key, flight: l.flight}
}

func (l *rpcResultOwnerLease) TryRetarget(reqMsgID int64) bool {
	return l != nil && l.delivery != nil && (&encodedOutboundMessage{delivery: l.delivery}).tryRetarget(reqMsgID)
}

func (l *rpcResultOwnerLease) Delivery() *rpcResultDelivery {
	if l == nil {
		return nil
	}
	return l.delivery
}

// CompleteExecution publishes the handler outcome exactly once while this
// lease still owns the flight. success=false includes RPC errors, internal
// failures and dependency failures. Delivery/ledger completion remains a
// separate later transition.
func (l *rpcResultOwnerLease) CompleteExecution(success bool) bool {
	if l == nil || l.ledger == nil || l.flight == nil {
		return false
	}
	s := l.ledger.shard(l.key)
	s.mu.Lock()
	flight, ok := s.pending[l.key]
	if !ok || flight != l.flight || flight.executionDone {
		s.mu.Unlock()
		return false
	}
	flight.executionDone = true
	flight.executionOK = success
	subscribers := append([]func(bool){}, flight.executionSubscribers...)
	flight.executionSubscribers = nil
	l.ledger.releaseFlightSubscriberSlotsLocked(l.key, flight, len(subscribers))
	s.mu.Unlock()
	for _, subscriber := range subscribers {
		subscriber(success)
	}
	return true
}

// HandOff transfers completion responsibility from the inbound RPC task to an
// already-admitted egress operation. The egress terminal callback must resolve
// the flight through Complete on both successful delivery and fenced failure.
func (l *rpcResultOwnerLease) HandOff() bool {
	if l == nil || l.ledger == nil || l.flight == nil {
		return false
	}
	s := l.ledger.shard(l.key)
	s.mu.Lock()
	defer s.mu.Unlock()
	flight, ok := s.pending[l.key]
	if !ok || flight != l.flight {
		return false
	}
	l.handedOff.Store(true)
	return true
}

// Abort releases an unfinished owner claim and wakes every waiter with no
// result. Pointer identity prevents an old lease from deleting a later owner
// that reacquired the same key. It returns true only for the winning abort.
func (l *rpcResultOwnerLease) Abort() bool {
	if l == nil || l.ledger == nil || l.flight == nil {
		return false
	}
	if l.handedOff.Load() {
		return false
	}
	s := l.ledger.shard(l.key)
	s.mu.Lock()
	if l.handedOff.Load() {
		s.mu.Unlock()
		return false
	}

	flight, ok := s.pending[l.key]
	if !ok || flight != l.flight {
		s.mu.Unlock()
		return false
	}
	delete(s.pending, l.key)
	if flight.reservation != nil {
		flight.reservation.release()
		flight.reservation = nil
	}
	l.ledger.flightLimit.release()
	l.ledger.activeAdmissions.retire(flight.admissionSeq)
	subscribers := append([]func(*encodedOutboundMessage, bool){}, flight.subscribers...)
	flight.subscribers = nil
	executionSubscribers := append([]func(bool){}, flight.executionSubscribers...)
	flight.executionSubscribers = nil
	l.ledger.releaseFlightSubscriberSlotsLocked(
		l.key, flight, len(subscribers)+len(executionSubscribers),
	)
	if !flight.executionDone {
		flight.executionDone = true
		flight.executionOK = false
	}
	close(flight.done)
	s.mu.Unlock()
	l.hookMu.Lock()
	abortHook := l.abortHook
	l.abortHook = nil
	l.hookMu.Unlock()
	if abortHook != nil {
		abortHook()
	}
	for _, subscriber := range subscribers {
		subscriber(nil, false)
	}
	for _, subscriber := range executionSubscribers {
		subscriber(false)
	}
	return true
}

type rpcResultFlightLimit struct {
	max  int64
	used atomic.Int64
}

func (l *rpcResultFlightLimit) reserve() bool {
	return l.reserveN(1)
}

func (l *rpcResultFlightLimit) reserveN(delta int64) bool {
	if l == nil || l.max <= 0 {
		return false
	}
	if delta <= 0 {
		return false
	}
	for {
		used := l.used.Load()
		if used < 0 || used > l.max-delta {
			return false
		}
		if l.used.CompareAndSwap(used, used+delta) {
			return true
		}
	}
}

func (l *rpcResultFlightLimit) release() {
	l.releaseN(1)
}

func (l *rpcResultFlightLimit) releaseN(delta int64) {
	if l == nil {
		return
	}
	if delta <= 0 {
		panic("mtproto rpc result counter release must be positive")
	}
	if remaining := l.used.Add(-delta); remaining < 0 {
		// Complete/Abort use map removal and lease identity to make double release
		// impossible. Fail fast instead of masking a capacity-accounting bug that
		// could otherwise admit more owners than the configured hard limit.
		panic("mtproto rpc result in-flight counter underflow")
	}
}

func (l *rpcResultFlightLimit) snapshot() int64 {
	if l == nil {
		return 0
	}
	return l.used.Load()
}

// Acquire atomically returns a completed result, joins the existing in-flight
// owner, or installs the unique owner lease. Pending entries have a separate
// lifecycle from completed receipt TTL but reserve the same global/auth/session
// ownership that Complete later transfers to a receipt or tombstone.
func (l *rpcExecutionLedger) Acquire(authKeyID [8]byte, sessionID, reqMsgID int64) (rpcResultAcquire, error) {
	return l.acquire(authKeyID, sessionID, reqMsgID, rpcResultRequestIdentity{})
}

func (l *rpcExecutionLedger) AcquireIdentified(
	authKeyID [8]byte,
	sessionID, reqMsgID int64,
	identity tlprofile.PreparedIdentity,
) (rpcResultAcquire, error) {
	return l.acquire(authKeyID, sessionID, reqMsgID, rpcResultRequestIdentity{exact: identity, valid: true})
}

// AcquireLayerIdentified is the production exact-RPC claim. In addition to the
// immutable full request identity it retains the admission profile required to
// decode a later same-msg_id naked replay under its original grammar.
func (l *rpcExecutionLedger) AcquireLayerIdentified(
	authKeyID [8]byte,
	sessionID, reqMsgID int64,
	profile tlprofile.Profile,
	identity tlprofile.PreparedIdentity,
) (rpcResultAcquire, error) {
	return l.acquire(authKeyID, sessionID, reqMsgID, rpcResultRequestIdentity{
		exact: identity, profile: profile, valid: true,
	})
}

// ExactAdmissionProfile returns the immutable profile of an existing exact
// owner/result. It does not create or join a flight. Callers still perform
// AcquireLayerIdentified after decode, which atomically rejects a same-msg_id
// body change by comparing the full prepared identity.
func (l *rpcExecutionLedger) ExactAdmissionProfile(authKeyID [8]byte, sessionID, reqMsgID int64) (tlprofile.Profile, bool) {
	if l == nil || reqMsgID == 0 {
		return 0, false
	}
	key := rpcExecutionKey{authKeyID: authKeyID, sessionID: sessionID, reqMsgID: reqMsgID}
	s := l.shard(key)
	now := s.now()
	s.mu.Lock()
	defer s.mu.Unlock()
	if elem := s.byKey[key]; elem != nil {
		entry := elem.Value.(*rpcExecutionReceipt)
		if entry.expiresAt.After(now) {
			if entry.identity.valid && entry.identity.profile != 0 {
				return entry.identity.profile, true
			}
			return 0, false
		}
		s.removeElement(elem)
	}
	if flight := s.pending[key]; flight != nil && flight.identity.valid && flight.identity.profile != 0 {
		return flight.identity.profile, true
	}
	return 0, false
}

func (l *rpcExecutionLedger) acquire(
	authKeyID [8]byte,
	sessionID, reqMsgID int64,
	identity rpcResultRequestIdentity,
) (rpcResultAcquire, error) {
	if l == nil || reqMsgID == 0 {
		return rpcResultAcquire{}, ErrRPCResultFlightInvalid
	}
	key := rpcExecutionKey{authKeyID: authKeyID, sessionID: sessionID, reqMsgID: reqMsgID}
	s := l.shard(key)
	reclaimedExpired := false
	for {
		now := s.now()
		s.mu.Lock()
		s.expireLocked(now)

		if elem, ok := s.byKey[key]; ok {
			entry := elem.Value.(*rpcExecutionReceipt)
			if !entry.identity.matches(identity) {
				s.mu.Unlock()
				return rpcResultAcquire{}, identityMismatch(entry.identity)
			}
			if entry.acknowledged {
				result := rpcResultAcquire{
					state: rpcResultAcquireAcknowledged, admissionSeq: entry.admissionSeq,
					executionKnown: entry.executionKnown, executionOK: entry.executionOK,
				}
				s.mu.Unlock()
				return result, nil
			}
			if entry.unavailable {
				s.mu.Unlock()
				return rpcResultAcquire{}, ErrRPCResultFlightCapacity
			}
			result := rpcResultAcquire{
				state: rpcResultAcquireCompleted, admissionSeq: entry.admissionSeq,
				executionKnown: entry.executionKnown, executionOK: entry.executionOK,
			}
			s.mu.Unlock()
			encoded, replayable := l.replayStore.rpcResult(authKeyID, sessionID, reqMsgID)
			if !replayable {
				return rpcResultAcquire{}, ErrRPCResultReplayUnavailable
			}
			result.encoded = encoded
			return result, nil
		}
		if flight, ok := s.pending[key]; ok {
			if !flight.identity.matches(identity) {
				s.mu.Unlock()
				return rpcResultAcquire{}, identityMismatch(flight.identity)
			}
			if flight.acknowledged {
				result := rpcResultAcquire{
					state: rpcResultAcquireAcknowledged, admissionSeq: flight.admissionSeq,
					executionKnown: flight.executionDone, executionOK: flight.executionOK,
				}
				s.mu.Unlock()
				return result, nil
			}
			result := rpcResultAcquire{
				state:        rpcResultAcquirePending,
				admissionSeq: flight.admissionSeq,
				waiter:       &rpcResultWaiter{ledger: l, key: key, flight: flight},
			}
			s.mu.Unlock()
			return result, nil
		}
		if !l.flightLimit.reserve() {
			s.mu.Unlock()
			return rpcResultAcquire{}, ErrRPCResultFlightCapacity
		}
		reservation := l.fairBudget.reserveOwner(key)
		if reservation == nil {
			l.flightLimit.release()
			s.mu.Unlock()
			if reclaimedExpired {
				return rpcResultAcquire{}, ErrRPCResultFlightCapacity
			}
			// Expired rows in another full-key shard may be the only consumers at
			// the global, auth or session scope. Reap once, then retry every identity
			// and capacity check because another goroutine may have won this key.
			l.expireReceipts()
			reclaimedExpired = true
			continue
		}
		var admissionSeq uint64
		if identity.valid {
			var err error
			admissionSeq, err = l.activeAdmissions.allocateAndRegister(&l.nextAdmissionSeq)
			if err != nil {
				reservation.release()
				l.flightLimit.release()
				s.mu.Unlock()
				return rpcResultAcquire{}, err
			}
		}
		flight := &rpcResultFlight{
			done: make(chan struct{}), identity: identity, admissionSeq: admissionSeq,
			reservation: reservation,
		}
		if s.pending == nil {
			s.pending = make(map[rpcExecutionKey]*rpcResultFlight)
		}
		s.pending[key] = flight
		s.mu.Unlock()
		return rpcResultAcquire{
			state:        rpcResultAcquireOwner,
			admissionSeq: admissionSeq,
			owner: &rpcResultOwnerLease{
				ledger: l, key: key, flight: flight, delivery: newRPCResultDelivery(reqMsgID),
			},
		}, nil
	}
}

// completeRPCResultFlightLocked publishes encoded to the current owner claim.
// The caller must hold s.mu and must publish the completed receipt first.
func (l *rpcExecutionLedger) completeRPCResultFlightLocked(
	s *rpcExecutionLedgerShard,
	key rpcExecutionKey,
	encoded *encodedOutboundMessage,
) (
	[]func(*encodedOutboundMessage, bool),
	[]func(bool),
	bool,
) {
	if l == nil || s == nil || encoded == nil {
		return nil, nil, false
	}
	flight, ok := s.pending[key]
	if !ok {
		return nil, nil, false
	}
	delete(s.pending, key)
	if flight.reservation != nil {
		flight.reservation.releasePending()
		// The completed entry now owns the same reservation.
		flight.reservation = nil
	}
	flight.encoded = encoded
	flight.ok = true
	l.flightLimit.release()
	l.activeAdmissions.retire(flight.admissionSeq)
	subscribers := append([]func(*encodedOutboundMessage, bool){}, flight.subscribers...)
	flight.subscribers = nil
	executionSubscribers := append([]func(bool){}, flight.executionSubscribers...)
	flight.executionSubscribers = nil
	executionOK := flight.executionOK
	if !flight.executionDone {
		// A result without an explicit handler-completion proof cannot satisfy an
		// invokeAfter dependency. Production completes execution before Complete;
		// branch is the conservative terminal cleanup for defensive callers.
		flight.executionDone = true
		flight.executionOK = false
		executionOK = false
	}
	l.releaseFlightSubscriberSlotsLocked(
		key, flight, len(subscribers)+len(executionSubscribers),
	)
	close(flight.done)
	return subscribers, executionSubscribers, executionOK
}

// releaseFlightSubscriberSlotsLocked releases callbacks detached from a flight.
// The caller holds that flight's ledger shard lock, preserving the only lock
// order used by subscription: shard -> subscriber budget.
func (l *rpcExecutionLedger) releaseFlightSubscriberSlotsLocked(
	key rpcExecutionKey,
	flight *rpcResultFlight,
	slots int,
) {
	if slots == 0 {
		return
	}
	if l == nil || flight == nil || slots < 0 || flight.subscriberSlots < slots {
		panic("mtproto rpc result subscriber slot underflow")
	}
	flight.subscriberSlots -= slots
	l.subscriberBudget.release(key, slots)
}