github.com/bhojpur/cache@v0.0.4/pkg/memory/tx_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 "errors" 26 "fmt" 27 "log" 28 "os" 29 "testing" 30 31 memcache "github.com/bhojpur/cache/pkg/memory" 32 ) 33 34 // TestTx_Check_ReadOnly tests consistency checking on a ReadOnly database. 35 func TestTx_Check_ReadOnly(t *testing.T) { 36 db := MustOpenDB() 37 defer db.Close() 38 if err := db.Update(func(tx *memcache.Tx) error { 39 b, err := tx.CreateBucket([]byte("widgets")) 40 if err != nil { 41 t.Fatal(err) 42 } 43 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 44 t.Fatal(err) 45 } 46 return nil 47 }); err != nil { 48 t.Fatal(err) 49 } 50 if err := db.DB.Close(); err != nil { 51 t.Fatal(err) 52 } 53 54 readOnlyDB, err := memcache.Open(db.f, 0666, &memcache.Options{ReadOnly: true}) 55 if err != nil { 56 t.Fatal(err) 57 } 58 defer readOnlyDB.Close() 59 60 tx, err := readOnlyDB.Begin(false) 61 if err != nil { 62 t.Fatal(err) 63 } 64 // ReadOnly DB will load freelist on Check call. 65 numChecks := 2 66 errc := make(chan error, numChecks) 67 check := func() { 68 errc <- <-tx.Check() 69 } 70 // Ensure the freelist is not reloaded and does not race. 71 for i := 0; i < numChecks; i++ { 72 go check() 73 } 74 for i := 0; i < numChecks; i++ { 75 if err := <-errc; err != nil { 76 t.Fatal(err) 77 } 78 } 79 // Close the view transaction 80 tx.Rollback() 81 } 82 83 // Ensure that committing a closed transaction returns an error. 84 func TestTx_Commit_ErrTxClosed(t *testing.T) { 85 db := MustOpenDB() 86 defer db.MustClose() 87 tx, err := db.Begin(true) 88 if err != nil { 89 t.Fatal(err) 90 } 91 92 if _, err := tx.CreateBucket([]byte("foo")); err != nil { 93 t.Fatal(err) 94 } 95 96 if err := tx.Commit(); err != nil { 97 t.Fatal(err) 98 } 99 100 if err := tx.Commit(); err != memcache.ErrTxClosed { 101 t.Fatalf("unexpected error: %s", err) 102 } 103 } 104 105 // Ensure that rolling back a closed transaction returns an error. 106 func TestTx_Rollback_ErrTxClosed(t *testing.T) { 107 db := MustOpenDB() 108 defer db.MustClose() 109 110 tx, err := db.Begin(true) 111 if err != nil { 112 t.Fatal(err) 113 } 114 115 if err := tx.Rollback(); err != nil { 116 t.Fatal(err) 117 } 118 if err := tx.Rollback(); err != memcache.ErrTxClosed { 119 t.Fatalf("unexpected error: %s", err) 120 } 121 } 122 123 // Ensure that committing a read-only transaction returns an error. 124 func TestTx_Commit_ErrTxNotWritable(t *testing.T) { 125 db := MustOpenDB() 126 defer db.MustClose() 127 tx, err := db.Begin(false) 128 if err != nil { 129 t.Fatal(err) 130 } 131 if err := tx.Commit(); err != memcache.ErrTxNotWritable { 132 t.Fatal(err) 133 } 134 // Close the view transaction 135 tx.Rollback() 136 } 137 138 // Ensure that a transaction can retrieve a cursor on the root bucket. 139 func TestTx_Cursor(t *testing.T) { 140 db := MustOpenDB() 141 defer db.MustClose() 142 if err := db.Update(func(tx *memcache.Tx) error { 143 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 144 t.Fatal(err) 145 } 146 147 if _, err := tx.CreateBucket([]byte("woojits")); err != nil { 148 t.Fatal(err) 149 } 150 151 c := tx.Cursor() 152 if k, v := c.First(); !bytes.Equal(k, []byte("widgets")) { 153 t.Fatalf("unexpected key: %v", k) 154 } else if v != nil { 155 t.Fatalf("unexpected value: %v", v) 156 } 157 158 if k, v := c.Next(); !bytes.Equal(k, []byte("woojits")) { 159 t.Fatalf("unexpected key: %v", k) 160 } else if v != nil { 161 t.Fatalf("unexpected value: %v", v) 162 } 163 164 if k, v := c.Next(); k != nil { 165 t.Fatalf("unexpected key: %v", k) 166 } else if v != nil { 167 t.Fatalf("unexpected value: %v", k) 168 } 169 170 return nil 171 }); err != nil { 172 t.Fatal(err) 173 } 174 } 175 176 // Ensure that creating a bucket with a read-only transaction returns an error. 177 func TestTx_CreateBucket_ErrTxNotWritable(t *testing.T) { 178 db := MustOpenDB() 179 defer db.MustClose() 180 if err := db.View(func(tx *memcache.Tx) error { 181 _, err := tx.CreateBucket([]byte("foo")) 182 if err != memcache.ErrTxNotWritable { 183 t.Fatalf("unexpected error: %s", err) 184 } 185 return nil 186 }); err != nil { 187 t.Fatal(err) 188 } 189 } 190 191 // Ensure that creating a bucket on a closed transaction returns an error. 192 func TestTx_CreateBucket_ErrTxClosed(t *testing.T) { 193 db := MustOpenDB() 194 defer db.MustClose() 195 tx, err := db.Begin(true) 196 if err != nil { 197 t.Fatal(err) 198 } 199 if err := tx.Commit(); err != nil { 200 t.Fatal(err) 201 } 202 203 if _, err := tx.CreateBucket([]byte("foo")); err != memcache.ErrTxClosed { 204 t.Fatalf("unexpected error: %s", err) 205 } 206 } 207 208 // Ensure that a Tx can retrieve a bucket. 209 func TestTx_Bucket(t *testing.T) { 210 db := MustOpenDB() 211 defer db.MustClose() 212 if err := db.Update(func(tx *memcache.Tx) error { 213 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 214 t.Fatal(err) 215 } 216 if tx.Bucket([]byte("widgets")) == nil { 217 t.Fatal("expected bucket") 218 } 219 return nil 220 }); err != nil { 221 t.Fatal(err) 222 } 223 } 224 225 // Ensure that a Tx retrieving a non-existent key returns nil. 226 func TestTx_Get_NotFound(t *testing.T) { 227 db := MustOpenDB() 228 defer db.MustClose() 229 if err := db.Update(func(tx *memcache.Tx) error { 230 b, err := tx.CreateBucket([]byte("widgets")) 231 if err != nil { 232 t.Fatal(err) 233 } 234 235 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 236 t.Fatal(err) 237 } 238 if b.Get([]byte("no_such_key")) != nil { 239 t.Fatal("expected nil value") 240 } 241 return nil 242 }); err != nil { 243 t.Fatal(err) 244 } 245 } 246 247 // Ensure that a bucket can be created and retrieved. 248 func TestTx_CreateBucket(t *testing.T) { 249 db := MustOpenDB() 250 defer db.MustClose() 251 252 // Create a bucket. 253 if err := db.Update(func(tx *memcache.Tx) error { 254 b, err := tx.CreateBucket([]byte("widgets")) 255 if err != nil { 256 t.Fatal(err) 257 } else if b == nil { 258 t.Fatal("expected bucket") 259 } 260 return nil 261 }); err != nil { 262 t.Fatal(err) 263 } 264 265 // Read the bucket through a separate transaction. 266 if err := db.View(func(tx *memcache.Tx) error { 267 if tx.Bucket([]byte("widgets")) == nil { 268 t.Fatal("expected bucket") 269 } 270 return nil 271 }); err != nil { 272 t.Fatal(err) 273 } 274 } 275 276 // Ensure that a bucket can be created if it doesn't already exist. 277 func TestTx_CreateBucketIfNotExists(t *testing.T) { 278 db := MustOpenDB() 279 defer db.MustClose() 280 if err := db.Update(func(tx *memcache.Tx) error { 281 // Create bucket. 282 if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil { 283 t.Fatal(err) 284 } else if b == nil { 285 t.Fatal("expected bucket") 286 } 287 288 // Create bucket again. 289 if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil { 290 t.Fatal(err) 291 } else if b == nil { 292 t.Fatal("expected bucket") 293 } 294 295 return nil 296 }); err != nil { 297 t.Fatal(err) 298 } 299 300 // Read the bucket through a separate transaction. 301 if err := db.View(func(tx *memcache.Tx) error { 302 if tx.Bucket([]byte("widgets")) == nil { 303 t.Fatal("expected bucket") 304 } 305 return nil 306 }); err != nil { 307 t.Fatal(err) 308 } 309 } 310 311 // Ensure transaction returns an error if creating an unnamed bucket. 312 func TestTx_CreateBucketIfNotExists_ErrBucketNameRequired(t *testing.T) { 313 db := MustOpenDB() 314 defer db.MustClose() 315 if err := db.Update(func(tx *memcache.Tx) error { 316 if _, err := tx.CreateBucketIfNotExists([]byte{}); err != memcache.ErrBucketNameRequired { 317 t.Fatalf("unexpected error: %s", err) 318 } 319 320 if _, err := tx.CreateBucketIfNotExists(nil); err != memcache.ErrBucketNameRequired { 321 t.Fatalf("unexpected error: %s", err) 322 } 323 324 return nil 325 }); err != nil { 326 t.Fatal(err) 327 } 328 } 329 330 // Ensure that a bucket cannot be created twice. 331 func TestTx_CreateBucket_ErrBucketExists(t *testing.T) { 332 db := MustOpenDB() 333 defer db.MustClose() 334 335 // Create a bucket. 336 if err := db.Update(func(tx *memcache.Tx) error { 337 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 338 t.Fatal(err) 339 } 340 return nil 341 }); err != nil { 342 t.Fatal(err) 343 } 344 345 // Create the same bucket again. 346 if err := db.Update(func(tx *memcache.Tx) error { 347 if _, err := tx.CreateBucket([]byte("widgets")); err != memcache.ErrBucketExists { 348 t.Fatalf("unexpected error: %s", err) 349 } 350 return nil 351 }); err != nil { 352 t.Fatal(err) 353 } 354 } 355 356 // Ensure that a bucket is created with a non-blank name. 357 func TestTx_CreateBucket_ErrBucketNameRequired(t *testing.T) { 358 db := MustOpenDB() 359 defer db.MustClose() 360 if err := db.Update(func(tx *memcache.Tx) error { 361 if _, err := tx.CreateBucket(nil); err != memcache.ErrBucketNameRequired { 362 t.Fatalf("unexpected error: %s", err) 363 } 364 return nil 365 }); err != nil { 366 t.Fatal(err) 367 } 368 } 369 370 // Ensure that a bucket can be deleted. 371 func TestTx_DeleteBucket(t *testing.T) { 372 db := MustOpenDB() 373 defer db.MustClose() 374 375 // Create a bucket and add a value. 376 if err := db.Update(func(tx *memcache.Tx) error { 377 b, err := tx.CreateBucket([]byte("widgets")) 378 if err != nil { 379 t.Fatal(err) 380 } 381 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 382 t.Fatal(err) 383 } 384 return nil 385 }); err != nil { 386 t.Fatal(err) 387 } 388 389 // Delete the bucket and make sure we can't get the value. 390 if err := db.Update(func(tx *memcache.Tx) error { 391 if err := tx.DeleteBucket([]byte("widgets")); err != nil { 392 t.Fatal(err) 393 } 394 if tx.Bucket([]byte("widgets")) != nil { 395 t.Fatal("unexpected bucket") 396 } 397 return nil 398 }); err != nil { 399 t.Fatal(err) 400 } 401 402 if err := db.Update(func(tx *memcache.Tx) error { 403 // Create the bucket again and make sure there's not a phantom value. 404 b, err := tx.CreateBucket([]byte("widgets")) 405 if err != nil { 406 t.Fatal(err) 407 } 408 if v := b.Get([]byte("foo")); v != nil { 409 t.Fatalf("unexpected phantom value: %v", v) 410 } 411 return nil 412 }); err != nil { 413 t.Fatal(err) 414 } 415 } 416 417 // Ensure that deleting a bucket on a closed transaction returns an error. 418 func TestTx_DeleteBucket_ErrTxClosed(t *testing.T) { 419 db := MustOpenDB() 420 defer db.MustClose() 421 tx, err := db.Begin(true) 422 if err != nil { 423 t.Fatal(err) 424 } 425 if err := tx.Commit(); err != nil { 426 t.Fatal(err) 427 } 428 if err := tx.DeleteBucket([]byte("foo")); err != memcache.ErrTxClosed { 429 t.Fatalf("unexpected error: %s", err) 430 } 431 } 432 433 // Ensure that deleting a bucket with a read-only transaction returns an error. 434 func TestTx_DeleteBucket_ReadOnly(t *testing.T) { 435 db := MustOpenDB() 436 defer db.MustClose() 437 if err := db.View(func(tx *memcache.Tx) error { 438 if err := tx.DeleteBucket([]byte("foo")); err != memcache.ErrTxNotWritable { 439 t.Fatalf("unexpected error: %s", err) 440 } 441 return nil 442 }); err != nil { 443 t.Fatal(err) 444 } 445 } 446 447 // Ensure that nothing happens when deleting a bucket that doesn't exist. 448 func TestTx_DeleteBucket_NotFound(t *testing.T) { 449 db := MustOpenDB() 450 defer db.MustClose() 451 if err := db.Update(func(tx *memcache.Tx) error { 452 if err := tx.DeleteBucket([]byte("widgets")); err != memcache.ErrBucketNotFound { 453 t.Fatalf("unexpected error: %s", err) 454 } 455 return nil 456 }); err != nil { 457 t.Fatal(err) 458 } 459 } 460 461 // Ensure that no error is returned when a tx.ForEach function does not return 462 // an error. 463 func TestTx_ForEach_NoError(t *testing.T) { 464 db := MustOpenDB() 465 defer db.MustClose() 466 if err := db.Update(func(tx *memcache.Tx) error { 467 b, err := tx.CreateBucket([]byte("widgets")) 468 if err != nil { 469 t.Fatal(err) 470 } 471 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 472 t.Fatal(err) 473 } 474 475 if err := tx.ForEach(func(name []byte, b *memcache.Bucket) error { 476 return nil 477 }); err != nil { 478 t.Fatal(err) 479 } 480 return nil 481 }); err != nil { 482 t.Fatal(err) 483 } 484 } 485 486 // Ensure that an error is returned when a tx.ForEach function returns an error. 487 func TestTx_ForEach_WithError(t *testing.T) { 488 db := MustOpenDB() 489 defer db.MustClose() 490 if err := db.Update(func(tx *memcache.Tx) error { 491 b, err := tx.CreateBucket([]byte("widgets")) 492 if err != nil { 493 t.Fatal(err) 494 } 495 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 496 t.Fatal(err) 497 } 498 499 marker := errors.New("marker") 500 if err := tx.ForEach(func(name []byte, b *memcache.Bucket) error { 501 return marker 502 }); err != marker { 503 t.Fatalf("unexpected error: %s", err) 504 } 505 return nil 506 }); err != nil { 507 t.Fatal(err) 508 } 509 } 510 511 // Ensure that Tx commit handlers are called after a transaction successfully commits. 512 func TestTx_OnCommit(t *testing.T) { 513 db := MustOpenDB() 514 defer db.MustClose() 515 516 var x int 517 if err := db.Update(func(tx *memcache.Tx) error { 518 tx.OnCommit(func() { x += 1 }) 519 tx.OnCommit(func() { x += 2 }) 520 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 521 t.Fatal(err) 522 } 523 return nil 524 }); err != nil { 525 t.Fatal(err) 526 } else if x != 3 { 527 t.Fatalf("unexpected x: %d", x) 528 } 529 } 530 531 // Ensure that Tx commit handlers are NOT called after a transaction rolls back. 532 func TestTx_OnCommit_Rollback(t *testing.T) { 533 db := MustOpenDB() 534 defer db.MustClose() 535 536 var x int 537 if err := db.Update(func(tx *memcache.Tx) error { 538 tx.OnCommit(func() { x += 1 }) 539 tx.OnCommit(func() { x += 2 }) 540 if _, err := tx.CreateBucket([]byte("widgets")); err != nil { 541 t.Fatal(err) 542 } 543 return errors.New("rollback this commit") 544 }); err == nil || err.Error() != "rollback this commit" { 545 t.Fatalf("unexpected error: %s", err) 546 } else if x != 0 { 547 t.Fatalf("unexpected x: %d", x) 548 } 549 } 550 551 // Ensure that the database can be copied to a file path. 552 func TestTx_CopyFile(t *testing.T) { 553 db := MustOpenDB() 554 defer db.MustClose() 555 556 path := tempfile() 557 if err := db.Update(func(tx *memcache.Tx) error { 558 b, err := tx.CreateBucket([]byte("widgets")) 559 if err != nil { 560 t.Fatal(err) 561 } 562 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 563 t.Fatal(err) 564 } 565 if err := b.Put([]byte("baz"), []byte("bat")); err != nil { 566 t.Fatal(err) 567 } 568 return nil 569 }); err != nil { 570 t.Fatal(err) 571 } 572 573 if err := db.View(func(tx *memcache.Tx) error { 574 return tx.CopyFile(path, 0600) 575 }); err != nil { 576 t.Fatal(err) 577 } 578 579 db2, err := memcache.Open(path, 0600, nil) 580 if err != nil { 581 t.Fatal(err) 582 } 583 584 if err := db2.View(func(tx *memcache.Tx) error { 585 if v := tx.Bucket([]byte("widgets")).Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) { 586 t.Fatalf("unexpected value: %v", v) 587 } 588 if v := tx.Bucket([]byte("widgets")).Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) { 589 t.Fatalf("unexpected value: %v", v) 590 } 591 return nil 592 }); err != nil { 593 t.Fatal(err) 594 } 595 596 if err := db2.Close(); err != nil { 597 t.Fatal(err) 598 } 599 } 600 601 type failWriterError struct{} 602 603 func (failWriterError) Error() string { 604 return "error injected for tests" 605 } 606 607 type failWriter struct { 608 // fail after this many bytes 609 After int 610 } 611 612 func (f *failWriter) Write(p []byte) (n int, err error) { 613 n = len(p) 614 if n > f.After { 615 n = f.After 616 err = failWriterError{} 617 } 618 f.After -= n 619 return n, err 620 } 621 622 // Ensure that Copy handles write errors right. 623 func TestTx_CopyFile_Error_Meta(t *testing.T) { 624 db := MustOpenDB() 625 defer db.MustClose() 626 if err := db.Update(func(tx *memcache.Tx) error { 627 b, err := tx.CreateBucket([]byte("widgets")) 628 if err != nil { 629 t.Fatal(err) 630 } 631 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 632 t.Fatal(err) 633 } 634 if err := b.Put([]byte("baz"), []byte("bat")); err != nil { 635 t.Fatal(err) 636 } 637 return nil 638 }); err != nil { 639 t.Fatal(err) 640 } 641 642 if err := db.View(func(tx *memcache.Tx) error { 643 return tx.Copy(&failWriter{}) 644 }); err == nil || err.Error() != "meta 0 copy: error injected for tests" { 645 t.Fatalf("unexpected error: %v", err) 646 } 647 } 648 649 // Ensure that Copy handles write errors right. 650 func TestTx_CopyFile_Error_Normal(t *testing.T) { 651 db := MustOpenDB() 652 defer db.MustClose() 653 if err := db.Update(func(tx *memcache.Tx) error { 654 b, err := tx.CreateBucket([]byte("widgets")) 655 if err != nil { 656 t.Fatal(err) 657 } 658 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 659 t.Fatal(err) 660 } 661 if err := b.Put([]byte("baz"), []byte("bat")); err != nil { 662 t.Fatal(err) 663 } 664 return nil 665 }); err != nil { 666 t.Fatal(err) 667 } 668 669 if err := db.View(func(tx *memcache.Tx) error { 670 return tx.Copy(&failWriter{3 * db.Info().PageSize}) 671 }); err == nil || err.Error() != "error injected for tests" { 672 t.Fatalf("unexpected error: %v", err) 673 } 674 } 675 676 // TestTx_Rollback ensures there is no error when tx rollback whether we sync freelist or not. 677 func TestTx_Rollback(t *testing.T) { 678 for _, isSyncFreelist := range []bool{false, true} { 679 // Open the database. 680 db, err := memcache.Open(tempfile(), 0666, nil) 681 if err != nil { 682 log.Fatal(err) 683 } 684 defer os.Remove(db.Path()) 685 db.NoFreelistSync = isSyncFreelist 686 687 tx, err := db.Begin(true) 688 if err != nil { 689 t.Fatalf("Error starting tx: %v", err) 690 } 691 bucket := []byte("mybucket") 692 if _, err := tx.CreateBucket(bucket); err != nil { 693 t.Fatalf("Error creating bucket: %v", err) 694 } 695 if err := tx.Commit(); err != nil { 696 t.Fatalf("Error on commit: %v", err) 697 } 698 699 tx, err = db.Begin(true) 700 if err != nil { 701 t.Fatalf("Error starting tx: %v", err) 702 } 703 b := tx.Bucket(bucket) 704 if err := b.Put([]byte("k"), []byte("v")); err != nil { 705 t.Fatalf("Error on put: %v", err) 706 } 707 // Imagine there is an error and tx needs to be rolled-back 708 if err := tx.Rollback(); err != nil { 709 t.Fatalf("Error on rollback: %v", err) 710 } 711 712 tx, err = db.Begin(false) 713 if err != nil { 714 t.Fatalf("Error starting tx: %v", err) 715 } 716 b = tx.Bucket(bucket) 717 if v := b.Get([]byte("k")); v != nil { 718 t.Fatalf("Value for k should not have been stored") 719 } 720 if err := tx.Rollback(); err != nil { 721 t.Fatalf("Error on rollback: %v", err) 722 } 723 724 } 725 } 726 727 // TestTx_releaseRange ensures db.freePages handles page releases 728 // correctly when there are transaction that are no longer reachable 729 // via any read/write transactions and are "between" ongoing read 730 // transactions, which requires they must be freed by 731 // freelist.releaseRange. 732 func TestTx_releaseRange(t *testing.T) { 733 // Set initial mmap size well beyond the limit we will hit in this 734 // test, since we are testing with long running read transactions 735 // and will deadlock if db.grow is triggered. 736 db := MustOpenWithOption(&memcache.Options{InitialMmapSize: os.Getpagesize() * 100}) 737 defer db.MustClose() 738 739 bucket := "bucket" 740 741 put := func(key, value string) { 742 if err := db.Update(func(tx *memcache.Tx) error { 743 b, err := tx.CreateBucketIfNotExists([]byte(bucket)) 744 if err != nil { 745 t.Fatal(err) 746 } 747 return b.Put([]byte(key), []byte(value)) 748 }); err != nil { 749 t.Fatal(err) 750 } 751 } 752 753 del := func(key string) { 754 if err := db.Update(func(tx *memcache.Tx) error { 755 b, err := tx.CreateBucketIfNotExists([]byte(bucket)) 756 if err != nil { 757 t.Fatal(err) 758 } 759 return b.Delete([]byte(key)) 760 }); err != nil { 761 t.Fatal(err) 762 } 763 } 764 765 getWithTxn := func(txn *memcache.Tx, key string) []byte { 766 return txn.Bucket([]byte(bucket)).Get([]byte(key)) 767 } 768 769 openReadTxn := func() *memcache.Tx { 770 readTx, err := db.Begin(false) 771 if err != nil { 772 t.Fatal(err) 773 } 774 return readTx 775 } 776 777 checkWithReadTxn := func(txn *memcache.Tx, key string, wantValue []byte) { 778 value := getWithTxn(txn, key) 779 if !bytes.Equal(value, wantValue) { 780 t.Errorf("Wanted value to be %s for key %s, but got %s", wantValue, key, string(value)) 781 } 782 } 783 784 rollback := func(txn *memcache.Tx) { 785 if err := txn.Rollback(); err != nil { 786 t.Fatal(err) 787 } 788 } 789 790 put("k1", "v1") 791 rtx1 := openReadTxn() 792 put("k2", "v2") 793 hold1 := openReadTxn() 794 put("k3", "v3") 795 hold2 := openReadTxn() 796 del("k3") 797 rtx2 := openReadTxn() 798 del("k1") 799 hold3 := openReadTxn() 800 del("k2") 801 hold4 := openReadTxn() 802 put("k4", "v4") 803 hold5 := openReadTxn() 804 805 // Close the read transactions we established to hold a portion of the pages in pending state. 806 rollback(hold1) 807 rollback(hold2) 808 rollback(hold3) 809 rollback(hold4) 810 rollback(hold5) 811 812 // Execute a write transaction to trigger a releaseRange operation in the db 813 // that will free multiple ranges between the remaining open read transactions, now that the 814 // holds have been rolled back. 815 put("k4", "v4") 816 817 // Check that all long running reads still read correct values. 818 checkWithReadTxn(rtx1, "k1", []byte("v1")) 819 checkWithReadTxn(rtx2, "k2", []byte("v2")) 820 rollback(rtx1) 821 rollback(rtx2) 822 823 // Check that the final state is correct. 824 rtx7 := openReadTxn() 825 checkWithReadTxn(rtx7, "k1", nil) 826 checkWithReadTxn(rtx7, "k2", nil) 827 checkWithReadTxn(rtx7, "k3", nil) 828 checkWithReadTxn(rtx7, "k4", []byte("v4")) 829 rollback(rtx7) 830 } 831 832 func ExampleTx_Rollback() { 833 // Open the database. 834 db, err := memcache.Open(tempfile(), 0666, nil) 835 if err != nil { 836 log.Fatal(err) 837 } 838 defer os.Remove(db.Path()) 839 840 // Create a bucket. 841 if err := db.Update(func(tx *memcache.Tx) error { 842 _, err := tx.CreateBucket([]byte("widgets")) 843 return err 844 }); err != nil { 845 log.Fatal(err) 846 } 847 848 // Set a value for a key. 849 if err := db.Update(func(tx *memcache.Tx) error { 850 return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) 851 }); err != nil { 852 log.Fatal(err) 853 } 854 855 // Update the key but rollback the transaction so it never saves. 856 tx, err := db.Begin(true) 857 if err != nil { 858 log.Fatal(err) 859 } 860 b := tx.Bucket([]byte("widgets")) 861 if err := b.Put([]byte("foo"), []byte("baz")); err != nil { 862 log.Fatal(err) 863 } 864 if err := tx.Rollback(); err != nil { 865 log.Fatal(err) 866 } 867 868 // Ensure that our original value is still set. 869 if err := db.View(func(tx *memcache.Tx) error { 870 value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) 871 fmt.Printf("The value for 'foo' is still: %s\n", value) 872 return nil 873 }); err != nil { 874 log.Fatal(err) 875 } 876 877 // Close database to release file lock. 878 if err := db.Close(); err != nil { 879 log.Fatal(err) 880 } 881 882 // Output: 883 // The value for 'foo' is still: bar 884 } 885 886 func ExampleTx_CopyFile() { 887 // Open the database. 888 db, err := memcache.Open(tempfile(), 0666, nil) 889 if err != nil { 890 log.Fatal(err) 891 } 892 defer os.Remove(db.Path()) 893 894 // Create a bucket and a key. 895 if err := db.Update(func(tx *memcache.Tx) error { 896 b, err := tx.CreateBucket([]byte("widgets")) 897 if err != nil { 898 return err 899 } 900 if err := b.Put([]byte("foo"), []byte("bar")); err != nil { 901 return err 902 } 903 return nil 904 }); err != nil { 905 log.Fatal(err) 906 } 907 908 // Copy the database to another file. 909 toFile := tempfile() 910 if err := db.View(func(tx *memcache.Tx) error { 911 return tx.CopyFile(toFile, 0666) 912 }); err != nil { 913 log.Fatal(err) 914 } 915 defer os.Remove(toFile) 916 917 // Open the cloned database. 918 db2, err := memcache.Open(toFile, 0666, nil) 919 if err != nil { 920 log.Fatal(err) 921 } 922 923 // Ensure that the key exists in the copy. 924 if err := db2.View(func(tx *memcache.Tx) error { 925 value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) 926 fmt.Printf("The value for 'foo' in the clone is: %s\n", value) 927 return nil 928 }); err != nil { 929 log.Fatal(err) 930 } 931 932 // Close database to release file lock. 933 if err := db.Close(); err != nil { 934 log.Fatal(err) 935 } 936 937 if err := db2.Close(); err != nil { 938 log.Fatal(err) 939 } 940 941 // Output: 942 // The value for 'foo' in the clone is: bar 943 }