Ctrl K
ComunidadGuías…

internal/mtprotoedge/rpc_result_subscriber_budget.go

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

Archivo original ↗Preguntar a KiBot
package mtprotoedge

import (
	"encoding/binary"
	"hash/maphash"
	"sync"
)

const (
	rpcResultSubscriberMaxGlobal    = 1 << 16
	rpcResultSubscriberMaxAuth      = 1 << 13
	rpcResultSubscriberMaxSession   = 1 << 11
	rpcResultSubscriberMaxPerFlight = 1 << 7
)

type rpcResultSubscriberBudgetShard[K comparable] struct {
	mu    sync.Mutex
	usage map[K]int64
}

// rpcResultSubscriberBudget bounds callbacks retained by pending replay
// flights independently from owner/result bytes. A duplicate does not reserve a
// new result row, so charging only unique owners would otherwise leave an
// unbounded same-msg-id reconnect path.
type rpcResultSubscriberBudget struct {
	seed          maphash.Seed
	global        rpcResultFlightLimit
	authLimit     int64
	sessionLimit  int64
	authShards    [rpcExecutionBudgetShards]rpcResultSubscriberBudgetShard[[8]byte]
	sessionShards [rpcExecutionBudgetShards]rpcResultSubscriberBudgetShard[rpcExecutionSessionBudgetKey]
}

func newRPCResultSubscriberBudget(
	seed maphash.Seed,
	globalLimit, authLimit, sessionLimit int,
) *rpcResultSubscriberBudget {
	b := &rpcResultSubscriberBudget{
		seed:         seed,
		authLimit:    int64(authLimit),
		sessionLimit: int64(sessionLimit),
	}
	b.global.max = int64(globalLimit)
	for i := range b.authShards {
		b.authShards[i].usage = make(map[[8]byte]int64)
		b.sessionShards[i].usage = make(map[rpcExecutionSessionBudgetKey]int64)
	}
	return b
}

func (b *rpcResultSubscriberBudget) reserve(key rpcExecutionKey, slots int) bool {
	if b == nil || slots <= 0 {
		return false
	}
	authShard := b.authShard(key.authKeyID)
	sessionKey := rpcExecutionSessionBudgetKey{authKeyID: key.authKeyID, sessionID: key.sessionID}
	sessionShard := b.sessionShard(sessionKey)
	delta := int64(slots)
	authShard.mu.Lock()
	sessionShard.mu.Lock()
	authUsed := authShard.usage[key.authKeyID]
	sessionUsed := sessionShard.usage[sessionKey]
	if !withinRPCExecutionBudget(authUsed, delta, b.authLimit) ||
		!withinRPCExecutionBudget(sessionUsed, delta, b.sessionLimit) ||
		!b.global.reserveN(delta) {
		sessionShard.mu.Unlock()
		authShard.mu.Unlock()
		return false
	}
	authShard.usage[key.authKeyID] = authUsed + delta
	sessionShard.usage[sessionKey] = sessionUsed + delta
	sessionShard.mu.Unlock()
	authShard.mu.Unlock()
	return true
}

func (b *rpcResultSubscriberBudget) release(key rpcExecutionKey, slots int) {
	if b == nil || slots <= 0 {
		panic("mtproto rpc result subscriber release must be positive")
	}
	authShard := b.authShard(key.authKeyID)
	sessionKey := rpcExecutionSessionBudgetKey{authKeyID: key.authKeyID, sessionID: key.sessionID}
	sessionShard := b.sessionShard(sessionKey)
	delta := int64(slots)
	authShard.mu.Lock()
	sessionShard.mu.Lock()
	authUsed, authOK := authShard.usage[key.authKeyID]
	sessionUsed, sessionOK := sessionShard.usage[sessionKey]
	if !authOK || !sessionOK || authUsed < delta || sessionUsed < delta {
		sessionShard.mu.Unlock()
		authShard.mu.Unlock()
		panic("mtproto rpc result subscriber budget underflow")
	}
	authUsed -= delta
	sessionUsed -= delta
	if authUsed == 0 {
		delete(authShard.usage, key.authKeyID)
	} else {
		authShard.usage[key.authKeyID] = authUsed
	}
	if sessionUsed == 0 {
		delete(sessionShard.usage, sessionKey)
	} else {
		sessionShard.usage[sessionKey] = sessionUsed
	}
	b.global.releaseN(delta)
	sessionShard.mu.Unlock()
	authShard.mu.Unlock()
}

func (b *rpcResultSubscriberBudget) authSnapshot(authKeyID [8]byte) int64 {
	if b == nil {
		return 0
	}
	shard := b.authShard(authKeyID)
	shard.mu.Lock()
	used := shard.usage[authKeyID]
	shard.mu.Unlock()
	return used
}

func (b *rpcResultSubscriberBudget) sessionSnapshot(authKeyID [8]byte, sessionID int64) int64 {
	if b == nil {
		return 0
	}
	key := rpcExecutionSessionBudgetKey{authKeyID: authKeyID, sessionID: sessionID}
	shard := b.sessionShard(key)
	shard.mu.Lock()
	used := shard.usage[key]
	shard.mu.Unlock()
	return used
}

func (b *rpcResultSubscriberBudget) authShard(
	authKeyID [8]byte,
) *rpcResultSubscriberBudgetShard[[8]byte] {
	index := maphash.Bytes(b.seed, authKeyID[:]) & (rpcExecutionBudgetShards - 1)
	return &b.authShards[index]
}

func (b *rpcResultSubscriberBudget) sessionShard(
	key rpcExecutionSessionBudgetKey,
) *rpcResultSubscriberBudgetShard[rpcExecutionSessionBudgetKey] {
	var raw [16]byte
	copy(raw[:8], key.authKeyID[:])
	binary.LittleEndian.PutUint64(raw[8:], uint64(key.sessionID))
	index := maphash.Bytes(b.seed, raw[:]) & (rpcExecutionBudgetShards - 1)
	return &b.sessionShards[index]
}