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