github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/bitree/bdb/db_test.go (about) 1 // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package bdb_test 16 17 import ( 18 "bytes" 19 "encoding/binary" 20 "errors" 21 "flag" 22 "fmt" 23 "hash/fnv" 24 "io/ioutil" 25 "log" 26 "math/rand" 27 "os" 28 "path" 29 "path/filepath" 30 "regexp" 31 "strconv" 32 "sync" 33 "testing" 34 "time" 35 "unsafe" 36 37 "github.com/zuoyebang/bitalosdb/bitree/bdb" 38 "github.com/zuoyebang/bitalosdb/internal/consts" 39 "github.com/zuoyebang/bitalosdb/internal/options" 40 ) 41 42 var statsFlag = flag.Bool("stats", false, "show performance stats") 43 44 const pageSize = 4096 45 46 const pageHeaderSize = 16 47 48 type meta struct { 49 magic uint32 50 version uint32 51 _ uint32 52 _ uint32 53 _ [16]byte 54 _ uint64 55 pgid uint64 56 _ uint64 57 checksum uint64 58 } 59 60 func TestOpen(t *testing.T) { 61 path := tempfile() 62 defer os.RemoveAll(path) 63 64 db, err := bdb.Open(path, nil) 65 if err != nil { 66 t.Fatal(err) 67 } else if db == nil { 68 t.Fatal("expected db") 69 } 70 71 if s := db.Path(); s != path { 72 t.Fatalf("unexpected path: %s", s) 73 } 74 75 if err := db.Close(); err != nil { 76 t.Fatal(err) 77 } 78 } 79 80 func TestOpen_MultipleGoroutines(t *testing.T) { 81 if testing.Short() { 82 t.Skip("skipping test in short mode") 83 } 84 85 const ( 86 instances = 30 87 iterations = 30 88 ) 89 path := tempfile() 90 defer os.RemoveAll(path) 91 var wg sync.WaitGroup 92 errCh := make(chan error, iterations*instances) 93 for iteration := 0; iteration < iterations; iteration++ { 94 for instance := 0; instance < instances; instance++ { 95 wg.Add(1) 96 go func() { 97 defer wg.Done() 98 db, err := bdb.Open(path, nil) 99 if err != nil { 100 errCh <- err 101 return 102 } 103 if err := db.Close(); err != nil { 104 errCh <- err 105 return 106 } 107 }() 108 } 109 wg.Wait() 110 } 111 close(errCh) 112 for err := range errCh { 113 if err != nil { 114 t.Fatalf("error from inside goroutine: %v", err) 115 } 116 } 117 } 118 119 func TestOpen_ErrPathRequired(t *testing.T) { 120 _, err := bdb.Open("", nil) 121 if err == nil { 122 t.Fatalf("expected error") 123 } 124 } 125 126 func TestOpen_ErrNotExists(t *testing.T) { 127 _, err := bdb.Open(filepath.Join(tempfile(), "bad-path"), nil) 128 if err == nil { 129 t.Fatal("expected error") 130 } 131 } 132 133 func TestOpen_ErrInvalid(t *testing.T) { 134 path := tempfile() 135 defer os.RemoveAll(path) 136 137 f, err := os.Create(path) 138 if err != nil { 139 t.Fatal(err) 140 } 141 if _, err := fmt.Fprintln(f, "this is not a bdb database"); err != nil { 142 t.Fatal(err) 143 } 144 if err := f.Close(); err != nil { 145 t.Fatal(err) 146 } 147 148 if _, err := bdb.Open(path, nil); err != bdb.ErrInvalid { 149 t.Fatalf("unexpected error: %s", err) 150 } 151 } 152 153 func TestOpen_ErrVersionMismatch(t *testing.T) { 154 if pageSize != os.Getpagesize() { 155 t.Skip("page size mismatch") 156 } 157 158 db := MustOpenDB() 159 path := db.Path() 160 defer db.MustClose() 161 162 if err := db.DB.Close(); err != nil { 163 t.Fatal(err) 164 } 165 166 buf, err := os.ReadFile(path) 167 if err != nil { 168 t.Fatal(err) 169 } 170 171 meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize])) 172 meta0.version++ 173 meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize])) 174 meta1.version++ 175 if err := os.WriteFile(path, buf, 0666); err != nil { 176 t.Fatal(err) 177 } 178 179 if _, err := bdb.Open(path, nil); err != bdb.ErrVersionMismatch { 180 t.Fatalf("unexpected error: %s", err) 181 } 182 } 183 184 func TestOpen_ErrChecksum(t *testing.T) { 185 if pageSize != os.Getpagesize() { 186 t.Skip("page size mismatch") 187 } 188 189 db := MustOpenDB() 190 path := db.Path() 191 defer db.MustClose() 192 193 if err := db.DB.Close(); err != nil { 194 t.Fatal(err) 195 } 196 197 buf, err := ioutil.ReadFile(path) 198 if err != nil { 199 t.Fatal(err) 200 } 201 202 meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize])) 203 meta0.pgid++ 204 meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize])) 205 meta1.pgid++ 206 if err := ioutil.WriteFile(path, buf, 0666); err != nil { 207 t.Fatal(err) 208 } 209 210 if _, err := bdb.Open(path, nil); err != bdb.ErrChecksum { 211 t.Fatalf("unexpected error: %s", err) 212 } 213 } 214 215 func TestOpen_Size(t *testing.T) { 216 db := MustOpenDB() 217 path := db.Path() 218 defer db.MustClose() 219 220 pagesize := db.Info().PageSize 221 222 if err := db.Update(func(tx *bdb.Tx) error { 223 b, _ := tx.CreateBucketIfNotExists([]byte("data")) 224 for i := 0; i < 10000; i++ { 225 if err := b.Put([]byte(fmt.Sprintf("%04d", i)), make([]byte, 1000)); err != nil { 226 t.Fatal(err) 227 } 228 } 229 return nil 230 }); err != nil { 231 t.Fatal(err) 232 } 233 234 if err := db.DB.Close(); err != nil { 235 t.Fatal(err) 236 } 237 sz := fileSize(path) 238 if sz == 0 { 239 t.Fatalf("unexpected new file size: %d", sz) 240 } 241 242 db0, err := bdb.Open(path, nil) 243 if err != nil { 244 t.Fatal(err) 245 } 246 if err := db0.Update(func(tx *bdb.Tx) error { 247 if err := tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}); err != nil { 248 t.Fatal(err) 249 } 250 return nil 251 }); err != nil { 252 t.Fatal(err) 253 } 254 if err := db0.Close(); err != nil { 255 t.Fatal(err) 256 } 257 newSz := fileSize(path) 258 if newSz == 0 { 259 t.Fatalf("unexpected new file size: %d", newSz) 260 } 261 262 if sz < newSz-5*int64(pagesize) { 263 t.Fatalf("unexpected file growth: %d => %d", sz, newSz) 264 } 265 } 266 267 func No_TestOpen_Size_Large(t *testing.T) { 268 if testing.Short() { 269 t.Skip("short mode") 270 } 271 272 db := MustOpenDB() 273 path := db.Path() 274 defer db.MustClose() 275 276 pagesize := db.Info().PageSize 277 278 var index uint64 279 for i := 0; i < 10000; i++ { 280 if err := db.Update(func(tx *bdb.Tx) error { 281 b, _ := tx.CreateBucketIfNotExists([]byte("data")) 282 for j := 0; j < 1000; j++ { 283 if err := b.Put(u64tob(index), make([]byte, 50)); err != nil { 284 t.Fatal(err) 285 } 286 index++ 287 } 288 return nil 289 }); err != nil { 290 t.Fatal(err) 291 } 292 } 293 294 if err := db.DB.Close(); err != nil { 295 t.Fatal(err) 296 } 297 sz := fileSize(path) 298 if sz == 0 { 299 t.Fatalf("unexpected new file size: %d", sz) 300 } else if sz < (1 << 30) { 301 t.Fatalf("expected larger initial size: %d", sz) 302 } 303 304 db0, err := bdb.Open(path, nil) 305 if err != nil { 306 t.Fatal(err) 307 } 308 if err := db0.Update(func(tx *bdb.Tx) error { 309 return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}) 310 }); err != nil { 311 t.Fatal(err) 312 } 313 if err := db0.Close(); err != nil { 314 t.Fatal(err) 315 } 316 317 newSz := fileSize(path) 318 if newSz == 0 { 319 t.Fatalf("unexpected new file size: %d", newSz) 320 } 321 322 if sz < newSz-5*int64(pagesize) { 323 t.Fatalf("unexpected file growth: %d => %d", sz, newSz) 324 } 325 } 326 327 func TestOpen_Check(t *testing.T) { 328 path := tempfile() 329 defer os.RemoveAll(path) 330 331 db, err := bdb.Open(path, nil) 332 if err != nil { 333 t.Fatal(err) 334 } 335 if err = db.View(func(tx *bdb.Tx) error { return <-tx.Check() }); err != nil { 336 t.Fatal(err) 337 } 338 if err = db.Close(); err != nil { 339 t.Fatal(err) 340 } 341 342 db, err = bdb.Open(path, nil) 343 if err != nil { 344 t.Fatal(err) 345 } 346 if err := db.View(func(tx *bdb.Tx) error { return <-tx.Check() }); err != nil { 347 t.Fatal(err) 348 } 349 if err := db.Close(); err != nil { 350 t.Fatal(err) 351 } 352 } 353 354 func TestOpen_FileTooSmall(t *testing.T) { 355 path := tempfile() 356 defer os.RemoveAll(path) 357 358 db, err := bdb.Open(path, nil) 359 if err != nil { 360 t.Fatal(err) 361 } 362 pageSize := int64(db.Info().PageSize) 363 if err = db.Close(); err != nil { 364 t.Fatal(err) 365 } 366 367 if err = os.Truncate(path, pageSize); err != nil { 368 t.Fatal(err) 369 } 370 371 _, err = bdb.Open(path, nil) 372 if err == nil || err.Error() != "file size too small" { 373 t.Fatalf("unexpected error: %s", err) 374 } 375 } 376 377 func TestDB_Open_InitialMmapSize(t *testing.T) { 378 path := tempfile() 379 defer os.Remove(path) 380 381 testWriteSize := 1 << 27 382 383 opts := options.DefaultBdbOptions 384 opts.InitialMmapSize = 1 << 30 385 386 db, err := bdb.Open(path, opts) 387 if err != nil { 388 t.Fatal(err) 389 } 390 391 rtx, err := db.Begin(false) 392 if err != nil { 393 t.Fatal(err) 394 } 395 396 wtx, err := db.Begin(true) 397 if err != nil { 398 t.Fatal(err) 399 } 400 401 b, err := wtx.CreateBucket([]byte("test")) 402 if err != nil { 403 t.Fatal(err) 404 } 405 406 err = b.Put([]byte("foo"), make([]byte, testWriteSize)) 407 if err != nil { 408 t.Fatal(err) 409 } 410 411 done := make(chan error, 1) 412 413 go func() { 414 err := wtx.Commit() 415 done <- err 416 }() 417 418 select { 419 case <-time.After(5 * time.Second): 420 t.Errorf("unexpected that the reader blocks writer") 421 case err := <-done: 422 if err != nil { 423 t.Fatal(err) 424 } 425 } 426 427 if err := rtx.Rollback(); err != nil { 428 t.Fatal(err) 429 } 430 } 431 432 func TestDB_Open_ReadOnly(t *testing.T) { 433 db := MustOpenDB() 434 defer db.MustClose() 435 436 if err := db.Update(func(tx *bdb.Tx) error { 437 b, err := tx.CreateBucket([]byte("widgets")) 438 if err != nil { 439 t.Fatal(err) 440 } 441 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 442 t.Fatal(err) 443 } 444 return nil 445 }); err != nil { 446 t.Fatal(err) 447 } 448 if err := db.DB.Close(); err != nil { 449 t.Fatal(err) 450 } 451 452 f := db.f 453 o := &options.BdbOptions{ReadOnly: true} 454 readOnlyDB, err := bdb.Open(f, o) 455 if err != nil { 456 panic(err) 457 } 458 459 if !readOnlyDB.IsReadOnly() { 460 t.Fatal("expect db in read only mode") 461 } 462 463 if err := readOnlyDB.View(func(tx *bdb.Tx) error { 464 value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) 465 if !bytes.Equal(value, []byte("bar")) { 466 t.Fatal("expect value 'bar', got", value) 467 } 468 return nil 469 }); err != nil { 470 t.Fatal(err) 471 } 472 473 if _, err := readOnlyDB.Begin(true); err != bdb.ErrDatabaseReadOnly { 474 t.Fatalf("unexpected error: %s", err) 475 } 476 477 if err := readOnlyDB.Close(); err != nil { 478 t.Fatal(err) 479 } 480 } 481 482 func TestOpen_BigPage(t *testing.T) { 483 pz := os.Getpagesize() 484 485 opts := options.DefaultBdbOptions 486 opts.PageSize = pz * 2 487 db1 := MustOpenWithOption(opts) 488 defer db1.MustClose() 489 490 opts.PageSize = pz * 4 491 db2 := MustOpenWithOption(opts) 492 defer db2.MustClose() 493 494 if db1sz, db2sz := fileSize(db1.f), fileSize(db2.f); db1sz >= db2sz { 495 t.Errorf("expected %d < %d", db1sz, db2sz) 496 } 497 } 498 499 func TestOpen_RecoverFreeList(t *testing.T) { 500 opts := options.DefaultBdbOptions 501 opts.NoFreelistSync = true 502 db := MustOpenWithOption(opts) 503 defer db.MustClose() 504 505 tx, err := db.Begin(true) 506 if err != nil { 507 t.Fatal(err) 508 } 509 wbuf := make([]byte, 8192) 510 for i := 0; i < 100; i++ { 511 s := fmt.Sprintf("%d", i) 512 b, err := tx.CreateBucket([]byte(s)) 513 if err != nil { 514 t.Fatal(err) 515 } 516 if err = b.Put([]byte(s), wbuf); err != nil { 517 t.Fatal(err) 518 } 519 } 520 if err = tx.Commit(); err != nil { 521 t.Fatal(err) 522 } 523 524 if tx, err = db.Begin(true); err != nil { 525 t.Fatal(err) 526 } 527 for i := 0; i < 50; i++ { 528 s := fmt.Sprintf("%d", i) 529 b := tx.Bucket([]byte(s)) 530 if b == nil { 531 t.Fatal(err) 532 } 533 if err := b.Delete([]byte(s)); err != nil { 534 t.Fatal(err) 535 } 536 } 537 if err := tx.Commit(); err != nil { 538 t.Fatal(err) 539 } 540 if err := db.DB.Close(); err != nil { 541 t.Fatal(err) 542 } 543 544 db.MustReopen() 545 freepages := db.Stats().FreePageN 546 if freepages == 0 { 547 t.Fatalf("no free pages on NoFreelistSync reopen") 548 } 549 if err := db.DB.Close(); err != nil { 550 t.Fatal(err) 551 } 552 553 db.o = options.DefaultBdbOptions 554 db.MustReopen() 555 freepages-- 556 if fp := db.Stats().FreePageN; fp < freepages { 557 t.Fatalf("closed with %d free pages, opened with %d", freepages, fp) 558 } 559 } 560 561 func TestDB_Begin_ErrDatabaseNotOpen(t *testing.T) { 562 var db bdb.DB 563 if _, err := db.Begin(false); err != bdb.ErrDatabaseNotOpen { 564 t.Fatalf("unexpected error: %s", err) 565 } 566 } 567 568 func TestDB_BeginRW(t *testing.T) { 569 db := MustOpenDB() 570 defer db.MustClose() 571 572 tx, err := db.Begin(true) 573 if err != nil { 574 t.Fatal(err) 575 } else if tx == nil { 576 t.Fatal("expected tx") 577 } 578 579 if tx.DB() != db.DB { 580 t.Fatal("unexpected tx database") 581 } else if !tx.Writable() { 582 t.Fatal("expected writable tx") 583 } 584 585 if err := tx.Commit(); err != nil { 586 t.Fatal(err) 587 } 588 } 589 590 func TestDB_Concurrent_WriteTo(t *testing.T) { 591 o := &options.BdbOptions{NoFreelistSync: false} 592 db := MustOpenWithOption(o) 593 defer db.MustClose() 594 595 var wg sync.WaitGroup 596 wtxs, rtxs := 5, 5 597 wg.Add(wtxs * rtxs) 598 f := func(tx *bdb.Tx) { 599 defer wg.Done() 600 f, err := ioutil.TempFile("", "bdb-") 601 if err != nil { 602 panic(err) 603 } 604 time.Sleep(time.Duration(rand.Intn(20)+1) * time.Millisecond) 605 tx.WriteTo(f) 606 tx.Rollback() 607 f.Close() 608 snap := &DB{nil, f.Name(), o} 609 snap.MustReopen() 610 defer snap.MustClose() 611 snap.MustCheck() 612 } 613 614 tx1, err := db.Begin(true) 615 if err != nil { 616 t.Fatal(err) 617 } 618 if _, err := tx1.CreateBucket([]byte("abc")); err != nil { 619 t.Fatal(err) 620 } 621 if err := tx1.Commit(); err != nil { 622 t.Fatal(err) 623 } 624 625 for i := 0; i < wtxs; i++ { 626 tx, err := db.Begin(true) 627 if err != nil { 628 t.Fatal(err) 629 } 630 if err := tx.Bucket([]byte("abc")).Put([]byte{0}, []byte{0}); err != nil { 631 t.Fatal(err) 632 } 633 for j := 0; j < rtxs; j++ { 634 rtx, rerr := db.Begin(false) 635 if rerr != nil { 636 t.Fatal(rerr) 637 } 638 go f(rtx) 639 } 640 if err := tx.Commit(); err != nil { 641 t.Fatal(err) 642 } 643 } 644 wg.Wait() 645 } 646 647 func TestDB_BeginRW_Closed(t *testing.T) { 648 var db bdb.DB 649 if _, err := db.Begin(true); err != bdb.ErrDatabaseNotOpen { 650 t.Fatalf("unexpected error: %s", err) 651 } 652 } 653 654 func TestDB_Close_PendingTx_RW(t *testing.T) { testDB_Close_PendingTx(t, true) } 655 func TestDB_Close_PendingTx_RO(t *testing.T) { testDB_Close_PendingTx(t, false) } 656 657 func testDB_Close_PendingTx(t *testing.T, writable bool) { 658 db := MustOpenDB() 659 660 tx, err := db.Begin(writable) 661 if err != nil { 662 t.Fatal(err) 663 } 664 665 done := make(chan error, 1) 666 go func() { 667 err := db.Close() 668 done <- err 669 }() 670 671 time.Sleep(100 * time.Millisecond) 672 select { 673 case err := <-done: 674 if err != nil { 675 t.Errorf("error from inside goroutine: %v", err) 676 } 677 t.Fatal("database closed too early") 678 default: 679 } 680 681 if writable { 682 err = tx.Commit() 683 } else { 684 err = tx.Rollback() 685 } 686 if err != nil { 687 t.Fatal(err) 688 } 689 690 time.Sleep(100 * time.Millisecond) 691 select { 692 case err := <-done: 693 if err != nil { 694 t.Fatalf("error from inside goroutine: %v", err) 695 } 696 default: 697 t.Fatal("database did not close") 698 } 699 } 700 701 func TestDB_Update(t *testing.T) { 702 db := MustOpenDB() 703 defer db.MustClose() 704 if err := db.Update(func(tx *bdb.Tx) error { 705 b, err := tx.CreateBucket([]byte("widgets")) 706 if err != nil { 707 t.Fatal(err) 708 } 709 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 710 t.Fatal(err) 711 } 712 if err := b.Put([]byte("baz"), []byte("bat")); err != nil { 713 t.Fatal(err) 714 } 715 if err := b.Delete([]byte("foo")); err != nil { 716 t.Fatal(err) 717 } 718 return nil 719 }); err != nil { 720 t.Fatal(err) 721 } 722 if err := db.View(func(tx *bdb.Tx) error { 723 b := tx.Bucket([]byte("widgets")) 724 if v := b.Get([]byte("foo")); v != nil { 725 t.Fatalf("expected nil value, got: %v", v) 726 } 727 if v := b.Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) { 728 t.Fatalf("unexpected value: %v", v) 729 } 730 return nil 731 }); err != nil { 732 t.Fatal(err) 733 } 734 } 735 736 func TestDB_Update_Closed(t *testing.T) { 737 var db bdb.DB 738 if err := db.Update(func(tx *bdb.Tx) error { 739 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 740 t.Fatal(err) 741 } 742 return nil 743 }); err != bdb.ErrDatabaseNotOpen { 744 t.Fatalf("unexpected error: %s", err) 745 } 746 } 747 748 func TestDB_Update_ManualCommit(t *testing.T) { 749 db := MustOpenDB() 750 defer db.MustClose() 751 752 var panicked bool 753 if err := db.Update(func(tx *bdb.Tx) error { 754 func() { 755 defer func() { 756 if r := recover(); r != nil { 757 panicked = true 758 } 759 }() 760 761 if err := tx.Commit(); err != nil { 762 t.Fatal(err) 763 } 764 }() 765 return nil 766 }); err != nil { 767 t.Fatal(err) 768 } else if !panicked { 769 t.Fatal("expected panic") 770 } 771 } 772 773 func TestDB_Update_ManualRollback(t *testing.T) { 774 db := MustOpenDB() 775 defer db.MustClose() 776 777 var panicked bool 778 if err := db.Update(func(tx *bdb.Tx) error { 779 func() { 780 defer func() { 781 if r := recover(); r != nil { 782 panicked = true 783 } 784 }() 785 786 if err := tx.Rollback(); err != nil { 787 t.Fatal(err) 788 } 789 }() 790 return nil 791 }); err != nil { 792 t.Fatal(err) 793 } else if !panicked { 794 t.Fatal("expected panic") 795 } 796 } 797 798 func TestDB_View_ManualCommit(t *testing.T) { 799 db := MustOpenDB() 800 defer db.MustClose() 801 802 var panicked bool 803 if err := db.View(func(tx *bdb.Tx) error { 804 func() { 805 defer func() { 806 if r := recover(); r != nil { 807 panicked = true 808 } 809 }() 810 811 if err := tx.Commit(); err != nil { 812 t.Fatal(err) 813 } 814 }() 815 return nil 816 }); err != nil { 817 t.Fatal(err) 818 } else if !panicked { 819 t.Fatal("expected panic") 820 } 821 } 822 823 func TestDB_View_ManualRollback(t *testing.T) { 824 db := MustOpenDB() 825 defer db.MustClose() 826 827 var panicked bool 828 if err := db.View(func(tx *bdb.Tx) error { 829 func() { 830 defer func() { 831 if r := recover(); r != nil { 832 panicked = true 833 } 834 }() 835 836 if err := tx.Rollback(); err != nil { 837 t.Fatal(err) 838 } 839 }() 840 return nil 841 }); err != nil { 842 t.Fatal(err) 843 } else if !panicked { 844 t.Fatal("expected panic") 845 } 846 } 847 848 func TestDB_Update_Panic(t *testing.T) { 849 db := MustOpenDB() 850 defer db.MustClose() 851 852 func() { 853 defer func() { 854 if r := recover(); r != nil { 855 t.Log("recover: update", r) 856 } 857 }() 858 859 if err := db.Update(func(tx *bdb.Tx) error { 860 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 861 t.Fatal(err) 862 } 863 panic("omg") 864 }); err != nil { 865 t.Fatal(err) 866 } 867 }() 868 869 if err := db.Update(func(tx *bdb.Tx) error { 870 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 871 t.Fatal(err) 872 } 873 return nil 874 }); err != nil { 875 t.Fatal(err) 876 } 877 878 if err := db.Update(func(tx *bdb.Tx) error { 879 if tx.Bucket([]byte("widgets")) == nil { 880 t.Fatal("expected bucket") 881 } 882 return nil 883 }); err != nil { 884 t.Fatal(err) 885 } 886 } 887 888 func TestDB_View_Error(t *testing.T) { 889 db := MustOpenDB() 890 defer db.MustClose() 891 892 if err := db.View(func(tx *bdb.Tx) error { 893 return errors.New("xxx") 894 }); err == nil || err.Error() != "xxx" { 895 t.Fatalf("unexpected error: %s", err) 896 } 897 } 898 899 func TestDB_View_Panic(t *testing.T) { 900 db := MustOpenDB() 901 defer db.MustClose() 902 903 if err := db.Update(func(tx *bdb.Tx) error { 904 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 905 t.Fatal(err) 906 } 907 return nil 908 }); err != nil { 909 t.Fatal(err) 910 } 911 912 func() { 913 defer func() { 914 if r := recover(); r != nil { 915 t.Log("recover: view", r) 916 } 917 }() 918 919 if err := db.View(func(tx *bdb.Tx) error { 920 if tx.Bucket([]byte("widgets")) == nil { 921 t.Fatal("expected bucket") 922 } 923 panic("omg") 924 }); err != nil { 925 t.Fatal(err) 926 } 927 }() 928 929 if err := db.View(func(tx *bdb.Tx) error { 930 if tx.Bucket([]byte("widgets")) == nil { 931 t.Fatal("expected bucket") 932 } 933 return nil 934 }); err != nil { 935 t.Fatal(err) 936 } 937 } 938 939 func TestDB_Stats(t *testing.T) { 940 db := MustOpenDB() 941 defer db.MustClose() 942 if err := db.Update(func(tx *bdb.Tx) error { 943 _, err := tx.CreateBucket([]byte("widgets")) 944 return err 945 }); err != nil { 946 t.Fatal(err) 947 } 948 949 stats := db.Stats() 950 if stats.TxStats.PageCount != 2 { 951 t.Fatalf("unexpected TxStats.PageCount: %d", stats.TxStats.PageCount) 952 } else if stats.FreePageN != 0 { 953 t.Fatalf("unexpected FreePageN != 0: %d", stats.FreePageN) 954 } else if stats.PendingPageN != 2 { 955 t.Fatalf("unexpected PendingPageN != 2: %d", stats.PendingPageN) 956 } 957 } 958 959 func TestDB_Consistency(t *testing.T) { 960 db := MustOpenDB() 961 defer db.MustClose() 962 if err := db.Update(func(tx *bdb.Tx) error { 963 _, err := tx.CreateBucket([]byte("widgets")) 964 return err 965 }); err != nil { 966 t.Fatal(err) 967 } 968 969 for i := 0; i < 10; i++ { 970 if err := db.Update(func(tx *bdb.Tx) error { 971 if err := tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")); err != nil { 972 t.Fatal(err) 973 } 974 return nil 975 }); err != nil { 976 t.Fatal(err) 977 } 978 } 979 980 if err := db.Update(func(tx *bdb.Tx) error { 981 if p, _ := tx.Page(0); p == nil { 982 t.Fatal("expected page") 983 } else if p.Type != "meta" { 984 t.Fatalf("unexpected page type: %s", p.Type) 985 } 986 987 if p, _ := tx.Page(1); p == nil { 988 t.Fatal("expected page") 989 } else if p.Type != "meta" { 990 t.Fatalf("unexpected page type: %s", p.Type) 991 } 992 993 if p, _ := tx.Page(2); p == nil { 994 t.Fatal("expected page") 995 } else if p.Type != "free" { 996 t.Fatalf("unexpected page type: %s", p.Type) 997 } 998 999 if p, _ := tx.Page(3); p == nil { 1000 t.Fatal("expected page") 1001 } else if p.Type != "free" { 1002 t.Fatalf("unexpected page type: %s", p.Type) 1003 } 1004 1005 if p, _ := tx.Page(4); p == nil { 1006 t.Fatal("expected page") 1007 } else if p.Type != "leaf" { 1008 t.Fatalf("unexpected page type: %s", p.Type) 1009 } 1010 1011 if p, _ := tx.Page(5); p == nil { 1012 t.Fatal("expected page") 1013 } else if p.Type != "freelist" { 1014 t.Fatalf("unexpected page type: %s", p.Type) 1015 } 1016 1017 if p, _ := tx.Page(6); p != nil { 1018 t.Fatal("unexpected page") 1019 } 1020 return nil 1021 }); err != nil { 1022 t.Fatal(err) 1023 } 1024 } 1025 1026 func TestDBStats_Sub(t *testing.T) { 1027 var a, b bdb.Stats 1028 a.TxStats.PageCount = 3 1029 a.FreePageN = 4 1030 b.TxStats.PageCount = 10 1031 b.FreePageN = 14 1032 diff := b.Sub(&a) 1033 if diff.TxStats.PageCount != 7 { 1034 t.Fatalf("unexpected TxStats.PageCount: %d", diff.TxStats.PageCount) 1035 } 1036 1037 if diff.FreePageN != 14 { 1038 t.Fatalf("unexpected FreePageN: %d", diff.FreePageN) 1039 } 1040 } 1041 1042 func TestDB_Batch(t *testing.T) { 1043 db := MustOpenDB() 1044 defer db.MustClose() 1045 1046 if err := db.Update(func(tx *bdb.Tx) error { 1047 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 1048 t.Fatal(err) 1049 } 1050 return nil 1051 }); err != nil { 1052 t.Fatal(err) 1053 } 1054 1055 n := 2 1056 ch := make(chan error, n) 1057 for i := 0; i < n; i++ { 1058 go func(i int) { 1059 ch <- db.Batch(func(tx *bdb.Tx) error { 1060 return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{}) 1061 }) 1062 }(i) 1063 } 1064 1065 for i := 0; i < n; i++ { 1066 if err := <-ch; err != nil { 1067 t.Fatal(err) 1068 } 1069 } 1070 1071 if err := db.View(func(tx *bdb.Tx) error { 1072 b := tx.Bucket([]byte("widgets")) 1073 for i := 0; i < n; i++ { 1074 if v := b.Get(u64tob(uint64(i))); v == nil { 1075 t.Errorf("key not found: %d", i) 1076 } 1077 } 1078 return nil 1079 }); err != nil { 1080 t.Fatal(err) 1081 } 1082 } 1083 1084 func TestDB_Batch_Panic(t *testing.T) { 1085 db := MustOpenDB() 1086 defer db.MustClose() 1087 1088 var sentinel int 1089 var bork = &sentinel 1090 var problem interface{} 1091 var err error 1092 1093 func() { 1094 defer func() { 1095 if p := recover(); p != nil { 1096 problem = p 1097 } 1098 }() 1099 err = db.Batch(func(tx *bdb.Tx) error { 1100 panic(bork) 1101 }) 1102 }() 1103 1104 if g, e := err, error(nil); g != e { 1105 t.Fatalf("wrong error: %v != %v", g, e) 1106 } 1107 if g, e := problem, bork; g != e { 1108 t.Fatalf("wrong error: %v != %v", g, e) 1109 } 1110 } 1111 1112 func TestDB_BatchFull(t *testing.T) { 1113 db := MustOpenDB() 1114 defer db.MustClose() 1115 if err := db.Update(func(tx *bdb.Tx) error { 1116 _, err := tx.CreateBucket([]byte("widgets")) 1117 return err 1118 }); err != nil { 1119 t.Fatal(err) 1120 } 1121 1122 const size = 3 1123 ch := make(chan error, size) 1124 put := func(i int) { 1125 ch <- db.Batch(func(tx *bdb.Tx) error { 1126 return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{}) 1127 }) 1128 } 1129 1130 db.MaxBatchSize = size 1131 db.MaxBatchDelay = 1 * time.Hour 1132 1133 go put(1) 1134 go put(2) 1135 1136 time.Sleep(10 * time.Millisecond) 1137 1138 select { 1139 case <-ch: 1140 t.Fatalf("batch triggered too early") 1141 default: 1142 } 1143 1144 go put(3) 1145 1146 for i := 0; i < size; i++ { 1147 if err := <-ch; err != nil { 1148 t.Fatal(err) 1149 } 1150 } 1151 1152 if err := db.View(func(tx *bdb.Tx) error { 1153 b := tx.Bucket([]byte("widgets")) 1154 for i := 1; i <= size; i++ { 1155 if v := b.Get(u64tob(uint64(i))); v == nil { 1156 t.Errorf("key not found: %d", i) 1157 } 1158 } 1159 return nil 1160 }); err != nil { 1161 t.Fatal(err) 1162 } 1163 } 1164 1165 func TestDB_BatchTime(t *testing.T) { 1166 db := MustOpenDB() 1167 defer db.MustClose() 1168 if err := db.Update(func(tx *bdb.Tx) error { 1169 _, err := tx.CreateBucket([]byte("widgets")) 1170 return err 1171 }); err != nil { 1172 t.Fatal(err) 1173 } 1174 1175 const size = 1 1176 ch := make(chan error, size) 1177 put := func(i int) { 1178 ch <- db.Batch(func(tx *bdb.Tx) error { 1179 return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{}) 1180 }) 1181 } 1182 1183 db.MaxBatchSize = 1000 1184 db.MaxBatchDelay = 0 1185 1186 go put(1) 1187 1188 for i := 0; i < size; i++ { 1189 if err := <-ch; err != nil { 1190 t.Fatal(err) 1191 } 1192 } 1193 1194 if err := db.View(func(tx *bdb.Tx) error { 1195 b := tx.Bucket([]byte("widgets")) 1196 for i := 1; i <= size; i++ { 1197 if v := b.Get(u64tob(uint64(i))); v == nil { 1198 t.Errorf("key not found: %d", i) 1199 } 1200 } 1201 return nil 1202 }); err != nil { 1203 t.Fatal(err) 1204 } 1205 } 1206 1207 func TestDB_Compact(t *testing.T) { 1208 dbDir := "/tmp/000" 1209 os.RemoveAll(dbDir) 1210 if err := os.MkdirAll(dbDir, 0755); err != nil { 1211 t.Fatal(err) 1212 } 1213 1214 srcFile := path.Join(dbDir, "db.src") 1215 srcDb, err := bdb.Open(srcFile, nil) 1216 if err != nil { 1217 t.Fatal(err) 1218 } 1219 defer srcDb.Close() 1220 1221 destFile := path.Join(dbDir, "db.dest") 1222 destDb, err := bdb.Open(destFile, nil) 1223 if err != nil { 1224 t.Fatal(err) 1225 } 1226 defer destDb.Close() 1227 1228 n := 10000 1229 tx, err := srcDb.Begin(true) 1230 if err != nil { 1231 t.Fatal(err) 1232 } 1233 bucket, err := tx.CreateBucketIfNotExists([]byte("widgets")) 1234 if err != nil { 1235 t.Fatal(err) 1236 } 1237 1238 commitCount := 0 1239 for i := 0; i < n; i++ { 1240 key := "test-" + strconv.FormatInt(int64(i), 10) 1241 value := "value-" + strconv.FormatInt(int64(i), 10) 1242 1243 if err := bucket.Put([]byte(key), []byte(value)); err != nil { 1244 t.Fatal(err) 1245 } 1246 commitCount++ 1247 if commitCount > 0 && commitCount%200 == 0 { 1248 tx.Commit() 1249 commitCount = 0 1250 tx, _ = srcDb.Begin(true) 1251 bucket = tx.Bucket([]byte("widgets")) 1252 } 1253 } 1254 if commitCount > 0 { 1255 tx.Commit() 1256 commitCount = 0 1257 } else { 1258 tx.Rollback() 1259 commitCount = 0 1260 } 1261 1262 tx, _ = srcDb.Begin(true) 1263 bucket = tx.Bucket([]byte("widgets")) 1264 for i := 0; i < n; i++ { 1265 if i%2 == 0 { 1266 continue 1267 } 1268 key := "test-" + strconv.FormatInt(int64(i), 10) 1269 1270 if err := bucket.Delete([]byte(key)); err != nil { 1271 t.Fatal(err) 1272 } 1273 commitCount++ 1274 if commitCount > 0 && commitCount%200 == 0 { 1275 tx.Commit() 1276 commitCount = 0 1277 tx, _ = srcDb.Begin(true) 1278 bucket = tx.Bucket([]byte("widgets")) 1279 } 1280 } 1281 if commitCount > 0 { 1282 tx.Commit() 1283 commitCount = 0 1284 } else { 1285 tx.Rollback() 1286 commitCount = 0 1287 } 1288 1289 txMaxSize := int64(200) 1290 if err := bdb.Compact(destDb, srcDb, txMaxSize); err != nil { 1291 t.Fatal(err) 1292 } 1293 fmt.Println("compact end") 1294 } 1295 1296 func ExampleDB_Update() { 1297 db, err := bdb.Open(tempfile(), nil) 1298 if err != nil { 1299 log.Fatal(err) 1300 } 1301 defer os.Remove(db.Path()) 1302 1303 if err := db.Update(func(tx *bdb.Tx) error { 1304 b, err := tx.CreateBucket([]byte("widgets")) 1305 if err != nil { 1306 return err 1307 } 1308 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 1309 return err 1310 } 1311 return nil 1312 }); err != nil { 1313 log.Fatal(err) 1314 } 1315 1316 if err := db.View(func(tx *bdb.Tx) error { 1317 value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) 1318 fmt.Printf("The value of 'foo' is: %s\n", value) 1319 return nil 1320 }); err != nil { 1321 log.Fatal(err) 1322 } 1323 1324 if err := db.Close(); err != nil { 1325 log.Fatal(err) 1326 } 1327 } 1328 1329 func ExampleDB_View() { 1330 db, err := bdb.Open(tempfile(), nil) 1331 if err != nil { 1332 log.Fatal(err) 1333 } 1334 defer os.Remove(db.Path()) 1335 1336 if err := db.Update(func(tx *bdb.Tx) error { 1337 b, err := tx.CreateBucket([]byte("people")) 1338 if err != nil { 1339 return err 1340 } 1341 if err := b.Put([]byte("john"), []byte("doe")); err != nil { 1342 return err 1343 } 1344 if err := b.Put([]byte("susy"), []byte("que")); err != nil { 1345 return err 1346 } 1347 return nil 1348 }); err != nil { 1349 log.Fatal(err) 1350 } 1351 1352 if err := db.View(func(tx *bdb.Tx) error { 1353 v := tx.Bucket([]byte("people")).Get([]byte("john")) 1354 fmt.Printf("John's last name is %s.\n", v) 1355 return nil 1356 }); err != nil { 1357 log.Fatal(err) 1358 } 1359 1360 if err := db.Close(); err != nil { 1361 log.Fatal(err) 1362 } 1363 } 1364 1365 func ExampleDB_Begin() { 1366 db, err := bdb.Open(tempfile(), nil) 1367 if err != nil { 1368 log.Fatal(err) 1369 } 1370 defer os.Remove(db.Path()) 1371 1372 if err = db.Update(func(tx *bdb.Tx) error { 1373 _, err := tx.CreateBucket([]byte("widgets")) 1374 return err 1375 }); err != nil { 1376 log.Fatal(err) 1377 } 1378 1379 tx, err := db.Begin(true) 1380 if err != nil { 1381 log.Fatal(err) 1382 } 1383 b := tx.Bucket([]byte("widgets")) 1384 if err = b.Put([]byte("john"), []byte("blue")); err != nil { 1385 log.Fatal(err) 1386 } 1387 if err = b.Put([]byte("abby"), []byte("red")); err != nil { 1388 log.Fatal(err) 1389 } 1390 if err = b.Put([]byte("zephyr"), []byte("purple")); err != nil { 1391 log.Fatal(err) 1392 } 1393 if err = tx.Commit(); err != nil { 1394 log.Fatal(err) 1395 } 1396 1397 tx, err = db.Begin(false) 1398 if err != nil { 1399 log.Fatal(err) 1400 } 1401 c := tx.Bucket([]byte("widgets")).Cursor() 1402 for k, v := c.First(); k != nil; k, v = c.Next() { 1403 fmt.Printf("%s likes %s\n", k, v) 1404 } 1405 1406 if err = tx.Rollback(); err != nil { 1407 log.Fatal(err) 1408 } 1409 1410 if err = db.Close(); err != nil { 1411 log.Fatal(err) 1412 } 1413 } 1414 1415 func BenchmarkDBBatchAutomatic(b *testing.B) { 1416 db := MustOpenDB() 1417 defer db.MustClose() 1418 if err := db.Update(func(tx *bdb.Tx) error { 1419 _, err := tx.CreateBucket([]byte("bench")) 1420 return err 1421 }); err != nil { 1422 b.Fatal(err) 1423 } 1424 1425 b.ResetTimer() 1426 for i := 0; i < b.N; i++ { 1427 start := make(chan struct{}) 1428 var wg sync.WaitGroup 1429 1430 for round := 0; round < 1000; round++ { 1431 wg.Add(1) 1432 1433 go func(id uint32) { 1434 defer wg.Done() 1435 <-start 1436 1437 h := fnv.New32a() 1438 buf := make([]byte, 4) 1439 binary.LittleEndian.PutUint32(buf, id) 1440 _, _ = h.Write(buf[:]) 1441 k := h.Sum(nil) 1442 insert := func(tx *bdb.Tx) error { 1443 b := tx.Bucket([]byte("bench")) 1444 return b.Put(k, []byte("filler")) 1445 } 1446 if err := db.Batch(insert); err != nil { 1447 b.Error(err) 1448 return 1449 } 1450 }(uint32(round)) 1451 } 1452 close(start) 1453 wg.Wait() 1454 } 1455 1456 b.StopTimer() 1457 validateBatchBench(b, db) 1458 } 1459 1460 func BenchmarkDBBatchSingle(b *testing.B) { 1461 db := MustOpenDB() 1462 defer db.MustClose() 1463 if err := db.Update(func(tx *bdb.Tx) error { 1464 _, err := tx.CreateBucket([]byte("bench")) 1465 return err 1466 }); err != nil { 1467 b.Fatal(err) 1468 } 1469 1470 b.ResetTimer() 1471 for i := 0; i < b.N; i++ { 1472 start := make(chan struct{}) 1473 var wg sync.WaitGroup 1474 1475 for round := 0; round < 1000; round++ { 1476 wg.Add(1) 1477 go func(id uint32) { 1478 defer wg.Done() 1479 <-start 1480 1481 h := fnv.New32a() 1482 buf := make([]byte, 4) 1483 binary.LittleEndian.PutUint32(buf, id) 1484 _, _ = h.Write(buf[:]) 1485 k := h.Sum(nil) 1486 insert := func(tx *bdb.Tx) error { 1487 b := tx.Bucket([]byte("bench")) 1488 return b.Put(k, []byte("filler")) 1489 } 1490 if err := db.Update(insert); err != nil { 1491 b.Error(err) 1492 return 1493 } 1494 }(uint32(round)) 1495 } 1496 close(start) 1497 wg.Wait() 1498 } 1499 1500 b.StopTimer() 1501 validateBatchBench(b, db) 1502 } 1503 1504 func BenchmarkDBBatchManual10x100(b *testing.B) { 1505 db := MustOpenDB() 1506 defer db.MustClose() 1507 if err := db.Update(func(tx *bdb.Tx) error { 1508 _, err := tx.CreateBucket([]byte("bench")) 1509 return err 1510 }); err != nil { 1511 b.Fatal(err) 1512 } 1513 1514 b.ResetTimer() 1515 for i := 0; i < b.N; i++ { 1516 start := make(chan struct{}) 1517 var wg sync.WaitGroup 1518 errCh := make(chan error, 10) 1519 1520 for major := 0; major < 10; major++ { 1521 wg.Add(1) 1522 go func(id uint32) { 1523 defer wg.Done() 1524 <-start 1525 1526 insert100 := func(tx *bdb.Tx) error { 1527 h := fnv.New32a() 1528 buf := make([]byte, 4) 1529 for minor := uint32(0); minor < 100; minor++ { 1530 binary.LittleEndian.PutUint32(buf, uint32(id*100+minor)) 1531 h.Reset() 1532 _, _ = h.Write(buf[:]) 1533 k := h.Sum(nil) 1534 b := tx.Bucket([]byte("bench")) 1535 if err := b.Put(k, []byte("filler")); err != nil { 1536 return err 1537 } 1538 } 1539 return nil 1540 } 1541 err := db.Update(insert100) 1542 errCh <- err 1543 }(uint32(major)) 1544 } 1545 close(start) 1546 wg.Wait() 1547 close(errCh) 1548 for err := range errCh { 1549 if err != nil { 1550 b.Fatal(err) 1551 } 1552 } 1553 } 1554 1555 b.StopTimer() 1556 validateBatchBench(b, db) 1557 } 1558 1559 func validateBatchBench(b *testing.B, db *DB) { 1560 var rollback = errors.New("sentinel error to cause rollback") 1561 validate := func(tx *bdb.Tx) error { 1562 bucket := tx.Bucket([]byte("bench")) 1563 h := fnv.New32a() 1564 buf := make([]byte, 4) 1565 for id := uint32(0); id < 1000; id++ { 1566 binary.LittleEndian.PutUint32(buf, id) 1567 h.Reset() 1568 _, _ = h.Write(buf[:]) 1569 k := h.Sum(nil) 1570 v := bucket.Get(k) 1571 if v == nil { 1572 b.Errorf("not found id=%d key=%x", id, k) 1573 continue 1574 } 1575 if g, e := v, []byte("filler"); !bytes.Equal(g, e) { 1576 b.Errorf("bad value for id=%d key=%x: %s != %q", id, k, g, e) 1577 } 1578 if err := bucket.Delete(k); err != nil { 1579 return err 1580 } 1581 } 1582 1583 c := bucket.Cursor() 1584 for k, v := c.First(); k != nil; k, v = c.Next() { 1585 b.Errorf("unexpected key: %x = %q", k, v) 1586 } 1587 return rollback 1588 } 1589 if err := db.Update(validate); err != nil && err != rollback { 1590 b.Error(err) 1591 } 1592 } 1593 1594 type DB struct { 1595 *bdb.DB 1596 f string 1597 o *options.BdbOptions 1598 } 1599 1600 func MustOpenDB() *DB { 1601 return MustOpenWithOption(nil) 1602 } 1603 1604 func MustOpenWithOption(o *options.BdbOptions) *DB { 1605 f := tempfile() 1606 if o == nil { 1607 o = options.DefaultBdbOptions 1608 } 1609 1610 freelistType := consts.BdbFreelistArrayType 1611 if env := os.Getenv(bdb.TestFreelistType); env == consts.BdbFreelistMapType { 1612 freelistType = consts.BdbFreelistMapType 1613 } 1614 o.FreelistType = freelistType 1615 1616 db, err := bdb.Open(f, o) 1617 if err != nil { 1618 panic(err) 1619 } 1620 return &DB{ 1621 DB: db, 1622 f: f, 1623 o: o, 1624 } 1625 } 1626 1627 func (db *DB) Close() error { 1628 if *statsFlag { 1629 db.PrintStats() 1630 } 1631 1632 db.MustCheck() 1633 1634 defer os.Remove(db.Path()) 1635 return db.DB.Close() 1636 } 1637 1638 func (db *DB) MustClose() { 1639 if err := db.Close(); err != nil { 1640 panic(err) 1641 } 1642 } 1643 1644 func (db *DB) MustReopen() { 1645 indb, err := bdb.Open(db.f, db.o) 1646 if err != nil { 1647 panic(err) 1648 } 1649 db.DB = indb 1650 } 1651 1652 func (db *DB) PrintStats() { 1653 var stats = db.Stats() 1654 fmt.Printf("[db] %-20s %-20s %-20s\n", 1655 fmt.Sprintf("pg(%d/%d)", stats.TxStats.PageCount, stats.TxStats.PageAlloc), 1656 fmt.Sprintf("cur(%d)", stats.TxStats.CursorCount), 1657 fmt.Sprintf("node(%d/%d)", stats.TxStats.NodeCount, stats.TxStats.NodeDeref), 1658 ) 1659 fmt.Printf(" %-20s %-20s %-20s\n", 1660 fmt.Sprintf("rebal(%d/%v)", stats.TxStats.Rebalance, truncDuration(stats.TxStats.RebalanceTime)), 1661 fmt.Sprintf("spill(%d/%v)", stats.TxStats.Spill, truncDuration(stats.TxStats.SpillTime)), 1662 fmt.Sprintf("w(%d/%v)", stats.TxStats.Write, truncDuration(stats.TxStats.WriteTime)), 1663 ) 1664 } 1665 1666 func (db *DB) MustCheck() { 1667 if err := db.Update(func(tx *bdb.Tx) error { 1668 var errors []error 1669 for err := range tx.Check() { 1670 errors = append(errors, err) 1671 if len(errors) > 10 { 1672 break 1673 } 1674 } 1675 1676 if len(errors) > 0 { 1677 var path = tempfile() 1678 if err := tx.CopyFile(path, 0600); err != nil { 1679 panic(err) 1680 } 1681 1682 fmt.Print("\n\n") 1683 fmt.Printf("consistency check failed (%d errors)\n", len(errors)) 1684 for _, err := range errors { 1685 fmt.Println(err) 1686 } 1687 fmt.Println("") 1688 fmt.Println("db saved to:") 1689 fmt.Println(path) 1690 fmt.Print("\n\n") 1691 os.Exit(-1) 1692 } 1693 1694 return nil 1695 }); err != nil && err != bdb.ErrDatabaseNotOpen { 1696 panic(err) 1697 } 1698 } 1699 1700 func (db *DB) CopyTempFile() { 1701 path := tempfile() 1702 if err := db.View(func(tx *bdb.Tx) error { 1703 return tx.CopyFile(path, 0600) 1704 }); err != nil { 1705 panic(err) 1706 } 1707 fmt.Println("db copied to: ", path) 1708 } 1709 1710 func tempfile() string { 1711 f, err := os.CreateTemp("", "bdb-") 1712 if err != nil { 1713 panic(err) 1714 } 1715 if err := f.Close(); err != nil { 1716 panic(err) 1717 } 1718 if err := os.Remove(f.Name()); err != nil { 1719 panic(err) 1720 } 1721 return f.Name() 1722 } 1723 1724 func trunc(b []byte, length int) []byte { 1725 if length < len(b) { 1726 return b[:length] 1727 } 1728 return b 1729 } 1730 1731 func truncDuration(d time.Duration) string { 1732 return regexp.MustCompile(`^(\d+)(\.\d+)`).ReplaceAllString(d.String(), "$1") 1733 } 1734 1735 func fileSize(path string) int64 { 1736 fi, err := os.Stat(path) 1737 if err != nil { 1738 return 0 1739 } 1740 return fi.Size() 1741 } 1742 1743 func u64tob(v uint64) []byte { 1744 b := make([]byte, 8) 1745 binary.BigEndian.PutUint64(b, v) 1746 return b 1747 }