bufferpool_test.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. package util
  2. import (
  3. "math"
  4. "math/bits"
  5. "sync"
  6. "testing"
  7. )
  8. // --- poolIndex / putIndex unit tests ---
  9. func TestPoolIndex(t *testing.T) {
  10. cases := []struct {
  11. length int
  12. want int
  13. }{
  14. {1, 0},
  15. {2, 1},
  16. {3, 2},
  17. {4, 2},
  18. {5, 3},
  19. {7, 3},
  20. {8, 3},
  21. {255, 8},
  22. {256, 8},
  23. {1023, 10},
  24. {1024, 10},
  25. {math.MaxUint16 - 50, 16},
  26. }
  27. for _, c := range cases {
  28. got := poolIndex(c.length)
  29. if got != c.want {
  30. t.Errorf("poolIndex(%d) = %d, want %d", c.length, got, c.want)
  31. }
  32. }
  33. }
  34. func TestAllocMinusOne(t *testing.T) {
  35. bp := newBufferPool()
  36. for i := 1; i <= 16; i++ {
  37. capacity := 1 << i
  38. length := capacity - 1
  39. if length <= 0 {
  40. continue
  41. }
  42. b := bp.Get(length)
  43. c := cap(b)
  44. pIndex := poolIndex(length)
  45. rIndex := putIndex(c)
  46. if pIndex != rIndex {
  47. t.Errorf("pIndex: %d != rIndex: %d\n", pIndex, rIndex)
  48. }
  49. }
  50. }
  51. func TestPutIndex(t *testing.T) {
  52. // putIndex must be the inverse of poolIndex for all power-of-two capacities
  53. // that Get hands out.
  54. for i := 1; i <= 16; i++ {
  55. cap := 1 << i
  56. got := putIndex(cap)
  57. if got != i {
  58. t.Errorf("putIndex(1<<%d = %d) = %d, want %d", i, cap, got, i)
  59. }
  60. }
  61. }
  62. func TestPoolIndexPutIndexRoundTrip(t *testing.T) {
  63. // For any requested length, the buffer Get returns has capacity 1<<poolIndex(length).
  64. // Confirm that putIndex maps that capacity back to the same pool slot.
  65. for length := 1; length <= math.MaxUint16; length++ {
  66. i := poolIndex(length)
  67. capacity := 1 << i
  68. j := putIndex(capacity)
  69. if i != j {
  70. t.Errorf("length=%d: poolIndex=%d, capacity=1<<%d=%d, putIndex=%d — round-trip broken",
  71. length, i, i, capacity, j)
  72. }
  73. }
  74. }
  75. // --- Get ---
  76. func TestGetNilOnZeroOrNegative(t *testing.T) {
  77. bp := newBufferPool()
  78. for _, n := range []int{0, -1, -100} {
  79. if got := bp.Get(n); got != nil {
  80. t.Errorf("Get(%d) = %v, want nil", n, got)
  81. }
  82. }
  83. }
  84. func TestGetLengthIsExact(t *testing.T) {
  85. bp := newBufferPool()
  86. for _, n := range []int{1, 2, 3, 7, 8, 100, 1000, 65535, 65536} {
  87. buf := bp.Get(n)
  88. if len(buf) != n {
  89. t.Errorf("Get(%d): len = %d, want %d", n, len(buf), n)
  90. }
  91. }
  92. }
  93. func TestGetCapacityIsPowerOfTwo(t *testing.T) {
  94. bp := newBufferPool()
  95. for _, n := range []int{1, 2, 3, 4, 5, 100, 1000, 550, math.MaxUint16 - 100, math.MaxUint16} {
  96. buf := bp.Get(n)
  97. c := cap(buf)
  98. if c == 0 || !isPowerOfTwo(c) {
  99. t.Errorf("Get(%d): cap = %d, not a power of two", n, c)
  100. }
  101. }
  102. }
  103. func TestGetCapacityIsSmallestFittingPowerOfTwo(t *testing.T) {
  104. bp := newBufferPool()
  105. cases := []struct {
  106. n int
  107. wantCap int
  108. }{
  109. {1, 1},
  110. {2, 2},
  111. {3, 4},
  112. {4, 4},
  113. {5, 8},
  114. {8, 8},
  115. {9, 16},
  116. {255, 256},
  117. {256, 256},
  118. {1024, 1024},
  119. }
  120. for _, c := range cases {
  121. buf := bp.Get(c.n)
  122. if cap(buf) != c.wantCap {
  123. t.Errorf("Get(%d): cap = %d, want %d", c.n, cap(buf), c.wantCap)
  124. }
  125. }
  126. }
  127. func TestGetOversizeFallback(t *testing.T) {
  128. bp := newBufferPool()
  129. n := math.MaxUint16 + 1
  130. buf := bp.Get(n)
  131. if len(buf) != n {
  132. t.Errorf("Get(MaxUint16+1): len = %d, want %d", len(buf), n)
  133. }
  134. }
  135. // --- Put ---
  136. func TestPutDropsZeroCapBuffer(t *testing.T) {
  137. // Put on a nil or zero-cap slice must not panic.
  138. bp := newBufferPool()
  139. bp.Put(nil)
  140. bp.Put([]byte{})
  141. }
  142. // --- Get / Put round-trip ---
  143. func TestGetPutSamePool(t *testing.T) {
  144. // A buffer returned via Put must land in the same pool that Get draws from,
  145. // so the very next Get (with the same length) should reuse it.
  146. bp := newBufferPool()
  147. buf := bp.Get(100)
  148. ptr := &buf[0]
  149. bp.Put(buf)
  150. buf2 := bp.Get(100)
  151. if &buf2[0] != ptr {
  152. // sync.Pool may have GC'd the entry; this is not a hard failure but
  153. // we at minimum require length and capacity to be correct.
  154. if len(buf2) != 100 {
  155. t.Errorf("Get(100) after Put: len = %d, want 100", len(buf2))
  156. }
  157. }
  158. }
  159. func TestPutRestoresFullCapacity(t *testing.T) {
  160. // After Put, the pooled slice should have full capacity, not the resliced length.
  161. // We verify this by inspecting what comes out of the pool on the next Get.
  162. bp := newBufferPool()
  163. buf := bp.Get(10) // len=10, cap=16
  164. bp.Put(buf) // must put back with cap=16
  165. buf2 := bp.Get(15) // asks for 15 — still fits in cap=16 pool
  166. if cap(buf2) < 15 {
  167. t.Errorf("After Put(cap=16), Get(15): cap = %d, too small", cap(buf2))
  168. }
  169. }
  170. func TestIsPowerOfTwo(t *testing.T) {
  171. for i := 0; i < 16; i++ {
  172. cap := 1 << i
  173. if !isPowerOfTwo(cap) {
  174. t.Fatalf("Failed at: i=%d, cap=%d\n", i, cap)
  175. }
  176. }
  177. for _, v := range []int{5, 17, 19, 31, 55} {
  178. if isPowerOfTwo(v) {
  179. t.Fatalf("Unexpected isPowerOfTwo: %d", v)
  180. }
  181. }
  182. }
  183. func TestPutNonPowerOfTwoCapIsDiscarded(t *testing.T) {
  184. // Buffers with non-power-of-two capacities (e.g. from outside the pool)
  185. // get silently dropped. Confirm no panic and pool still works after.
  186. bp := newBufferPool()
  187. value := make([]byte, 0, 17)
  188. bp.Put(value)
  189. buf := bp.Get(24)
  190. if len(buf) != 24 {
  191. t.Errorf("Get(24) after spurious Put: len = %d, want 24", len(buf))
  192. }
  193. if cap(buf) != 32 {
  194. t.Errorf("Get(24) after spurious Put: cap = %d, want 32", cap(buf))
  195. }
  196. }
  197. // --- Concurrency ---
  198. func TestConcurrentGetPut(t *testing.T) {
  199. bp := newBufferPool()
  200. var wg sync.WaitGroup
  201. const goroutines = 64
  202. const iters = 1000
  203. for g := 0; g < goroutines; g++ {
  204. wg.Add(1)
  205. go func(id int) {
  206. defer wg.Done()
  207. for i := 0; i < iters; i++ {
  208. n := (id*iters + i) % 4096
  209. if n == 0 {
  210. n = 1
  211. }
  212. buf := bp.Get(n)
  213. if len(buf) != n {
  214. t.Errorf("concurrent Get(%d): len = %d", n, len(buf))
  215. }
  216. // Write to every byte to catch races under -race.
  217. for j := range buf {
  218. buf[j] = byte(j)
  219. }
  220. bp.Put(buf)
  221. }
  222. }(g)
  223. }
  224. wg.Wait()
  225. }
  226. // --- Edge cases at pool boundaries ---
  227. func TestGetExactPowerOfTwo(t *testing.T) {
  228. // Exact powers of two are the boundary between two pools; confirm correct
  229. // bucket selection and full round-trip for each.
  230. bp := newBufferPool()
  231. for i := 0; i < 17; i++ {
  232. n := 1 << i
  233. buf := bp.Get(n)
  234. if len(buf) != n {
  235. t.Errorf("Get(1<<%d=%d): len = %d", i, n, len(buf))
  236. }
  237. expectedCap := 1 << (bits.Len16(uint16(n - 1)))
  238. if cap(buf) != expectedCap {
  239. t.Errorf("Get(1<<%d=%d): cap = %d, want %d", i, n, cap(buf), expectedCap)
  240. }
  241. bp.Put(buf)
  242. }
  243. }
  244. func TestGetMaxInt16(t *testing.T) {
  245. i := poolIndex(math.MaxUint16)
  246. if i >= 17 {
  247. t.Errorf("poolIndex(MaxUint16) = %d, overflows pool array", i)
  248. }
  249. }
  250. // --- Benchmarks ---
  251. func BenchmarkGetPut(b *testing.B) {
  252. sizes := []int{64, 512, 4096, 65535}
  253. for _, size := range sizes {
  254. bp := newBufferPool()
  255. b.Run("", func(b *testing.B) {
  256. b.ReportAllocs()
  257. for i := 0; i < b.N; i++ {
  258. buf := bp.Get(size)
  259. bp.Put(buf)
  260. }
  261. })
  262. }
  263. }
  264. func BenchmarkGetPutParallel(b *testing.B) {
  265. bp := newBufferPool()
  266. b.ReportAllocs()
  267. b.RunParallel(func(pb *testing.PB) {
  268. for pb.Next() {
  269. buf := bp.Get(4096)
  270. bp.Put(buf)
  271. }
  272. })
  273. }