package mtprotoedge import "sync" // bulkRPCScheduler limits runnable file handlers before they enter the shared // inbound worker pool. Waiters remain behind inboundRPCGate, so bulk backend // latency cannot occupy every worker needed by bootstrap and control RPCs. type bulkRPCScheduler struct { mu sync.Mutex max int inUse int waiters []*bulkRPCLease closed bool } type bulkRPCLease struct { scheduler *bulkRPCScheduler granted bool released bool notified bool notify func(bool) } // bulkRPCAdmission serializes the two bulk prerequisites: a request may enter // the process-wide handler scheduler only after its logical session owns an ACK // window credit. This prevents credit-blocked requests from parking all global // handler slots. type bulkRPCAdmission struct { mu sync.Mutex scheduler *bulkRPCScheduler lease *bulkRPCLease released bool } func newBulkRPCScheduler(max int) *bulkRPCScheduler { if max <= 0 { max = 1 } return &bulkRPCScheduler{max: max} } func (s *bulkRPCScheduler) reserve() *bulkRPCLease { if s == nil { return nil } lease := &bulkRPCLease{scheduler: s} s.mu.Lock() if s.closed { lease.released = true } else if s.inUse < s.max { s.inUse++ lease.granted = true } else { s.waiters = append(s.waiters, lease) } s.mu.Unlock() return lease } func (l *bulkRPCLease) subscribe(notify func(bool)) { if l == nil || l.scheduler == nil || notify == nil { return } s := l.scheduler s.mu.Lock() l.notify = notify granted, released := l.granted, l.released shouldNotify := (granted || released) && !l.notified if shouldNotify { l.notified = true } s.mu.Unlock() if !shouldNotify { return } if granted { notify(true) } else { notify(false) } } func (l *bulkRPCLease) release() { if l == nil || l.scheduler == nil { return } s := l.scheduler var notifications []func(bool) s.mu.Lock() if l.released { s.mu.Unlock() return } l.released = true if l.granted { l.granted = false s.inUse-- } for !s.closed && s.inUse < s.max && len(s.waiters) > 0 { next := s.waiters[0] s.waiters[0] = nil s.waiters = s.waiters[1:] if next == nil || next.released { continue } next.granted = true s.inUse++ if next.notify != nil && !next.notified { next.notified = true notifications = append(notifications, next.notify) } } s.mu.Unlock() for _, notify := range notifications { notify(true) } } func newBulkRPCAdmission(scheduler *bulkRPCScheduler) *bulkRPCAdmission { if scheduler == nil { return nil } return &bulkRPCAdmission{scheduler: scheduler} } func (a *bulkRPCAdmission) subscribeAfter(credit *outboundBulkCredit, notify func(bool)) { if a == nil || a.scheduler == nil || credit == nil || notify == nil { if notify != nil { notify(false) } return } credit.subscribe(func(success bool) { if !success { notify(false) return } lease := a.scheduler.reserve() a.mu.Lock() if a.released { a.mu.Unlock() lease.release() notify(false) return } a.lease = lease a.mu.Unlock() lease.subscribe(notify) }) } func (a *bulkRPCAdmission) release() { if a == nil { return } a.mu.Lock() if a.released { a.mu.Unlock() return } a.released = true lease := a.lease a.lease = nil a.mu.Unlock() lease.release() } func (s *bulkRPCScheduler) close() { if s == nil { return } var notifications []func(bool) s.mu.Lock() s.closed = true for _, lease := range s.waiters { if lease == nil || lease.released { continue } lease.released = true if lease.notify != nil && !lease.notified { lease.notified = true notifications = append(notifications, lease.notify) } } s.waiters = nil s.mu.Unlock() for _, notify := range notifications { notify(false) } }