owpengram-server/internal/mtprotoedge/rpc_flight.go
2026-09-01 12:06:31 +03:00

717 lines
22 KiB
Go

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)
}