flow_budget.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package nebula
  2. import (
  3. "sync/atomic"
  4. "time"
  5. "github.com/slackhq/nebula/overlay/tio"
  6. )
  7. // txBudget is the reservation credit of one tunnel, shared by every staged reader sending on it. The low 24 bits of
  8. // state are outstanding counters, the next 24 spent credit, and the top 16 the sole ordered reader (q+1); owner zero
  9. // means shared. Readers beyond the owner field's range always take the conservative shared path.
  10. //
  11. // A cap on outstanding counters alone is not enough: a fast reader could repeatedly send and reacquire credit,
  12. // overtaking a paused reader by arbitrarily far. So once readers overlap, or another reader is blocked, spent credit
  13. // is only recycled when ALL reservations have drained. A sole reader instead gets each ordered completion refunded,
  14. // its later batches cannot overtake its earlier ones. All transitions are one CAS, without locks.
  15. //
  16. // The credit is half the peer's replay window. The other half is left for uncredited sends and transport reordering:
  17. // inline and control traffic, and relay forwarding through SendVia, which takes counters on the relay tunnel without
  18. // credit. Unlimited bypass traffic or network delay can still exceed it. The window is not negotiated, so
  19. // ci.window.length, from the build-time ReplayWindow, is also the assumed peer window.
  20. type txBudget struct{ state atomic.Uint64 }
  21. const txBudgetMask = uint64(1<<24 - 1)
  22. // Fail compilation for unsupported build-time windows: a power of two, at least 8192, fitting the credit fields (up
  23. // to 16777216).
  24. const (
  25. _ = uint64(txBudgetMask) - ReplayWindow/2
  26. _ = uint64(ReplayWindow) - 8192
  27. _ = uint64(0) - (ReplayWindow & (ReplayWindow - 1))
  28. )
  29. func txBudgetOwner(q int) uint64 {
  30. if q < 0 || q >= 65535 {
  31. return 0
  32. }
  33. return uint64(q + 1)
  34. }
  35. func (b *txBudget) acquire(n, limit, owner uint64) bool {
  36. for {
  37. old := b.state.Load()
  38. pending, spent, who := old&txBudgetMask, (old>>24)&txBudgetMask, old>>48
  39. if n > limit || spent > limit-n {
  40. // A blocked reader ends sole ownership so ordered completions stop refunding credit. Otherwise a busy
  41. // owner could keep a larger waiter blocked forever without ever sharing a reservation.
  42. if who != 0 && who != owner && !b.state.CompareAndSwap(old, old&^(uint64(0xffff)<<48)) {
  43. continue
  44. }
  45. return false
  46. }
  47. if pending == 0 {
  48. who = owner
  49. } else if who != owner {
  50. who = 0
  51. }
  52. next := who<<48 | (spent+n)<<24 | (pending + n)
  53. if b.state.CompareAndSwap(old, next) {
  54. return true
  55. }
  56. }
  57. }
  58. // release runs only after the socket flush or an explicit drop. A rollback returns credit before any message counter
  59. // was allocated, as when a later tunnel of a relay or multi-tunnel batch could not acquire its own.
  60. func (b *txBudget) release(n uint64, rollback bool) {
  61. for {
  62. old := b.state.Load()
  63. pending, spent, owner := old&txBudgetMask, (old>>24)&txBudgetMask, old>>48
  64. pending -= n
  65. if rollback || owner != 0 {
  66. spent -= n
  67. }
  68. next := owner<<48 | spent<<24 | pending
  69. if pending == 0 {
  70. next = 0
  71. }
  72. if b.state.CompareAndSwap(old, next) {
  73. return
  74. }
  75. }
  76. }
  77. func (ci *ConnectionState) txBudgetLimit() uint64 { return ci.window.length / 2 }
  78. func (r *txReserver) oversized() bool {
  79. for _, t := range r.tunnels {
  80. if t.n > t.ci.txBudgetLimit() {
  81. return true
  82. }
  83. }
  84. return false
  85. }
  86. func (r *txReserver) tryBudget() bool {
  87. for i, t := range r.tunnels {
  88. if !t.ci.txBudget.acquire(t.n, t.ci.txBudgetLimit(), r.owner) {
  89. for _, acquired := range r.tunnels[:i] {
  90. acquired.ci.txBudget.release(acquired.n, true)
  91. }
  92. return false
  93. }
  94. }
  95. return true
  96. }
  97. // waitBudget acquires credit for every tunnel of the batch, never holding a partial acquisition while it waits. Each
  98. // reader first flushes its own oldest batch, flushes need no credit; with none, another reader holds all outstanding
  99. // work and can flush it independently. So there is no circular wait, even with relay tunnels acquired in opposite
  100. // orders. The sleep is only for readers with no local work, not the uncontended hot path. Acquisition is
  101. // all-or-nothing within each direct or relay batch, so pressure on one tunnel also delays packets for idle tunnels
  102. // in that batch. Reader fairness is not guaranteed.
  103. func (p *stagedTx) waitBudget(r *txReserver) bool {
  104. if r.tryBudget() {
  105. return true
  106. }
  107. for {
  108. if p.f.closed.Load() {
  109. return false
  110. }
  111. if !p.flushOldest() {
  112. time.Sleep(50 * time.Microsecond)
  113. }
  114. if r.tryBudget() {
  115. return true
  116. }
  117. }
  118. }
  119. func (s *txSlot) releaseBudget() {
  120. for _, t := range s.reservations {
  121. t.ci.txBudget.release(t.n, false)
  122. }
  123. clear(s.reservations)
  124. s.reservations = s.reservations[:0]
  125. }
  126. // sendLarge handles the unusual reads that exceed a whole tunnel's credit, such as tiny-MSS superpackets. It drains
  127. // the pipeline, then segments and sends them synchronously with credit for each segment, never exceeding the cap and
  128. // never filtering or running connection manager side effects twice. This deliberately slow path avoids a lone-batch
  129. // exception to the shared invariant.
  130. func (p *stagedTx) sendLarge(r *txReserver, items []txItem) {
  131. for p.flushOldest() {
  132. }
  133. for _, it := range items {
  134. err := tio.SegmentSuperpacket(it.pkt, func(seg []byte) error {
  135. if p.f.closed.Load() {
  136. return nil
  137. }
  138. part := it
  139. part.pkt, part.segs = tio.Packet{Bytes: seg}, 1
  140. r.tunnels = r.tunnels[:0]
  141. part.counter = r.need(part.hostinfo, part.ci, 1)
  142. if part.relayHI != nil {
  143. part.relayCounter = r.need(part.relayHI, part.relayHI.ConnectionState, 1)
  144. }
  145. if !p.waitBudget(r) {
  146. return nil
  147. }
  148. one := [1]txItem{part}
  149. if reserved := r.reserve(one[:]); len(reserved) != 0 {
  150. p.f.encryptTxItem(&reserved[0], p.nb, p.sb)
  151. p.f.flushSendBatch(p.sb, p.q)
  152. }
  153. for _, t := range r.tunnels {
  154. t.ci.txBudget.release(t.n, false)
  155. }
  156. return nil
  157. })
  158. if err != nil {
  159. it.hostinfo.logger(p.f.l).Error("Failed to segment superpacket for send", "error", err)
  160. }
  161. }
  162. clear(items)
  163. }