freezer_table_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. // Copyright 2019 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package rawdb
  17. import (
  18. "bytes"
  19. "encoding/binary"
  20. "fmt"
  21. "io/ioutil"
  22. "math/rand"
  23. "os"
  24. "path/filepath"
  25. "sync"
  26. "testing"
  27. "time"
  28. "github.com/ethereum/go-ethereum/metrics"
  29. )
  30. func init() {
  31. rand.Seed(time.Now().Unix())
  32. }
  33. // Gets a chunk of data, filled with 'b'
  34. func getChunk(size int, b int) []byte {
  35. data := make([]byte, size)
  36. for i := range data {
  37. data[i] = byte(b)
  38. }
  39. return data
  40. }
  41. // TestFreezerBasics test initializing a freezertable from scratch, writing to the table,
  42. // and reading it back.
  43. func TestFreezerBasics(t *testing.T) {
  44. t.Parallel()
  45. // set cutoff at 50 bytes
  46. f, err := newCustomTable(os.TempDir(),
  47. fmt.Sprintf("unittest-%d", rand.Uint64()),
  48. metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 50, true)
  49. if err != nil {
  50. t.Fatal(err)
  51. }
  52. defer f.Close()
  53. // Write 15 bytes 255 times, results in 85 files
  54. for x := 0; x < 255; x++ {
  55. data := getChunk(15, x)
  56. f.Append(uint64(x), data)
  57. }
  58. //print(t, f, 0)
  59. //print(t, f, 1)
  60. //print(t, f, 2)
  61. //
  62. //db[0] = 000000000000000000000000000000
  63. //db[1] = 010101010101010101010101010101
  64. //db[2] = 020202020202020202020202020202
  65. for y := 0; y < 255; y++ {
  66. exp := getChunk(15, y)
  67. got, err := f.Retrieve(uint64(y))
  68. if err != nil {
  69. t.Fatal(err)
  70. }
  71. if !bytes.Equal(got, exp) {
  72. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  73. }
  74. }
  75. // Check that we cannot read too far
  76. _, err = f.Retrieve(uint64(255))
  77. if err != errOutOfBounds {
  78. t.Fatal(err)
  79. }
  80. }
  81. // TestFreezerBasicsClosing tests same as TestFreezerBasics, but also closes and reopens the freezer between
  82. // every operation
  83. func TestFreezerBasicsClosing(t *testing.T) {
  84. t.Parallel()
  85. // set cutoff at 50 bytes
  86. var (
  87. fname = fmt.Sprintf("basics-close-%d", rand.Uint64())
  88. rm, wm, sg = metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  89. f *freezerTable
  90. err error
  91. )
  92. f, err = newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  93. if err != nil {
  94. t.Fatal(err)
  95. }
  96. // Write 15 bytes 255 times, results in 85 files
  97. for x := 0; x < 255; x++ {
  98. data := getChunk(15, x)
  99. f.Append(uint64(x), data)
  100. f.Close()
  101. f, err = newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  102. if err != nil {
  103. t.Fatal(err)
  104. }
  105. }
  106. defer f.Close()
  107. for y := 0; y < 255; y++ {
  108. exp := getChunk(15, y)
  109. got, err := f.Retrieve(uint64(y))
  110. if err != nil {
  111. t.Fatal(err)
  112. }
  113. if !bytes.Equal(got, exp) {
  114. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  115. }
  116. f.Close()
  117. f, err = newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  118. if err != nil {
  119. t.Fatal(err)
  120. }
  121. }
  122. }
  123. // TestFreezerRepairDanglingHead tests that we can recover if index entries are removed
  124. func TestFreezerRepairDanglingHead(t *testing.T) {
  125. t.Parallel()
  126. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  127. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  128. { // Fill table
  129. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  130. if err != nil {
  131. t.Fatal(err)
  132. }
  133. // Write 15 bytes 255 times
  134. for x := 0; x < 255; x++ {
  135. data := getChunk(15, x)
  136. f.Append(uint64(x), data)
  137. }
  138. // The last item should be there
  139. if _, err = f.Retrieve(0xfe); err != nil {
  140. t.Fatal(err)
  141. }
  142. f.Close()
  143. }
  144. // open the index
  145. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  146. if err != nil {
  147. t.Fatalf("Failed to open index file: %v", err)
  148. }
  149. // Remove 4 bytes
  150. stat, err := idxFile.Stat()
  151. if err != nil {
  152. t.Fatalf("Failed to stat index file: %v", err)
  153. }
  154. idxFile.Truncate(stat.Size() - 4)
  155. idxFile.Close()
  156. // Now open it again
  157. {
  158. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  159. if err != nil {
  160. t.Fatal(err)
  161. }
  162. // The last item should be missing
  163. if _, err = f.Retrieve(0xff); err == nil {
  164. t.Errorf("Expected error for missing index entry")
  165. }
  166. // The one before should still be there
  167. if _, err = f.Retrieve(0xfd); err != nil {
  168. t.Fatalf("Expected no error, got %v", err)
  169. }
  170. }
  171. }
  172. // TestFreezerRepairDanglingHeadLarge tests that we can recover if very many index entries are removed
  173. func TestFreezerRepairDanglingHeadLarge(t *testing.T) {
  174. t.Parallel()
  175. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  176. fname := fmt.Sprintf("dangling_headtest-%d", rand.Uint64())
  177. { // Fill a table and close it
  178. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  179. if err != nil {
  180. t.Fatal(err)
  181. }
  182. // Write 15 bytes 255 times
  183. for x := 0; x < 0xff; x++ {
  184. data := getChunk(15, x)
  185. f.Append(uint64(x), data)
  186. }
  187. // The last item should be there
  188. if _, err = f.Retrieve(f.items - 1); err != nil {
  189. t.Fatal(err)
  190. }
  191. f.Close()
  192. }
  193. // open the index
  194. idxFile, err := os.OpenFile(filepath.Join(os.TempDir(), fmt.Sprintf("%s.ridx", fname)), os.O_RDWR, 0644)
  195. if err != nil {
  196. t.Fatalf("Failed to open index file: %v", err)
  197. }
  198. // Remove everything but the first item, and leave data unaligned
  199. // 0-indexEntry, 1-indexEntry, corrupt-indexEntry
  200. idxFile.Truncate(indexEntrySize + indexEntrySize + indexEntrySize/2)
  201. idxFile.Close()
  202. // Now open it again
  203. {
  204. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  205. if err != nil {
  206. t.Fatal(err)
  207. }
  208. // The first item should be there
  209. if _, err = f.Retrieve(0); err != nil {
  210. t.Fatal(err)
  211. }
  212. // The second item should be missing
  213. if _, err = f.Retrieve(1); err == nil {
  214. t.Errorf("Expected error for missing index entry")
  215. }
  216. // We should now be able to store items again, from item = 1
  217. for x := 1; x < 0xff; x++ {
  218. data := getChunk(15, ^x)
  219. f.Append(uint64(x), data)
  220. }
  221. f.Close()
  222. }
  223. // And if we open it, we should now be able to read all of them (new values)
  224. {
  225. f, _ := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  226. for y := 1; y < 255; y++ {
  227. exp := getChunk(15, ^y)
  228. got, err := f.Retrieve(uint64(y))
  229. if err != nil {
  230. t.Fatal(err)
  231. }
  232. if !bytes.Equal(got, exp) {
  233. t.Fatalf("test %d, got \n%x != \n%x", y, got, exp)
  234. }
  235. }
  236. }
  237. }
  238. // TestSnappyDetection tests that we fail to open a snappy database and vice versa
  239. func TestSnappyDetection(t *testing.T) {
  240. t.Parallel()
  241. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  242. fname := fmt.Sprintf("snappytest-%d", rand.Uint64())
  243. // Open with snappy
  244. {
  245. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  246. if err != nil {
  247. t.Fatal(err)
  248. }
  249. // Write 15 bytes 255 times
  250. for x := 0; x < 0xff; x++ {
  251. data := getChunk(15, x)
  252. f.Append(uint64(x), data)
  253. }
  254. f.Close()
  255. }
  256. // Open without snappy
  257. {
  258. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, false)
  259. if err != nil {
  260. t.Fatal(err)
  261. }
  262. if _, err = f.Retrieve(0); err == nil {
  263. f.Close()
  264. t.Fatalf("expected empty table")
  265. }
  266. }
  267. // Open with snappy
  268. {
  269. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  270. if err != nil {
  271. t.Fatal(err)
  272. }
  273. // There should be 255 items
  274. if _, err = f.Retrieve(0xfe); err != nil {
  275. f.Close()
  276. t.Fatalf("expected no error, got %v", err)
  277. }
  278. }
  279. }
  280. func assertFileSize(f string, size int64) error {
  281. stat, err := os.Stat(f)
  282. if err != nil {
  283. return err
  284. }
  285. if stat.Size() != size {
  286. return fmt.Errorf("error, expected size %d, got %d", size, stat.Size())
  287. }
  288. return nil
  289. }
  290. // TestFreezerRepairDanglingIndex checks that if the index has more entries than there are data,
  291. // the index is repaired
  292. func TestFreezerRepairDanglingIndex(t *testing.T) {
  293. t.Parallel()
  294. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  295. fname := fmt.Sprintf("dangling_indextest-%d", rand.Uint64())
  296. { // Fill a table and close it
  297. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  298. if err != nil {
  299. t.Fatal(err)
  300. }
  301. // Write 15 bytes 9 times : 150 bytes
  302. for x := 0; x < 9; x++ {
  303. data := getChunk(15, x)
  304. f.Append(uint64(x), data)
  305. }
  306. // The last item should be there
  307. if _, err = f.Retrieve(f.items - 1); err != nil {
  308. f.Close()
  309. t.Fatal(err)
  310. }
  311. f.Close()
  312. // File sizes should be 45, 45, 45 : items[3, 3, 3)
  313. }
  314. // Crop third file
  315. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0002.rdat", fname))
  316. // Truncate third file: 45 ,45, 20
  317. {
  318. if err := assertFileSize(fileToCrop, 45); err != nil {
  319. t.Fatal(err)
  320. }
  321. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  322. if err != nil {
  323. t.Fatal(err)
  324. }
  325. file.Truncate(20)
  326. file.Close()
  327. }
  328. // Open db it again
  329. // It should restore the file(s) to
  330. // 45, 45, 15
  331. // with 3+3+1 items
  332. {
  333. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  334. if err != nil {
  335. t.Fatal(err)
  336. }
  337. if f.items != 7 {
  338. f.Close()
  339. t.Fatalf("expected %d items, got %d", 7, f.items)
  340. }
  341. if err := assertFileSize(fileToCrop, 15); err != nil {
  342. t.Fatal(err)
  343. }
  344. }
  345. }
  346. func TestFreezerTruncate(t *testing.T) {
  347. t.Parallel()
  348. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  349. fname := fmt.Sprintf("truncation-%d", rand.Uint64())
  350. { // Fill table
  351. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  352. if err != nil {
  353. t.Fatal(err)
  354. }
  355. // Write 15 bytes 30 times
  356. for x := 0; x < 30; x++ {
  357. data := getChunk(15, x)
  358. f.Append(uint64(x), data)
  359. }
  360. // The last item should be there
  361. if _, err = f.Retrieve(f.items - 1); err != nil {
  362. t.Fatal(err)
  363. }
  364. f.Close()
  365. }
  366. // Reopen, truncate
  367. {
  368. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  369. if err != nil {
  370. t.Fatal(err)
  371. }
  372. defer f.Close()
  373. f.truncate(10) // 150 bytes
  374. if f.items != 10 {
  375. t.Fatalf("expected %d items, got %d", 10, f.items)
  376. }
  377. // 45, 45, 45, 15 -- bytes should be 15
  378. if f.headBytes != 15 {
  379. t.Fatalf("expected %d bytes, got %d", 15, f.headBytes)
  380. }
  381. }
  382. }
  383. // TestFreezerRepairFirstFile tests a head file with the very first item only half-written.
  384. // That will rewind the index, and _should_ truncate the head file
  385. func TestFreezerRepairFirstFile(t *testing.T) {
  386. t.Parallel()
  387. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  388. fname := fmt.Sprintf("truncationfirst-%d", rand.Uint64())
  389. { // Fill table
  390. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  391. if err != nil {
  392. t.Fatal(err)
  393. }
  394. // Write 80 bytes, splitting out into two files
  395. f.Append(0, getChunk(40, 0xFF))
  396. f.Append(1, getChunk(40, 0xEE))
  397. // The last item should be there
  398. if _, err = f.Retrieve(f.items - 1); err != nil {
  399. t.Fatal(err)
  400. }
  401. f.Close()
  402. }
  403. // Truncate the file in half
  404. fileToCrop := filepath.Join(os.TempDir(), fmt.Sprintf("%s.0001.rdat", fname))
  405. {
  406. if err := assertFileSize(fileToCrop, 40); err != nil {
  407. t.Fatal(err)
  408. }
  409. file, err := os.OpenFile(fileToCrop, os.O_RDWR, 0644)
  410. if err != nil {
  411. t.Fatal(err)
  412. }
  413. file.Truncate(20)
  414. file.Close()
  415. }
  416. // Reopen
  417. {
  418. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  419. if err != nil {
  420. t.Fatal(err)
  421. }
  422. if f.items != 1 {
  423. f.Close()
  424. t.Fatalf("expected %d items, got %d", 0, f.items)
  425. }
  426. // Write 40 bytes
  427. f.Append(1, getChunk(40, 0xDD))
  428. f.Close()
  429. // Should have been truncated down to zero and then 40 written
  430. if err := assertFileSize(fileToCrop, 40); err != nil {
  431. t.Fatal(err)
  432. }
  433. }
  434. }
  435. // TestFreezerReadAndTruncate tests:
  436. // - we have a table open
  437. // - do some reads, so files are open in readonly
  438. // - truncate so those files are 'removed'
  439. // - check that we did not keep the rdonly file descriptors
  440. func TestFreezerReadAndTruncate(t *testing.T) {
  441. t.Parallel()
  442. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  443. fname := fmt.Sprintf("read_truncate-%d", rand.Uint64())
  444. { // Fill table
  445. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  446. if err != nil {
  447. t.Fatal(err)
  448. }
  449. // Write 15 bytes 30 times
  450. for x := 0; x < 30; x++ {
  451. data := getChunk(15, x)
  452. f.Append(uint64(x), data)
  453. }
  454. // The last item should be there
  455. if _, err = f.Retrieve(f.items - 1); err != nil {
  456. t.Fatal(err)
  457. }
  458. f.Close()
  459. }
  460. // Reopen and read all files
  461. {
  462. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
  463. if err != nil {
  464. t.Fatal(err)
  465. }
  466. if f.items != 30 {
  467. f.Close()
  468. t.Fatalf("expected %d items, got %d", 0, f.items)
  469. }
  470. for y := byte(0); y < 30; y++ {
  471. f.Retrieve(uint64(y))
  472. }
  473. // Now, truncate back to zero
  474. f.truncate(0)
  475. // Write the data again
  476. for x := 0; x < 30; x++ {
  477. data := getChunk(15, ^x)
  478. if err := f.Append(uint64(x), data); err != nil {
  479. t.Fatalf("error %v", err)
  480. }
  481. }
  482. f.Close()
  483. }
  484. }
  485. func TestOffset(t *testing.T) {
  486. t.Parallel()
  487. rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
  488. fname := fmt.Sprintf("offset-%d", rand.Uint64())
  489. { // Fill table
  490. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
  491. if err != nil {
  492. t.Fatal(err)
  493. }
  494. // Write 6 x 20 bytes, splitting out into three files
  495. f.Append(0, getChunk(20, 0xFF))
  496. f.Append(1, getChunk(20, 0xEE))
  497. f.Append(2, getChunk(20, 0xdd))
  498. f.Append(3, getChunk(20, 0xcc))
  499. f.Append(4, getChunk(20, 0xbb))
  500. f.Append(5, getChunk(20, 0xaa))
  501. f.DumpIndex(0, 100)
  502. f.Close()
  503. }
  504. // Now crop it.
  505. {
  506. // delete files 0 and 1
  507. for i := 0; i < 2; i++ {
  508. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.%04d.rdat", fname, i))
  509. if err := os.Remove(p); err != nil {
  510. t.Fatal(err)
  511. }
  512. }
  513. // Read the index file
  514. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname))
  515. indexFile, err := os.OpenFile(p, os.O_RDWR, 0644)
  516. if err != nil {
  517. t.Fatal(err)
  518. }
  519. indexBuf := make([]byte, 7*indexEntrySize)
  520. indexFile.Read(indexBuf)
  521. // Update the index file, so that we store
  522. // [ file = 2, offset = 4 ] at index zero
  523. tailId := uint32(2) // First file is 2
  524. itemOffset := uint32(4) // We have removed four items
  525. zeroIndex := indexEntry{
  526. filenum: tailId,
  527. offset: itemOffset,
  528. }
  529. buf := zeroIndex.marshallBinary()
  530. // Overwrite index zero
  531. copy(indexBuf, buf)
  532. // Remove the four next indices by overwriting
  533. copy(indexBuf[indexEntrySize:], indexBuf[indexEntrySize*5:])
  534. indexFile.WriteAt(indexBuf, 0)
  535. // Need to truncate the moved index items
  536. indexFile.Truncate(indexEntrySize * (1 + 2))
  537. indexFile.Close()
  538. }
  539. // Now open again
  540. checkPresent := func(numDeleted uint64) {
  541. f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
  542. if err != nil {
  543. t.Fatal(err)
  544. }
  545. f.DumpIndex(0, 100)
  546. // It should allow writing item 6
  547. f.Append(numDeleted+2, getChunk(20, 0x99))
  548. // It should be fine to fetch 4,5,6
  549. if got, err := f.Retrieve(numDeleted); err != nil {
  550. t.Fatal(err)
  551. } else if exp := getChunk(20, 0xbb); !bytes.Equal(got, exp) {
  552. t.Fatalf("expected %x got %x", exp, got)
  553. }
  554. if got, err := f.Retrieve(numDeleted + 1); err != nil {
  555. t.Fatal(err)
  556. } else if exp := getChunk(20, 0xaa); !bytes.Equal(got, exp) {
  557. t.Fatalf("expected %x got %x", exp, got)
  558. }
  559. if got, err := f.Retrieve(numDeleted + 2); err != nil {
  560. t.Fatal(err)
  561. } else if exp := getChunk(20, 0x99); !bytes.Equal(got, exp) {
  562. t.Fatalf("expected %x got %x", exp, got)
  563. }
  564. // It should error at 0, 1,2,3
  565. for i := numDeleted - 1; i > numDeleted-10; i-- {
  566. if _, err := f.Retrieve(i); err == nil {
  567. t.Fatal("expected err")
  568. }
  569. }
  570. }
  571. checkPresent(4)
  572. // Now, let's pretend we have deleted 1M items
  573. {
  574. // Read the index file
  575. p := filepath.Join(os.TempDir(), fmt.Sprintf("%v.ridx", fname))
  576. indexFile, err := os.OpenFile(p, os.O_RDWR, 0644)
  577. if err != nil {
  578. t.Fatal(err)
  579. }
  580. indexBuf := make([]byte, 3*indexEntrySize)
  581. indexFile.Read(indexBuf)
  582. // Update the index file, so that we store
  583. // [ file = 2, offset = 1M ] at index zero
  584. tailId := uint32(2) // First file is 2
  585. itemOffset := uint32(1000000) // We have removed 1M items
  586. zeroIndex := indexEntry{
  587. offset: itemOffset,
  588. filenum: tailId,
  589. }
  590. buf := zeroIndex.marshallBinary()
  591. // Overwrite index zero
  592. copy(indexBuf, buf)
  593. indexFile.WriteAt(indexBuf, 0)
  594. indexFile.Close()
  595. }
  596. checkPresent(1000000)
  597. }
  598. // TODO (?)
  599. // - test that if we remove several head-files, aswell as data last data-file,
  600. // the index is truncated accordingly
  601. // Right now, the freezer would fail on these conditions:
  602. // 1. have data files d0, d1, d2, d3
  603. // 2. remove d2,d3
  604. //
  605. // However, all 'normal' failure modes arising due to failing to sync() or save a file
  606. // should be handled already, and the case described above can only (?) happen if an
  607. // external process/user deletes files from the filesystem.
  608. // TestAppendTruncateParallel is a test to check if the Append/truncate operations are
  609. // racy.
  610. //
  611. // The reason why it's not a regular fuzzer, within tests/fuzzers, is that it is dependent
  612. // on timing rather than 'clever' input -- there's no determinism.
  613. func TestAppendTruncateParallel(t *testing.T) {
  614. dir, err := ioutil.TempDir("", "freezer")
  615. if err != nil {
  616. t.Fatal(err)
  617. }
  618. defer os.RemoveAll(dir)
  619. f, err := newCustomTable(dir, "tmp", metrics.NilMeter{}, metrics.NilMeter{}, metrics.NilGauge{}, 8, true)
  620. if err != nil {
  621. t.Fatal(err)
  622. }
  623. fill := func(mark uint64) []byte {
  624. data := make([]byte, 8)
  625. binary.LittleEndian.PutUint64(data, mark)
  626. return data
  627. }
  628. for i := 0; i < 5000; i++ {
  629. f.truncate(0)
  630. data0 := fill(0)
  631. f.Append(0, data0)
  632. data1 := fill(1)
  633. var wg sync.WaitGroup
  634. wg.Add(2)
  635. go func() {
  636. f.truncate(0)
  637. wg.Done()
  638. }()
  639. go func() {
  640. f.Append(1, data1)
  641. wg.Done()
  642. }()
  643. wg.Wait()
  644. if have, err := f.Retrieve(0); err == nil {
  645. if !bytes.Equal(have, data0) {
  646. t.Fatalf("have %x want %x", have, data0)
  647. }
  648. }
  649. }
  650. }