1 /* 2 * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "block_cache_private.h" 8 9 #include <KernelExport.h> 10 #include <fs_cache.h> 11 12 #include <block_cache.h> 13 #include <lock.h> 14 #include <util/kernel_cpp.h> 15 #include <util/DoublyLinkedList.h> 16 #include <util/AutoLock.h> 17 #include <util/khash.h> 18 19 #include <unistd.h> 20 #include <stdlib.h> 21 #include <string.h> 22 #include <errno.h> 23 24 25 // ToDo: this is a naive but growing implementation to test the API: 26 // 1) block reading/writing is not at all optimized for speed, it will 27 // just read and write single blocks. 28 // 2) the locking could be improved; getting a block should not need to 29 // wait for blocks to be written 30 // 3) dirty blocks are only written back if asked for 31 // 4) blocks are never removed yet 32 33 //#define TRACE_BLOCK_CACHE 34 #ifdef TRACE_BLOCK_CACHE 35 # define TRACE(x) dprintf x 36 #else 37 # define TRACE(x) ; 38 #endif 39 40 41 struct cache_transaction { 42 cache_transaction *next; 43 int32 id; 44 int32 num_blocks; 45 cached_block *first_block; 46 block_list blocks; 47 transaction_notification_hook notification_hook; 48 void *notification_data; 49 bool open; 50 }; 51 52 static status_t write_cached_block(block_cache *cache, cached_block *block, bool deleteTransaction = true); 53 54 55 // #pragma mark - private transaction 56 57 58 static int 59 transaction_compare(void *_transaction, const void *_id) 60 { 61 cache_transaction *transaction = (cache_transaction *)_transaction; 62 const int32 *id = (const int32 *)_id; 63 64 return transaction->id - *id; 65 } 66 67 68 static uint32 69 transaction_hash(void *_transaction, const void *_id, uint32 range) 70 { 71 cache_transaction *transaction = (cache_transaction *)_transaction; 72 const int32 *id = (const int32 *)_id; 73 74 if (transaction != NULL) 75 return transaction->id % range; 76 77 return (uint32)*id % range; 78 } 79 80 81 static void 82 delete_transaction(block_cache *cache, cache_transaction *transaction) 83 { 84 hash_remove(cache->transaction_hash, transaction); 85 if (cache->last_transaction == transaction) 86 cache->last_transaction = NULL; 87 88 delete transaction; 89 } 90 91 92 static cache_transaction * 93 lookup_transaction(block_cache *cache, int32 id) 94 { 95 return (cache_transaction *)hash_lookup(cache->transaction_hash, &id); 96 } 97 98 99 // #pragma mark - private block_cache 100 101 102 /* static */ 103 int 104 cached_block::Compare(void *_cacheEntry, const void *_block) 105 { 106 cached_block *cacheEntry = (cached_block *)_cacheEntry; 107 const off_t *block = (const off_t *)_block; 108 109 return cacheEntry->block_number - *block; 110 } 111 112 113 114 /* static */ 115 uint32 116 cached_block::Hash(void *_cacheEntry, const void *_block, uint32 range) 117 { 118 cached_block *cacheEntry = (cached_block *)_cacheEntry; 119 const off_t *block = (const off_t *)_block; 120 121 if (cacheEntry != NULL) 122 return cacheEntry->block_number % range; 123 124 return (uint64)*block % range; 125 } 126 127 128 // #pragma mark - 129 130 131 block_cache::block_cache(int _fd, off_t numBlocks, size_t blockSize) 132 : 133 hash(NULL), 134 fd(_fd), 135 max_blocks(numBlocks), 136 block_size(blockSize), 137 next_transaction_id(1), 138 last_transaction(NULL), 139 transaction_hash(NULL), 140 ranges_hash(NULL), 141 free_ranges(NULL) 142 { 143 hash = hash_init(32, 0, &cached_block::Compare, &cached_block::Hash); 144 if (hash == NULL) 145 return; 146 147 transaction_hash = hash_init(16, 0, &transaction_compare, &::transaction_hash); 148 if (transaction_hash == NULL) 149 return; 150 151 ranges_hash = hash_init(16, 0, &block_range::Compare, &block_range::Hash); 152 if (ranges_hash == NULL) 153 return; 154 155 if (benaphore_init(&lock, "block cache") < B_OK) 156 return; 157 158 chunk_size = max_c(blockSize, B_PAGE_SIZE); 159 chunks_per_range = kBlockRangeSize / chunk_size; 160 range_mask = (1UL << chunks_per_range) - 1; 161 chunk_mask = (1UL << (chunk_size / blockSize)) - 1; 162 } 163 164 165 block_cache::~block_cache() 166 { 167 benaphore_destroy(&lock); 168 169 hash_uninit(ranges_hash); 170 hash_uninit(transaction_hash); 171 hash_uninit(hash); 172 } 173 174 175 status_t 176 block_cache::InitCheck() 177 { 178 if (lock.sem < B_OK) 179 return lock.sem; 180 181 if (hash == NULL || transaction_hash == NULL || ranges_hash == NULL) 182 return B_NO_MEMORY; 183 184 return B_OK; 185 } 186 187 188 block_range * 189 block_cache::GetFreeRange() 190 { 191 if (free_ranges != NULL) 192 return free_ranges; 193 194 // we need to allocate a new range 195 block_range *range; 196 if (block_range::NewBlockRange(this, &range) != B_OK) { 197 // ToDo: free up space in existing ranges 198 // We may also need to free ranges from other caches to get a free one 199 // (if not, an active volume might have stolen all free ranges already) 200 return NULL; 201 } 202 203 hash_insert(ranges_hash, range); 204 return range; 205 } 206 207 208 block_range * 209 block_cache::GetRange(void *address) 210 { 211 return (block_range *)hash_lookup(ranges_hash, address); 212 } 213 214 215 void 216 block_cache::Free(void *address) 217 { 218 if (address == NULL) 219 return; 220 221 block_range *range = GetRange(address); 222 ASSERT(range != NULL); 223 range->Free(this, address); 224 } 225 226 227 void * 228 block_cache::Allocate() 229 { 230 block_range *range = GetFreeRange(); 231 if (range == NULL) 232 return NULL; 233 234 return range->Allocate(this); 235 } 236 237 238 void 239 block_cache::FreeBlock(cached_block *block) 240 { 241 block_range *range = GetRange(block->data); 242 ASSERT(range != NULL); 243 range->Free(this, block); 244 245 Free(block->original); 246 #ifdef DEBUG_CHANGED 247 Free(block->compare); 248 #endif 249 250 free(block); 251 } 252 253 254 cached_block * 255 block_cache::NewBlock(off_t blockNumber) 256 { 257 cached_block *block = (cached_block *)malloc(sizeof(cached_block)); 258 if (block == NULL) 259 return NULL; 260 261 block_range *range = GetFreeRange(); 262 if (range == NULL) { 263 free(block); 264 return NULL; 265 } 266 267 range->Allocate(this, block); 268 269 block->block_number = blockNumber; 270 block->lock = 0; 271 block->transaction_next = NULL; 272 block->transaction = block->previous_transaction = NULL; 273 block->original = NULL; 274 block->is_dirty = false; 275 #ifdef DEBUG_CHANGED 276 block->compare = NULL; 277 #endif 278 279 hash_insert(hash, block); 280 281 return block; 282 } 283 284 285 #ifdef DEBUG_CHANGED 286 287 #define DUMPED_BLOCK_SIZE 16 288 289 void 290 dumpBlock(const char *buffer, int size, const char *prefix) 291 { 292 int i; 293 294 for (i = 0; i < size;) { 295 int start = i; 296 297 dprintf(prefix); 298 for (; i < start+DUMPED_BLOCK_SIZE; i++) { 299 if (!(i % 4)) 300 dprintf(" "); 301 302 if (i >= size) 303 dprintf(" "); 304 else 305 dprintf("%02x", *(unsigned char *)(buffer + i)); 306 } 307 dprintf(" "); 308 309 for (i = start; i < start + DUMPED_BLOCK_SIZE; i++) { 310 if (i < size) { 311 char c = buffer[i]; 312 313 if (c < 30) 314 dprintf("."); 315 else 316 dprintf("%c", c); 317 } else 318 break; 319 } 320 dprintf("\n"); 321 } 322 } 323 #endif 324 325 326 static void 327 put_cached_block(block_cache *cache, cached_block *block) 328 { 329 #ifdef DEBUG_CHANGED 330 if (!block->is_dirty && block->compare != NULL && memcmp(block->data, block->compare, cache->block_size)) { 331 dprintf("new block:\n"); 332 dumpBlock((const char *)block->data, 256, " "); 333 dprintf("unchanged block:\n"); 334 dumpBlock((const char *)block->compare, 256, " "); 335 write_cached_block(cache, block); 336 panic("block_cache: supposed to be clean block was changed!\n"); 337 338 cache->Free(block->compare); 339 block->compare = NULL; 340 } 341 #endif 342 343 if (--block->lock == 0) 344 ; 345 // block->data = cache->allocator->Release(block->data); 346 } 347 348 349 static void 350 put_cached_block(block_cache *cache, off_t blockNumber) 351 { 352 cached_block *block = (cached_block *)hash_lookup(cache->hash, &blockNumber); 353 if (block != NULL) 354 put_cached_block(cache, block); 355 } 356 357 358 static cached_block * 359 get_cached_block(block_cache *cache, off_t blockNumber, bool &allocated, bool readBlock = true) 360 { 361 cached_block *block = (cached_block *)hash_lookup(cache->hash, &blockNumber); 362 allocated = false; 363 364 if (block == NULL) { 365 // read block into cache 366 block = cache->NewBlock(blockNumber); 367 if (block == NULL) 368 return NULL; 369 370 allocated = true; 371 } else { 372 /* 373 if (block->lock == 0 && block->data != NULL) { 374 // see if the old block can be resurrected 375 block->data = cache->allocator->Acquire(block->data); 376 } 377 378 if (block->data == NULL) { 379 // there is no block yet, but we need one 380 block->data = cache->allocator->Get(); 381 if (block->data == NULL) 382 return NULL; 383 384 allocated = true; 385 } 386 */ 387 } 388 389 if (allocated && readBlock) { 390 int32 blockSize = cache->block_size; 391 392 if (read_pos(cache->fd, blockNumber * blockSize, block->data, blockSize) < blockSize) { 393 cache->FreeBlock(block); 394 return NULL; 395 } 396 } 397 398 block->lock++; 399 return block; 400 } 401 402 403 static void * 404 get_writable_cached_block(block_cache *cache, off_t blockNumber, off_t base, off_t length, 405 int32 transactionID, bool cleared) 406 { 407 BenaphoreLocker locker(&cache->lock); 408 409 TRACE(("get_writable_cached_block(blockNumber = %Ld, transaction = %ld)\n", blockNumber, transactionID)); 410 411 bool allocated; 412 cached_block *block = get_cached_block(cache, blockNumber, allocated, !cleared); 413 if (block == NULL) 414 return NULL; 415 416 // if there is no transaction support, we just return the current block 417 if (transactionID == -1) { 418 if (cleared) 419 memset(block->data, 0, cache->block_size); 420 421 block->is_dirty = true; 422 // mark the block as dirty 423 424 return block->data; 425 } 426 427 // ToDo: note, even if we panic, we should probably put the cached block 428 // back before we return 429 430 if (block->transaction != NULL && block->transaction->id != transactionID) { 431 // ToDo: we have to wait here until the other transaction is done. 432 // Maybe we should even panic, since we can't prevent any deadlocks. 433 panic("get_writable_cached_block(): asked to get busy writable block (transaction %ld)\n", block->transaction->id); 434 return NULL; 435 } 436 if (block->transaction == NULL && transactionID != -1) { 437 // get new transaction 438 cache_transaction *transaction = lookup_transaction(cache, transactionID); 439 if (transaction == NULL) { 440 panic("get_writable_cached_block(): invalid transaction %ld!\n", transactionID); 441 return NULL; 442 } 443 if (!transaction->open) { 444 panic("get_writable_cached_block(): transaction already done!\n"); 445 return NULL; 446 } 447 448 block->transaction = transaction; 449 450 // attach the block to the transaction block list 451 block->transaction_next = transaction->first_block; 452 transaction->first_block = block; 453 transaction->num_blocks++; 454 } 455 456 if (!(allocated && cleared) && block->original == NULL) { 457 // we already have data, so we need to save it 458 block->original = cache->Allocate(); 459 if (block->original == NULL) { 460 put_cached_block(cache, block); 461 return NULL; 462 } 463 464 memcpy(block->original, block->data, cache->block_size); 465 } 466 467 if (cleared) 468 memset(block->data, 0, cache->block_size); 469 470 block->is_dirty = true; 471 472 return block->data; 473 } 474 475 476 static status_t 477 write_cached_block(block_cache *cache, cached_block *block, bool deleteTransaction) 478 { 479 cache_transaction *previous = block->previous_transaction; 480 int32 blockSize = cache->block_size; 481 482 void *data = previous && block->original ? block->original : block->data; 483 // we first need to write back changes from previous transactions 484 485 TRACE(("write_cached_block(block %Ld)\n", block->block_number)); 486 487 ssize_t written = write_pos(cache->fd, block->block_number * blockSize, data, blockSize); 488 489 if (written < blockSize) { 490 dprintf("could not write back block %Ld (%s)\n", block->block_number, strerror(errno)); 491 return B_IO_ERROR; 492 } 493 494 if (data == block->data) 495 block->is_dirty = false; 496 497 if (previous != NULL) { 498 previous->blocks.Remove(block); 499 block->previous_transaction = NULL; 500 501 // Has the previous transation been finished with that write? 502 if (--previous->num_blocks == 0) { 503 TRACE(("cache transaction %ld finished!\n", previous->id)); 504 505 if (previous->notification_hook != NULL) 506 previous->notification_hook(previous->id, previous->notification_data); 507 508 if (deleteTransaction) 509 delete_transaction(cache, previous); 510 } 511 } 512 513 return B_OK; 514 } 515 516 517 extern "C" status_t 518 block_cache_init(void) 519 { 520 return init_block_allocator(); 521 } 522 523 524 // #pragma mark - public transaction 525 526 527 extern "C" int32 528 cache_start_transaction(void *_cache) 529 { 530 block_cache *cache = (block_cache *)_cache; 531 532 if (cache->last_transaction && cache->last_transaction->open) 533 panic("last transaction (%ld) still open!\n", cache->last_transaction->id); 534 535 cache_transaction *transaction = new cache_transaction; 536 if (transaction == NULL) 537 return B_NO_MEMORY; 538 539 transaction->id = atomic_add(&cache->next_transaction_id, 1); 540 transaction->num_blocks = 0; 541 transaction->first_block = NULL; 542 transaction->notification_hook = NULL; 543 transaction->notification_data = NULL; 544 transaction->open = true; 545 cache->last_transaction = transaction; 546 547 TRACE(("cache_transaction_start(): id %ld started\n", transaction->id)); 548 549 BenaphoreLocker locker(&cache->lock); 550 hash_insert(cache->transaction_hash, transaction); 551 552 return transaction->id; 553 } 554 555 556 extern "C" status_t 557 cache_sync_transaction(void *_cache, int32 id) 558 { 559 block_cache *cache = (block_cache *)_cache; 560 BenaphoreLocker locker(&cache->lock); 561 status_t status = B_ENTRY_NOT_FOUND; 562 563 hash_iterator iterator; 564 hash_open(cache->transaction_hash, &iterator); 565 566 cache_transaction *transaction; 567 while ((transaction = (cache_transaction *)hash_next(cache->transaction_hash, &iterator)) != NULL) { 568 // ToDo: fix hash interface to make this easier 569 570 if (transaction->id <= id && !transaction->open) { 571 while (transaction->num_blocks > 0) { 572 status = write_cached_block(cache, transaction->blocks.Head(), false); 573 if (status != B_OK) 574 return status; 575 } 576 delete_transaction(cache, transaction); 577 hash_rewind(cache->transaction_hash, &iterator); 578 } 579 } 580 581 hash_close(cache->transaction_hash, &iterator, false); 582 return B_OK; 583 } 584 585 586 extern "C" status_t 587 cache_end_transaction(void *_cache, int32 id, transaction_notification_hook hook, void *data) 588 { 589 block_cache *cache = (block_cache *)_cache; 590 BenaphoreLocker locker(&cache->lock); 591 592 TRACE(("cache_end_transaction(id = %ld)\n", id)); 593 594 cache_transaction *transaction = lookup_transaction(cache, id); 595 if (transaction == NULL) { 596 panic("cache_end_transaction(): invalid transaction ID\n"); 597 return B_BAD_VALUE; 598 } 599 600 transaction->notification_hook = hook; 601 transaction->notification_data = data; 602 603 // iterate through all blocks and free the unchanged original contents 604 605 cached_block *block = transaction->first_block, *next; 606 for (; block != NULL; block = next) { 607 next = block->transaction_next; 608 609 if (block->previous_transaction != NULL) { 610 // need to write back pending changes 611 write_cached_block(cache, block); 612 } 613 614 if (block->original != NULL) { 615 cache->Free(block->original); 616 block->original = NULL; 617 } 618 619 // move the block to the previous transaction list 620 transaction->blocks.Add(block); 621 622 block->previous_transaction = transaction; 623 block->transaction_next = NULL; 624 block->transaction = NULL; 625 } 626 627 transaction->open = false; 628 629 return B_OK; 630 } 631 632 633 extern "C" status_t 634 cache_abort_transaction(void *_cache, int32 id) 635 { 636 block_cache *cache = (block_cache *)_cache; 637 BenaphoreLocker locker(&cache->lock); 638 639 TRACE(("cache_abort_transaction(id = %ld)\n", id)); 640 641 cache_transaction *transaction = lookup_transaction(cache, id); 642 if (transaction == NULL) { 643 panic("cache_abort_transaction(): invalid transaction ID\n"); 644 return B_BAD_VALUE; 645 } 646 647 // iterate through all blocks and restore their original contents 648 649 cached_block *block = transaction->first_block, *next; 650 for (; block != NULL; block = next) { 651 next = block->transaction_next; 652 653 if (block->original != NULL) { 654 TRACE(("cache_abort_transaction(id = %ld): restored contents of block %Ld\n", 655 transaction->id, block->block_number)); 656 memcpy(block->data, block->original, cache->block_size); 657 cache->Free(block->original); 658 block->original = NULL; 659 } 660 661 block->transaction_next = NULL; 662 block->transaction = NULL; 663 } 664 665 delete_transaction(cache, transaction); 666 return B_OK; 667 } 668 669 670 extern "C" int32 671 cache_detach_sub_transaction(void *_cache, int32 id) 672 { 673 return B_ERROR; 674 } 675 676 677 extern "C" status_t 678 cache_abort_sub_transaction(void *_cache, int32 id) 679 { 680 return B_ERROR; 681 } 682 683 684 extern "C" status_t 685 cache_start_sub_transaction(void *_cache, int32 id) 686 { 687 return B_ERROR; 688 } 689 690 691 extern "C" status_t 692 cache_next_block_in_transaction(void *_cache, int32 id, uint32 *_cookie, off_t *_blockNumber, 693 void **_data, void **_unchangedData) 694 { 695 cached_block *block = (cached_block *)*_cookie; 696 block_cache *cache = (block_cache *)_cache; 697 698 BenaphoreLocker locker(&cache->lock); 699 700 cache_transaction *transaction = lookup_transaction(cache, id); 701 if (transaction == NULL) 702 return B_BAD_VALUE; 703 704 if (block == NULL) 705 block = transaction->first_block; 706 else 707 block = block->transaction_next; 708 709 if (block == NULL) 710 return B_ENTRY_NOT_FOUND; 711 712 if (_blockNumber) 713 *_blockNumber = block->block_number; 714 if (_data) 715 *_data = block->data; 716 if (_unchangedData) 717 *_unchangedData = block->original; 718 719 *_cookie = (uint32)block; 720 return B_OK; 721 } 722 723 724 // #pragma mark - public block cache 725 // public interface 726 727 728 extern "C" void 729 block_cache_delete(void *_cache, bool allowWrites) 730 { 731 block_cache *cache = (block_cache *)_cache; 732 733 if (allowWrites) 734 block_cache_sync(cache); 735 736 // free all blocks 737 738 uint32 cookie = 0; 739 cached_block *block; 740 while ((block = (cached_block *)hash_remove_first(cache->hash, &cookie)) != NULL) { 741 cache->FreeBlock(block); 742 } 743 744 // free all transactions (they will all be aborted) 745 746 cookie = 0; 747 cache_transaction *transaction; 748 while ((transaction = (cache_transaction *)hash_remove_first(cache->transaction_hash, &cookie)) != NULL) { 749 delete transaction; 750 } 751 752 delete cache; 753 } 754 755 756 extern "C" void * 757 block_cache_create(int fd, off_t numBlocks, size_t blockSize) 758 { 759 block_cache *cache = new block_cache(fd, numBlocks, blockSize); 760 if (cache == NULL) 761 return NULL; 762 763 if (cache->InitCheck() != B_OK) { 764 delete cache; 765 return NULL; 766 } 767 768 return cache; 769 } 770 771 772 extern "C" status_t 773 block_cache_sync(void *_cache) 774 { 775 block_cache *cache = (block_cache *)_cache; 776 777 // we will sync all dirty blocks to disk that have a completed 778 // transaction or no transaction only 779 780 BenaphoreLocker locker(&cache->lock); 781 hash_iterator iterator; 782 hash_open(cache->hash, &iterator); 783 784 cached_block *block; 785 while ((block = (cached_block *)hash_next(cache->hash, &iterator)) != NULL) { 786 if (block->previous_transaction != NULL 787 || (block->transaction == NULL && block->is_dirty)) { 788 status_t status = write_cached_block(cache, block); 789 if (status != B_OK) 790 return status; 791 } 792 } 793 794 hash_close(cache->hash, &iterator, false); 795 return B_OK; 796 } 797 798 799 extern "C" status_t 800 block_cache_make_writable(void *_cache, off_t blockNumber, int32 transaction) 801 { 802 // ToDo: this can be done better! 803 void *block = block_cache_get_writable_etc(_cache, blockNumber, blockNumber, 1, transaction); 804 if (block != NULL) { 805 put_cached_block((block_cache *)_cache, blockNumber); 806 return B_OK; 807 } 808 809 return B_ERROR; 810 } 811 812 813 extern "C" void * 814 block_cache_get_writable_etc(void *_cache, off_t blockNumber, off_t base, off_t length, 815 int32 transaction) 816 { 817 TRACE(("block_cache_get_writable_etc(block = %Ld, transaction = %ld)\n", blockNumber, transaction)); 818 819 return get_writable_cached_block((block_cache *)_cache, blockNumber, 820 base, length, transaction, false); 821 } 822 823 824 extern "C" void * 825 block_cache_get_writable(void *_cache, off_t blockNumber, int32 transaction) 826 { 827 return block_cache_get_writable_etc(_cache, blockNumber, blockNumber, 1, transaction); 828 } 829 830 831 extern "C" void * 832 block_cache_get_empty(void *_cache, off_t blockNumber, int32 transaction) 833 { 834 TRACE(("block_cache_get_empty(block = %Ld, transaction = %ld)\n", blockNumber, transaction)); 835 836 return get_writable_cached_block((block_cache *)_cache, blockNumber, 837 blockNumber, 1, transaction, true); 838 } 839 840 841 extern "C" const void * 842 block_cache_get_etc(void *_cache, off_t blockNumber, off_t base, off_t length) 843 { 844 block_cache *cache = (block_cache *)_cache; 845 BenaphoreLocker locker(&cache->lock); 846 bool allocated; 847 848 cached_block *block = get_cached_block(cache, blockNumber, allocated); 849 if (block == NULL) 850 return NULL; 851 852 #ifdef DEBUG_CHANGED 853 if (block->compare == NULL) 854 block->compare = cache->Allocate(); 855 if (block->compare != NULL) 856 memcpy(block->compare, block->data, cache->block_size); 857 #endif 858 return block->data; 859 } 860 861 862 extern "C" const void * 863 block_cache_get(void *_cache, off_t blockNumber) 864 { 865 return block_cache_get_etc(_cache, blockNumber, blockNumber, 1); 866 } 867 868 869 extern "C" status_t 870 block_cache_set_dirty(void *_cache, off_t blockNumber, bool isDirty, int32 transaction) 871 { 872 // not yet implemented 873 // Note, you must only use this function on blocks that were acquired writable! 874 if (isDirty) 875 panic("block_cache_set_dirty(): not yet implemented that way!\n"); 876 877 return B_OK; 878 } 879 880 881 extern "C" void 882 block_cache_put(void *_cache, off_t blockNumber) 883 { 884 block_cache *cache = (block_cache *)_cache; 885 BenaphoreLocker locker(&cache->lock); 886 887 put_cached_block(cache, blockNumber); 888 } 889 890