flow_budget_test.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package nebula
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/slackhq/nebula/test"
  6. "github.com/stretchr/testify/require"
  7. )
  8. var budgetWindows = []uint64{8192, 65536}
  9. // TestTXBudgetOvertaking: a fast sender must not recycle credit around a stalled older reservation. A pure in-flight
  10. // count passes the cap assertion but fails this replay test.
  11. func TestTXBudgetOvertaking(t *testing.T) {
  12. for _, window := range budgetWindows {
  13. t.Run(fmt.Sprint(window), func(t *testing.T) {
  14. var b txBudget
  15. limit := window / 2
  16. w := NewBits(window)
  17. l := test.NewLogger()
  18. require.True(t, b.acquire(1, limit, 1)) // counter 1 stalls
  19. for counter := uint64(2); counter <= limit; counter++ {
  20. require.True(t, b.acquire(1, limit, 2))
  21. require.True(t, w.Update(l, counter))
  22. b.release(1, false)
  23. }
  24. require.Equal(t, uint64(1), b.state.Load()&txBudgetMask)
  25. require.False(t, b.acquire(1, limit, 2), "spent credit cannot be recycled while the oldest sender stalls")
  26. // The other half of the window allows this many unreserved sends.
  27. for counter := limit + 1; counter <= window; counter++ {
  28. require.True(t, w.Update(l, counter))
  29. }
  30. require.True(t, w.Update(l, 1), "oldest reservation must still fit after using the whole margin")
  31. b.release(1, false)
  32. require.Zero(t, b.state.Load())
  33. require.True(t, b.acquire(limit, limit, 2))
  34. require.False(t, b.acquire(1, limit, 2))
  35. b.release(limit, false)
  36. })
  37. }
  38. }
  39. // TestTXBudgetBlockedReader models eight 512-segment reads of a busy owner and a 568-segment waiter, without socket
  40. // timing. Alone, the owner gets each completion refunded, so it keeps eight batches in flight forever, even across its
  41. // own failed acquisitions. But even when the owner always retries first, its completions must stop refunding credit
  42. // once the waiter is blocked, and let the budget drain. Waiter 0 is the conservative ID of readers beyond the owner
  43. // field's range.
  44. func TestTXBudgetBlockedReader(t *testing.T) {
  45. for _, waiter := range []uint64{1, 0} {
  46. t.Run(fmt.Sprintf("waiter=%d", waiter), func(t *testing.T) {
  47. var b txBudget
  48. const limit, n, want = uint64(4096), uint64(512), uint64(568)
  49. for range 8 {
  50. require.True(t, b.acquire(n, limit, 2))
  51. }
  52. for range 100 {
  53. require.False(t, b.acquire(n, limit, 2))
  54. b.release(n, false) // the sole sender flushes its oldest batch
  55. require.True(t, b.acquire(n, limit, 2))
  56. require.Equal(t, 8*n, b.state.Load()&txBudgetMask)
  57. }
  58. require.False(t, b.acquire(want, limit, waiter))
  59. for range 7 {
  60. b.release(n, false)
  61. require.False(t, b.acquire(n, limit, 2), "blocked reader must stop the owner's credit recycling")
  62. require.False(t, b.acquire(want, limit, waiter))
  63. }
  64. b.release(n, false)
  65. require.Zero(t, b.state.Load())
  66. // Even if the former owner wins the next acquisition, both now fit.
  67. require.True(t, b.acquire(n, limit, 2))
  68. require.True(t, b.acquire(want, limit, waiter))
  69. b.release(n, false)
  70. b.release(want, false)
  71. require.Zero(t, b.state.Load())
  72. })
  73. }
  74. }
  75. // TestTXBudgetRelayRollback: when a later tunnel of a batch cannot acquire, the earlier ones give their credit back,
  76. // spent credit included.
  77. func TestTXBudgetRelayRollback(t *testing.T) {
  78. for _, window := range budgetWindows {
  79. inner := &ConnectionState{window: NewBits(window)}
  80. outer := &ConnectionState{window: NewBits(window)}
  81. limit := inner.txBudgetLimit()
  82. require.True(t, inner.txBudget.acquire(10, limit, 1))
  83. require.True(t, outer.txBudget.acquire(limit, limit, 1))
  84. r := txReserver{owner: 2, tunnels: []txTunnel{{ci: inner, n: 20}, {ci: outer, n: 20}}}
  85. for range 100 {
  86. require.False(t, r.tryBudget())
  87. require.Equal(t, uint64(10), inner.txBudget.state.Load()&txBudgetMask)
  88. require.Equal(t, uint64(10), (inner.txBudget.state.Load()>>24)&txBudgetMask, "rollback must refund spent credit too")
  89. }
  90. outer.txBudget.release(limit, false)
  91. require.True(t, r.tryBudget())
  92. inner.txBudget.release(20, false)
  93. outer.txBudget.release(20, false)
  94. inner.txBudget.release(10, false)
  95. require.Zero(t, inner.txBudget.state.Load())
  96. require.Zero(t, outer.txBudget.state.Load())
  97. }
  98. }