freelist_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package bbolt
  2. import (
  3. "math/rand"
  4. "reflect"
  5. "sort"
  6. "testing"
  7. "unsafe"
  8. )
  9. // Ensure that a page is added to a transaction's freelist.
  10. func TestFreelist_free(t *testing.T) {
  11. f := newFreelist()
  12. f.free(100, &page{id: 12})
  13. if !reflect.DeepEqual([]pgid{12}, f.pending[100].ids) {
  14. t.Fatalf("exp=%v; got=%v", []pgid{12}, f.pending[100])
  15. }
  16. }
  17. // Ensure that a page and its overflow is added to a transaction's freelist.
  18. func TestFreelist_free_overflow(t *testing.T) {
  19. f := newFreelist()
  20. f.free(100, &page{id: 12, overflow: 3})
  21. if exp := []pgid{12, 13, 14, 15}; !reflect.DeepEqual(exp, f.pending[100].ids) {
  22. t.Fatalf("exp=%v; got=%v", exp, f.pending[100])
  23. }
  24. }
  25. // Ensure that a transaction's free pages can be released.
  26. func TestFreelist_release(t *testing.T) {
  27. f := newFreelist()
  28. f.free(100, &page{id: 12, overflow: 1})
  29. f.free(100, &page{id: 9})
  30. f.free(102, &page{id: 39})
  31. f.release(100)
  32. f.release(101)
  33. if exp := []pgid{9, 12, 13}; !reflect.DeepEqual(exp, f.ids) {
  34. t.Fatalf("exp=%v; got=%v", exp, f.ids)
  35. }
  36. f.release(102)
  37. if exp := []pgid{9, 12, 13, 39}; !reflect.DeepEqual(exp, f.ids) {
  38. t.Fatalf("exp=%v; got=%v", exp, f.ids)
  39. }
  40. }
  41. // Ensure that releaseRange handles boundary conditions correctly
  42. func TestFreelist_releaseRange(t *testing.T) {
  43. type testRange struct {
  44. begin, end txid
  45. }
  46. type testPage struct {
  47. id pgid
  48. n int
  49. allocTxn txid
  50. freeTxn txid
  51. }
  52. var releaseRangeTests = []struct {
  53. title string
  54. pagesIn []testPage
  55. releaseRanges []testRange
  56. wantFree []pgid
  57. }{
  58. {
  59. title: "Single pending in range",
  60. pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
  61. releaseRanges: []testRange{{1, 300}},
  62. wantFree: []pgid{3},
  63. },
  64. {
  65. title: "Single pending with minimum end range",
  66. pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
  67. releaseRanges: []testRange{{1, 200}},
  68. wantFree: []pgid{3},
  69. },
  70. {
  71. title: "Single pending outsize minimum end range",
  72. pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
  73. releaseRanges: []testRange{{1, 199}},
  74. wantFree: nil,
  75. },
  76. {
  77. title: "Single pending with minimum begin range",
  78. pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
  79. releaseRanges: []testRange{{100, 300}},
  80. wantFree: []pgid{3},
  81. },
  82. {
  83. title: "Single pending outside minimum begin range",
  84. pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
  85. releaseRanges: []testRange{{101, 300}},
  86. wantFree: nil,
  87. },
  88. {
  89. title: "Single pending in minimum range",
  90. pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
  91. releaseRanges: []testRange{{199, 200}},
  92. wantFree: []pgid{3},
  93. },
  94. {
  95. title: "Single pending and read transaction at 199",
  96. pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
  97. releaseRanges: []testRange{{100, 198}, {200, 300}},
  98. wantFree: nil,
  99. },
  100. {
  101. title: "Adjacent pending and read transactions at 199, 200",
  102. pagesIn: []testPage{
  103. {id: 3, n: 1, allocTxn: 199, freeTxn: 200},
  104. {id: 4, n: 1, allocTxn: 200, freeTxn: 201},
  105. },
  106. releaseRanges: []testRange{
  107. {100, 198},
  108. {200, 199}, // Simulate the ranges db.freePages might produce.
  109. {201, 300},
  110. },
  111. wantFree: nil,
  112. },
  113. {
  114. title: "Out of order ranges",
  115. pagesIn: []testPage{
  116. {id: 3, n: 1, allocTxn: 199, freeTxn: 200},
  117. {id: 4, n: 1, allocTxn: 200, freeTxn: 201},
  118. },
  119. releaseRanges: []testRange{
  120. {201, 199},
  121. {201, 200},
  122. {200, 200},
  123. },
  124. wantFree: nil,
  125. },
  126. {
  127. title: "Multiple pending, read transaction at 150",
  128. pagesIn: []testPage{
  129. {id: 3, n: 1, allocTxn: 100, freeTxn: 200},
  130. {id: 4, n: 1, allocTxn: 100, freeTxn: 125},
  131. {id: 5, n: 1, allocTxn: 125, freeTxn: 150},
  132. {id: 6, n: 1, allocTxn: 125, freeTxn: 175},
  133. {id: 7, n: 2, allocTxn: 150, freeTxn: 175},
  134. {id: 9, n: 2, allocTxn: 175, freeTxn: 200},
  135. },
  136. releaseRanges: []testRange{{50, 149}, {151, 300}},
  137. wantFree: []pgid{4, 9},
  138. },
  139. }
  140. for _, c := range releaseRangeTests {
  141. f := newFreelist()
  142. for _, p := range c.pagesIn {
  143. for i := uint64(0); i < uint64(p.n); i++ {
  144. f.ids = append(f.ids, pgid(uint64(p.id)+i))
  145. }
  146. }
  147. for _, p := range c.pagesIn {
  148. f.allocate(p.allocTxn, p.n)
  149. }
  150. for _, p := range c.pagesIn {
  151. f.free(p.freeTxn, &page{id: p.id})
  152. }
  153. for _, r := range c.releaseRanges {
  154. f.releaseRange(r.begin, r.end)
  155. }
  156. if exp := c.wantFree; !reflect.DeepEqual(exp, f.ids) {
  157. t.Errorf("exp=%v; got=%v for %s", exp, f.ids, c.title)
  158. }
  159. }
  160. }
  161. // Ensure that a freelist can find contiguous blocks of pages.
  162. func TestFreelist_allocate(t *testing.T) {
  163. f := newFreelist()
  164. f.ids = []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}
  165. if id := int(f.allocate(1, 3)); id != 3 {
  166. t.Fatalf("exp=3; got=%v", id)
  167. }
  168. if id := int(f.allocate(1, 1)); id != 6 {
  169. t.Fatalf("exp=6; got=%v", id)
  170. }
  171. if id := int(f.allocate(1, 3)); id != 0 {
  172. t.Fatalf("exp=0; got=%v", id)
  173. }
  174. if id := int(f.allocate(1, 2)); id != 12 {
  175. t.Fatalf("exp=12; got=%v", id)
  176. }
  177. if id := int(f.allocate(1, 1)); id != 7 {
  178. t.Fatalf("exp=7; got=%v", id)
  179. }
  180. if id := int(f.allocate(1, 0)); id != 0 {
  181. t.Fatalf("exp=0; got=%v", id)
  182. }
  183. if id := int(f.allocate(1, 0)); id != 0 {
  184. t.Fatalf("exp=0; got=%v", id)
  185. }
  186. if exp := []pgid{9, 18}; !reflect.DeepEqual(exp, f.ids) {
  187. t.Fatalf("exp=%v; got=%v", exp, f.ids)
  188. }
  189. if id := int(f.allocate(1, 1)); id != 9 {
  190. t.Fatalf("exp=9; got=%v", id)
  191. }
  192. if id := int(f.allocate(1, 1)); id != 18 {
  193. t.Fatalf("exp=18; got=%v", id)
  194. }
  195. if id := int(f.allocate(1, 1)); id != 0 {
  196. t.Fatalf("exp=0; got=%v", id)
  197. }
  198. if exp := []pgid{}; !reflect.DeepEqual(exp, f.ids) {
  199. t.Fatalf("exp=%v; got=%v", exp, f.ids)
  200. }
  201. }
  202. // Ensure that a freelist can deserialize from a freelist page.
  203. func TestFreelist_read(t *testing.T) {
  204. // Create a page.
  205. var buf [4096]byte
  206. page := (*page)(unsafe.Pointer(&buf[0]))
  207. page.flags = freelistPageFlag
  208. page.count = 2
  209. // Insert 2 page ids.
  210. ids := (*[3]pgid)(unsafe.Pointer(&page.ptr))
  211. ids[0] = 23
  212. ids[1] = 50
  213. // Deserialize page into a freelist.
  214. f := newFreelist()
  215. f.read(page)
  216. // Ensure that there are two page ids in the freelist.
  217. if exp := []pgid{23, 50}; !reflect.DeepEqual(exp, f.ids) {
  218. t.Fatalf("exp=%v; got=%v", exp, f.ids)
  219. }
  220. }
  221. // Ensure that a freelist can serialize into a freelist page.
  222. func TestFreelist_write(t *testing.T) {
  223. // Create a freelist and write it to a page.
  224. var buf [4096]byte
  225. f := &freelist{ids: []pgid{12, 39}, pending: make(map[txid]*txPending)}
  226. f.pending[100] = &txPending{ids: []pgid{28, 11}}
  227. f.pending[101] = &txPending{ids: []pgid{3}}
  228. p := (*page)(unsafe.Pointer(&buf[0]))
  229. if err := f.write(p); err != nil {
  230. t.Fatal(err)
  231. }
  232. // Read the page back out.
  233. f2 := newFreelist()
  234. f2.read(p)
  235. // Ensure that the freelist is correct.
  236. // All pages should be present and in reverse order.
  237. if exp := []pgid{3, 11, 12, 28, 39}; !reflect.DeepEqual(exp, f2.ids) {
  238. t.Fatalf("exp=%v; got=%v", exp, f2.ids)
  239. }
  240. }
  241. func Benchmark_FreelistRelease10K(b *testing.B) { benchmark_FreelistRelease(b, 10000) }
  242. func Benchmark_FreelistRelease100K(b *testing.B) { benchmark_FreelistRelease(b, 100000) }
  243. func Benchmark_FreelistRelease1000K(b *testing.B) { benchmark_FreelistRelease(b, 1000000) }
  244. func Benchmark_FreelistRelease10000K(b *testing.B) { benchmark_FreelistRelease(b, 10000000) }
  245. func benchmark_FreelistRelease(b *testing.B, size int) {
  246. ids := randomPgids(size)
  247. pending := randomPgids(len(ids) / 400)
  248. b.ResetTimer()
  249. for i := 0; i < b.N; i++ {
  250. txp := &txPending{ids: pending}
  251. f := &freelist{ids: ids, pending: map[txid]*txPending{1: txp}}
  252. f.release(1)
  253. }
  254. }
  255. func randomPgids(n int) []pgid {
  256. rand.Seed(42)
  257. pgids := make(pgids, n)
  258. for i := range pgids {
  259. pgids[i] = pgid(rand.Int63())
  260. }
  261. sort.Sort(pgids)
  262. return pgids
  263. }