2 * Copyright 2004-2012, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
7 #include <block_cache.h>
14 #include <KernelExport.h>
17 #include <condition_variable.h>
19 #include <low_resource_manager.h>
20 #include <slab/Slab.h>
22 #include <util/kernel_cpp.h>
23 #include <util/DoublyLinkedList.h>
24 #include <util/AutoLock.h>
25 #include <vm/vm_page.h>
27 #include "kernel_debug_config.h"
30 // TODO: this is a naive but growing implementation to test the API:
31 // block reading/writing is not at all optimized for speed, it will
32 // just read and write single blocks.
33 // TODO: the retrieval/copy of the original data could be delayed until the
34 // new data must be written, ie. in low memory situations.
36 //#define TRACE_BLOCK_CACHE
37 #ifdef TRACE_BLOCK_CACHE
38 # define TRACE(x) dprintf x
43 #define TRACE_ALWAYS(x) dprintf x
45 // This macro is used for fatal situations that are acceptable in a running
46 // system, like out of memory situations - should only panic for debugging.
47 #define FATAL(x) panic x
49 static const bigtime_t kTransactionIdleTime
= 2000000LL;
50 // a transaction is considered idle after 2 seconds of inactivity
53 struct cache_transaction
;
56 typedef DoublyLinkedListLink
<cached_block
> block_link
;
59 cached_block
* next
; // next in hash
60 cached_block
* transaction_next
;
64 // The data that is seen by everyone using the API; this one is always
67 // When in a transaction, this contains the original data from before
70 // This is a lazily alloced buffer that represents the contents of the
71 // block in the parent transaction. It may point to current_data if the
72 // contents have been changed only in the parent transaction, or, if the
73 // block has been changed in the current sub transaction already, to a
74 // new block containing the contents changed in the parent transaction.
75 // If this is NULL, the block has not been changed in the parent
76 // transaction at all.
77 #if BLOCK_CACHE_DEBUG_CHANGED
82 bool busy_reading
: 1;
83 bool busy_writing
: 1;
85 // Block has been checked out for writing without transactions, and
86 // cannot be written back if set
90 bool busy_reading_waiters
: 1;
91 bool busy_writing_waiters
: 1;
92 cache_transaction
* transaction
;
93 // This is the current active transaction, if any, the block is
94 // currently in (meaning was changed as a part of it).
95 cache_transaction
* previous_transaction
;
96 // This is set to the last transaction that was ended containing this
97 // block. In this case, the block has not yet written back yet, and
98 // the changed data is either in current_data, or original_data -- the
99 // latter if the block is already being part of another transaction.
100 // There can only be one previous transaction, so when the active
101 // transaction ends, the changes of the previous transaction have to
102 // be written back before that transaction becomes the next previous
105 bool CanBeWritten() const;
106 int32
LastAccess() const
107 { return system_time() / 1000000L - last_accessed
; }
110 typedef DoublyLinkedList
<cached_block
,
111 DoublyLinkedListMemberGetLink
<cached_block
,
112 &cached_block::link
> > block_list
;
114 struct cache_notification
: DoublyLinkedListLinkImpl
<cache_notification
> {
115 int32 transaction_id
;
116 int32 events_pending
;
118 transaction_notification_hook hook
;
120 bool delete_after_event
;
123 typedef DoublyLinkedList
<cache_notification
> NotificationList
;
126 typedef off_t KeyType
;
127 typedef cached_block ValueType
;
129 size_t HashKey(KeyType key
) const
134 size_t Hash(ValueType
* block
) const
136 return block
->block_number
;
139 bool Compare(KeyType key
, ValueType
* block
) const
141 return block
->block_number
== key
;
144 ValueType
*& GetLink(ValueType
* value
) const
150 typedef BOpenHashTable
<BlockHash
> BlockTable
;
153 struct TransactionHash
{
154 typedef int32 KeyType
;
155 typedef cache_transaction ValueType
;
157 size_t HashKey(KeyType key
) const
162 size_t Hash(ValueType
* transaction
) const;
163 bool Compare(KeyType key
, ValueType
* transaction
) const;
164 ValueType
*& GetLink(ValueType
* value
) const;
167 typedef BOpenHashTable
<TransactionHash
> TransactionTable
;
170 struct block_cache
: DoublyLinkedListLinkImpl
<block_cache
> {
176 int32 next_transaction_id
;
177 cache_transaction
* last_transaction
;
178 TransactionTable
* transaction_hash
;
180 object_cache
* buffer_cache
;
181 block_list unused_blocks
;
182 uint32 unused_block_count
;
184 ConditionVariable busy_reading_condition
;
185 uint32 busy_reading_count
;
186 bool busy_reading_waiters
;
188 ConditionVariable busy_writing_condition
;
189 uint32 busy_writing_count
;
190 bool busy_writing_waiters
;
192 uint32 num_dirty_blocks
;
195 NotificationList pending_notifications
;
196 ConditionVariable condition_variable
;
198 block_cache(int fd
, off_t numBlocks
, size_t blockSize
,
204 void Free(void* buffer
);
206 void FreeBlock(cached_block
* block
);
207 cached_block
* NewBlock(off_t blockNumber
);
208 void FreeBlockParentData(cached_block
* block
);
210 void RemoveUnusedBlocks(int32 count
, int32 minSecondsOld
= 0);
211 void RemoveBlock(cached_block
* block
);
212 void DiscardBlock(cached_block
* block
);
215 static void _LowMemoryHandler(void* data
, uint32 resources
,
217 cached_block
* _GetUnusedBlock();
220 struct cache_listener
;
221 typedef DoublyLinkedListLink
<cache_listener
> listener_link
;
223 struct cache_listener
: cache_notification
{
227 typedef DoublyLinkedList
<cache_listener
,
228 DoublyLinkedListMemberGetLink
<cache_listener
,
229 &cache_listener::link
> > ListenerList
;
232 struct cache_transaction
{
235 cache_transaction
* next
;
238 int32 main_num_blocks
;
239 int32 sub_num_blocks
;
240 cached_block
* first_block
;
242 ListenerList listeners
;
244 bool has_sub_transaction
;
246 int32 busy_writing_count
;
252 BlockWriter(block_cache
* cache
,
253 size_t max
= SIZE_MAX
);
256 bool Add(cached_block
* block
,
257 cache_transaction
* transaction
= NULL
);
258 bool Add(cache_transaction
* transaction
,
261 status_t
Write(cache_transaction
* transaction
= NULL
,
262 bool canUnlock
= true);
264 bool DeletedTransaction() const
265 { return fDeletedTransaction
; }
267 static status_t
WriteBlock(block_cache
* cache
,
268 cached_block
* block
);
271 void* _Data(cached_block
* block
) const;
272 status_t
_WriteBlock(cached_block
* block
);
273 void _BlockDone(cached_block
* block
,
274 cache_transaction
* transaction
);
275 void _UnmarkWriting(cached_block
* block
);
277 static int _CompareBlocks(const void* _blockA
,
278 const void* _blockB
);
281 static const size_t kBufferSize
= 64;
284 cached_block
* fBuffer
[kBufferSize
];
285 cached_block
** fBlocks
;
291 bool fDeletedTransaction
;
295 class TransactionLocking
{
297 inline bool Lock(block_cache
* cache
)
299 mutex_lock(&cache
->lock
);
301 while (cache
->busy_writing_count
!= 0) {
302 // wait for all blocks to be written
303 ConditionVariableEntry entry
;
304 cache
->busy_writing_condition
.Add(&entry
);
305 cache
->busy_writing_waiters
= true;
307 mutex_unlock(&cache
->lock
);
311 mutex_lock(&cache
->lock
);
317 inline void Unlock(block_cache
* cache
)
319 mutex_unlock(&cache
->lock
);
323 typedef AutoLocker
<block_cache
, TransactionLocking
> TransactionLocker
;
326 #if BLOCK_CACHE_BLOCK_TRACING && !defined(BUILDING_USERLAND_FS_SERVER)
327 namespace BlockTracing
{
329 class Action
: public AbstractTraceEntry
{
331 Action(block_cache
* cache
, cached_block
* block
)
334 fBlockNumber(block
->block_number
),
335 fIsDirty(block
->is_dirty
),
336 fHasOriginal(block
->original_data
!= NULL
),
337 fHasParent(block
->parent_data
!= NULL
),
341 if (block
->transaction
!= NULL
)
342 fTransactionID
= block
->transaction
->id
;
343 if (block
->previous_transaction
!= NULL
)
344 fPreviousID
= block
->previous_transaction
->id
;
347 virtual void AddDump(TraceOutput
& out
)
349 out
.Print("block cache %p, %s %" B_PRIu64
", %c%c%c transaction %" B_PRId32
350 " (previous id %" B_PRId32
")\n", fCache
, _Action(), fBlockNumber
,
351 fIsDirty
? 'd' : '-', fHasOriginal
? 'o' : '-',
352 fHasParent
? 'p' : '-', fTransactionID
, fPreviousID
);
355 virtual const char* _Action() const = 0;
363 int32 fTransactionID
;
367 class Get
: public Action
{
369 Get(block_cache
* cache
, cached_block
* block
)
376 virtual const char* _Action() const { return "get"; }
379 class Put
: public Action
{
381 Put(block_cache
* cache
, cached_block
* block
)
388 virtual const char* _Action() const { return "put"; }
391 class Read
: public Action
{
393 Read(block_cache
* cache
, cached_block
* block
)
400 virtual const char* _Action() const { return "read"; }
403 class Write
: public Action
{
405 Write(block_cache
* cache
, cached_block
* block
)
412 virtual const char* _Action() const { return "write"; }
415 class Flush
: public Action
{
417 Flush(block_cache
* cache
, cached_block
* block
, bool getUnused
= false)
419 Action(cache
, block
),
420 fGetUnused(getUnused
)
425 virtual const char* _Action() const
426 { return fGetUnused
? "get-unused" : "flush"; }
432 class Error
: public AbstractTraceEntry
{
434 Error(block_cache
* cache
, uint64 blockNumber
, const char* message
,
435 status_t status
= B_OK
)
438 fBlockNumber(blockNumber
),
445 virtual void AddDump(TraceOutput
& out
)
447 out
.Print("block cache %p, error %" B_PRIu64
", %s%s%s",
448 fCache
, fBlockNumber
, fMessage
, fStatus
!= B_OK
? ": " : "",
449 fStatus
!= B_OK
? strerror(fStatus
) : "");
455 const char* fMessage
;
459 #if BLOCK_CACHE_BLOCK_TRACING >= 2
460 class BlockData
: public AbstractTraceEntry
{
468 BlockData(block_cache
* cache
, cached_block
* block
, const char* message
)
471 fSize(cache
->block_size
),
472 fBlockNumber(block
->block_number
),
475 _Allocate(fCurrent
, block
->current_data
);
476 _Allocate(fParent
, block
->parent_data
);
477 _Allocate(fOriginal
, block
->original_data
);
479 #if KTRACE_PRINTF_STACK_TRACE
480 fStackTrace
= capture_tracing_stack_trace(KTRACE_PRINTF_STACK_TRACE
, 1,
487 virtual void AddDump(TraceOutput
& out
)
489 out
.Print("block cache %p, block %" B_PRIu64
", data %c%c%c: %s",
490 fCache
, fBlockNumber
, fCurrent
!= NULL
? 'c' : '-',
491 fParent
!= NULL
? 'p' : '-', fOriginal
!= NULL
? 'o' : '-',
495 #if KTRACE_PRINTF_STACK_TRACE
496 virtual void DumpStackTrace(TraceOutput
& out
)
498 out
.PrintStackTrace(fStackTrace
);
502 void DumpBlocks(uint32 which
, uint32 offset
, uint32 size
)
504 if ((which
& kCurrent
) != 0)
505 DumpBlock(kCurrent
, offset
, size
);
506 if ((which
& kParent
) != 0)
507 DumpBlock(kParent
, offset
, size
);
508 if ((which
& kOriginal
) != 0)
509 DumpBlock(kOriginal
, offset
, size
);
512 void DumpBlock(uint32 which
, uint32 offset
, uint32 size
)
514 if (offset
> fSize
) {
515 kprintf("invalid offset (block size %" B_PRIu32
")\n", fSize
);
518 if (offset
+ size
> fSize
)
519 size
= fSize
- offset
;
524 if ((which
& kCurrent
) != 0) {
527 } else if ((which
& kParent
) != 0) {
530 } else if ((which
& kOriginal
) != 0) {
536 kprintf("%s: offset %" B_PRIu32
", %" B_PRIu32
" bytes\n", label
, offset
, size
);
538 static const uint32 kBlockSize
= 16;
541 for (uint32 i
= 0; i
< size
;) {
544 kprintf(" %04" B_PRIx32
" ", i
);
545 for (; i
< start
+ kBlockSize
; i
++) {
552 kprintf("%02x", data
[i
]);
560 void _Allocate(uint8
*& target
, void* source
)
562 if (source
== NULL
) {
567 target
= alloc_tracing_buffer_memcpy(source
, fSize
, false);
573 const char* fMessage
;
577 #if KTRACE_PRINTF_STACK_TRACE
578 tracing_stack_trace
* fStackTrace
;
581 #endif // BLOCK_CACHE_BLOCK_TRACING >= 2
583 } // namespace BlockTracing
585 # define TB(x) new(std::nothrow) BlockTracing::x;
590 #if BLOCK_CACHE_BLOCK_TRACING >= 2
591 # define TB2(x) new(std::nothrow) BlockTracing::x;
597 #if BLOCK_CACHE_TRANSACTION_TRACING && !defined(BUILDING_USERLAND_FS_SERVER)
598 namespace TransactionTracing
{
600 class Action
: public AbstractTraceEntry
{
602 Action(const char* label
, block_cache
* cache
,
603 cache_transaction
* transaction
)
606 fTransaction(transaction
),
607 fID(transaction
->id
),
608 fSub(transaction
->has_sub_transaction
),
609 fNumBlocks(transaction
->num_blocks
),
610 fSubNumBlocks(transaction
->sub_num_blocks
)
612 strlcpy(fLabel
, label
, sizeof(fLabel
));
616 virtual void AddDump(TraceOutput
& out
)
618 out
.Print("block cache %p, %s transaction %p (id %" B_PRId32
")%s"
619 ", %" B_PRId32
"/%" B_PRId32
" blocks", fCache
, fLabel
, fTransaction
,
620 fID
, fSub
? " sub" : "", fNumBlocks
, fSubNumBlocks
);
626 cache_transaction
* fTransaction
;
633 class Detach
: public AbstractTraceEntry
{
635 Detach(block_cache
* cache
, cache_transaction
* transaction
,
636 cache_transaction
* newTransaction
)
639 fTransaction(transaction
),
640 fID(transaction
->id
),
641 fSub(transaction
->has_sub_transaction
),
642 fNewTransaction(newTransaction
),
643 fNewID(newTransaction
->id
)
648 virtual void AddDump(TraceOutput
& out
)
650 out
.Print("block cache %p, detach transaction %p (id %" B_PRId32
")"
651 "from transaction %p (id %" B_PRId32
")%s",
652 fCache
, fNewTransaction
, fNewID
, fTransaction
, fID
,
658 cache_transaction
* fTransaction
;
661 cache_transaction
* fNewTransaction
;
665 class Abort
: public AbstractTraceEntry
{
667 Abort(block_cache
* cache
, cache_transaction
* transaction
)
670 fTransaction(transaction
),
671 fID(transaction
->id
),
674 bool isSub
= transaction
->has_sub_transaction
;
675 fNumBlocks
= isSub
? transaction
->sub_num_blocks
676 : transaction
->num_blocks
;
677 fBlocks
= (off_t
*)alloc_tracing_buffer(fNumBlocks
* sizeof(off_t
));
678 if (fBlocks
!= NULL
) {
679 cached_block
* block
= transaction
->first_block
;
680 for (int32 i
= 0; block
!= NULL
&& i
< fNumBlocks
;
681 block
= block
->transaction_next
) {
682 fBlocks
[i
++] = block
->block_number
;
687 #if KTRACE_PRINTF_STACK_TRACE
688 fStackTrace
= capture_tracing_stack_trace(KTRACE_PRINTF_STACK_TRACE
, 1,
695 virtual void AddDump(TraceOutput
& out
)
697 out
.Print("block cache %p, abort transaction "
698 "%p (id %" B_PRId32
"), blocks", fCache
, fTransaction
, fID
);
699 for (int32 i
= 0; i
< fNumBlocks
&& !out
.IsFull(); i
++)
700 out
.Print(" %" B_PRIdOFF
, fBlocks
[i
]);
703 #if KTRACE_PRINTF_STACK_TRACE
704 virtual void DumpStackTrace(TraceOutput
& out
)
706 out
.PrintStackTrace(fStackTrace
);
712 cache_transaction
* fTransaction
;
716 #if KTRACE_PRINTF_STACK_TRACE
717 tracing_stack_trace
* fStackTrace
;
721 } // namespace TransactionTracing
723 # define T(x) new(std::nothrow) TransactionTracing::x;
729 static DoublyLinkedList
<block_cache
> sCaches
;
730 static mutex sCachesLock
= MUTEX_INITIALIZER("block caches");
731 static mutex sCachesMemoryUseLock
732 = MUTEX_INITIALIZER("block caches memory use");
733 static size_t sUsedMemory
;
734 static sem_id sEventSemaphore
;
735 static mutex sNotificationsLock
736 = MUTEX_INITIALIZER("block cache notifications");
737 static thread_id sNotifierWriterThread
;
738 static DoublyLinkedListLink
<block_cache
> sMarkCache
;
739 // TODO: this only works if the link is the first entry of block_cache
740 static object_cache
* sBlockCache
;
743 // #pragma mark - notifications/listener
746 /*! Checks whether or not this is an event that closes a transaction. */
748 is_closing_event(int32 event
)
750 return (event
& (TRANSACTION_ABORTED
| TRANSACTION_ENDED
)) != 0;
755 is_written_event(int32 event
)
757 return (event
& TRANSACTION_WRITTEN
) != 0;
761 /*! From the specified \a notification, it will remove the lowest pending
762 event, and return that one in \a _event.
763 If there is no pending event anymore, it will return \c false.
766 get_next_pending_event(cache_notification
* notification
, int32
* _event
)
768 for (int32 eventMask
= 1; eventMask
<= TRANSACTION_IDLE
; eventMask
<<= 1) {
769 int32 pending
= atomic_and(¬ification
->events_pending
,
772 bool more
= (pending
& ~eventMask
) != 0;
774 if ((pending
& eventMask
) != 0) {
785 flush_pending_notifications(block_cache
* cache
)
787 ASSERT_LOCKED_MUTEX(&sCachesLock
);
790 MutexLocker
locker(sNotificationsLock
);
792 cache_notification
* notification
= cache
->pending_notifications
.Head();
793 if (notification
== NULL
)
796 bool deleteAfterEvent
= false;
798 if (!get_next_pending_event(notification
, &event
)) {
799 // remove the notification if this was the last pending event
800 cache
->pending_notifications
.Remove(notification
);
801 deleteAfterEvent
= notification
->delete_after_event
;
805 // Notify listener, we need to copy the notification, as it might
806 // be removed when we unlock the list.
807 cache_notification copy
= *notification
;
810 copy
.hook(copy
.transaction_id
, event
, copy
.data
);
815 if (deleteAfterEvent
)
821 /*! Flushes all pending notifications by calling the appropriate hook
823 Must not be called with a cache lock held.
826 flush_pending_notifications()
828 MutexLocker
_(sCachesLock
);
830 DoublyLinkedList
<block_cache
>::Iterator iterator
= sCaches
.GetIterator();
831 while (iterator
.HasNext()) {
832 block_cache
* cache
= iterator
.Next();
834 flush_pending_notifications(cache
);
839 /*! Initializes the \a notification as specified. */
841 set_notification(cache_transaction
* transaction
,
842 cache_notification
¬ification
, int32 events
,
843 transaction_notification_hook hook
, void* data
)
845 notification
.transaction_id
= transaction
!= NULL
? transaction
->id
: -1;
846 notification
.events_pending
= 0;
847 notification
.events
= events
;
848 notification
.hook
= hook
;
849 notification
.data
= data
;
850 notification
.delete_after_event
= false;
854 /*! Makes sure the notification is deleted. It either deletes it directly,
855 when possible, or marks it for deletion if the notification is pending.
858 delete_notification(cache_notification
* notification
)
860 MutexLocker
locker(sNotificationsLock
);
862 if (notification
->events_pending
!= 0)
863 notification
->delete_after_event
= true;
869 /*! Adds the notification to the pending notifications list, or, if it's
870 already part of it, updates its events_pending field.
871 Also marks the notification to be deleted if \a deleteNotification
873 Triggers the notifier thread to run.
876 add_notification(block_cache
* cache
, cache_notification
* notification
,
877 int32 event
, bool deleteNotification
)
879 if (notification
->hook
== NULL
)
882 int32 pending
= atomic_or(¬ification
->events_pending
, event
);
884 // not yet part of the notification list
885 MutexLocker
locker(sNotificationsLock
);
886 if (deleteNotification
)
887 notification
->delete_after_event
= true;
888 cache
->pending_notifications
.Add(notification
);
889 } else if (deleteNotification
) {
890 // we might need to delete it ourselves if we're late
891 delete_notification(notification
);
894 release_sem_etc(sEventSemaphore
, 1, B_DO_NOT_RESCHEDULE
);
895 // We're probably still holding some locks that makes rescheduling
896 // not a good idea at this point.
900 /*! Notifies all interested listeners of this transaction about the \a event.
901 If \a event is a closing event (ie. TRANSACTION_ENDED, and
902 TRANSACTION_ABORTED), all listeners except those listening to
903 TRANSACTION_WRITTEN will be removed.
906 notify_transaction_listeners(block_cache
* cache
, cache_transaction
* transaction
,
909 T(Action("notify", cache
, transaction
));
911 bool isClosing
= is_closing_event(event
);
912 bool isWritten
= is_written_event(event
);
914 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
915 while (iterator
.HasNext()) {
916 cache_listener
* listener
= iterator
.Next();
918 bool remove
= (isClosing
&& !is_written_event(listener
->events
))
919 || (isWritten
&& is_written_event(listener
->events
));
923 if ((listener
->events
& event
) != 0)
924 add_notification(cache
, listener
, event
, remove
);
926 delete_notification(listener
);
931 /*! Removes and deletes all listeners that are still monitoring this
935 remove_transaction_listeners(block_cache
* cache
, cache_transaction
* transaction
)
937 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
938 while (iterator
.HasNext()) {
939 cache_listener
* listener
= iterator
.Next();
942 delete_notification(listener
);
948 add_transaction_listener(block_cache
* cache
, cache_transaction
* transaction
,
949 int32 events
, transaction_notification_hook hookFunction
, void* data
)
951 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
952 while (iterator
.HasNext()) {
953 cache_listener
* listener
= iterator
.Next();
955 if (listener
->data
== data
&& listener
->hook
== hookFunction
) {
956 // this listener already exists, just update it
957 listener
->events
|= events
;
962 cache_listener
* listener
= new(std::nothrow
) cache_listener
;
963 if (listener
== NULL
)
966 set_notification(transaction
, *listener
, events
, hookFunction
, data
);
967 transaction
->listeners
.Add(listener
);
972 // #pragma mark - private transaction
975 cache_transaction::cache_transaction()
982 has_sub_transaction
= false;
983 last_used
= system_time();
984 busy_writing_count
= 0;
989 delete_transaction(block_cache
* cache
, cache_transaction
* transaction
)
991 if (cache
->last_transaction
== transaction
)
992 cache
->last_transaction
= NULL
;
994 remove_transaction_listeners(cache
, transaction
);
999 static cache_transaction
*
1000 lookup_transaction(block_cache
* cache
, int32 id
)
1002 return cache
->transaction_hash
->Lookup(id
);
1006 size_t TransactionHash::Hash(cache_transaction
* transaction
) const
1008 return transaction
->id
;
1012 bool TransactionHash::Compare(int32 key
, cache_transaction
* transaction
) const
1014 return transaction
->id
== key
;
1018 cache_transaction
*& TransactionHash::GetLink(cache_transaction
* value
) const
1024 /*! Writes back any changes made to blocks in \a transaction that are still
1025 part of a previous transacton.
1028 write_blocks_in_previous_transaction(block_cache
* cache
,
1029 cache_transaction
* transaction
)
1031 BlockWriter
writer(cache
);
1033 cached_block
* block
= transaction
->first_block
;
1034 for (; block
!= NULL
; block
= block
->transaction_next
) {
1035 if (block
->previous_transaction
!= NULL
) {
1036 // need to write back pending changes
1041 return writer
.Write();
1045 // #pragma mark - cached_block
1049 cached_block::CanBeWritten() const
1051 return !busy_writing
&& !busy_reading
1052 && (previous_transaction
!= NULL
1053 || (transaction
== NULL
&& is_dirty
&& !is_writing
));
1057 // #pragma mark - BlockWriter
1060 BlockWriter::BlockWriter(block_cache
* cache
, size_t max
)
1066 fCapacity(kBufferSize
),
1069 fDeletedTransaction(false)
1074 BlockWriter::~BlockWriter()
1076 if (fBlocks
!= fBuffer
)
1081 /*! Adds the specified block to the to be written array. If no more blocks can
1082 be added, false is returned, otherwise true.
1085 BlockWriter::Add(cached_block
* block
, cache_transaction
* transaction
)
1087 ASSERT(block
->CanBeWritten());
1092 if (fCount
>= fCapacity
) {
1093 // Enlarge array if necessary
1094 cached_block
** newBlocks
;
1095 size_t newCapacity
= max_c(256, fCapacity
* 2);
1096 if (fBlocks
== fBuffer
)
1097 newBlocks
= (cached_block
**)malloc(newCapacity
* sizeof(void*));
1099 newBlocks
= (cached_block
**)realloc(fBlocks
,
1100 newCapacity
* sizeof(void*));
1103 if (newBlocks
== NULL
) {
1104 // Allocating a larger array failed - we need to write back what
1105 // we have synchronously now (this will also clear the array)
1106 Write(transaction
, false);
1108 if (fBlocks
== fBuffer
)
1109 memcpy(newBlocks
, fBuffer
, kBufferSize
* sizeof(void*));
1111 fBlocks
= newBlocks
;
1112 fCapacity
= newCapacity
;
1116 fBlocks
[fCount
++] = block
;
1118 block
->busy_writing
= true;
1119 fCache
->busy_writing_count
++;
1120 if (block
->previous_transaction
!= NULL
)
1121 block
->previous_transaction
->busy_writing_count
++;
1127 /*! Adds all blocks of the specified transaction to the to be written array.
1128 If no more blocks can be added, false is returned, otherwise true.
1131 BlockWriter::Add(cache_transaction
* transaction
, bool& hasLeftOvers
)
1133 ASSERT(!transaction
->open
);
1135 if (transaction
->busy_writing_count
!= 0) {
1136 hasLeftOvers
= true;
1140 hasLeftOvers
= false;
1142 block_list::Iterator blockIterator
= transaction
->blocks
.GetIterator();
1143 while (cached_block
* block
= blockIterator
.Next()) {
1144 if (!block
->CanBeWritten()) {
1145 // This block was already part of a previous transaction within this
1147 hasLeftOvers
= true;
1150 if (!Add(block
, transaction
))
1153 if (DeletedTransaction())
1161 /*! Cache must be locked when calling this method, but it will be unlocked
1162 while the blocks are written back.
1165 BlockWriter::Write(cache_transaction
* transaction
, bool canUnlock
)
1171 mutex_unlock(&fCache
->lock
);
1173 // Sort blocks in their on-disk order
1174 // TODO: ideally, this should be handled by the I/O scheduler
1176 qsort(fBlocks
, fCount
, sizeof(void*), &_CompareBlocks
);
1177 fDeletedTransaction
= false;
1179 for (uint32 i
= 0; i
< fCount
; i
++) {
1180 status_t status
= _WriteBlock(fBlocks
[i
]);
1181 if (status
!= B_OK
) {
1182 // propagate to global error handling
1183 if (fStatus
== B_OK
)
1186 _UnmarkWriting(fBlocks
[i
]);
1188 // This block will not be marked clean
1193 mutex_lock(&fCache
->lock
);
1195 for (uint32 i
= 0; i
< fCount
; i
++)
1196 _BlockDone(fBlocks
[i
], transaction
);
1203 /*! Writes the specified \a block back to disk. It will always only write back
1204 the oldest change of the block if it is part of more than one transaction.
1205 It will automatically send out TRANSACTION_WRITTEN notices, as well as
1206 delete transactions when they are no longer used, and \a deleteTransaction
1210 BlockWriter::WriteBlock(block_cache
* cache
, cached_block
* block
)
1212 BlockWriter
writer(cache
);
1215 return writer
.Write();
1220 BlockWriter::_Data(cached_block
* block
) const
1222 return block
->previous_transaction
!= NULL
&& block
->original_data
!= NULL
1223 ? block
->original_data
: block
->current_data
;
1224 // We first need to write back changes from previous transactions
1229 BlockWriter::_WriteBlock(cached_block
* block
)
1231 ASSERT(block
->busy_writing
);
1233 TRACE(("BlockWriter::_WriteBlock(block %" B_PRIdOFF
")\n", block
->block_number
));
1234 TB(Write(fCache
, block
));
1235 TB2(BlockData(fCache
, block
, "before write"));
1237 size_t blockSize
= fCache
->block_size
;
1239 ssize_t written
= write_pos(fCache
->fd
,
1240 block
->block_number
* blockSize
, _Data(block
), blockSize
);
1242 if (written
!= (ssize_t
)blockSize
) {
1243 TB(Error(fCache
, block
->block_number
, "write failed", written
));
1244 TRACE_ALWAYS(("could not write back block %" B_PRIdOFF
" (%s)\n", block
->block_number
,
1257 BlockWriter::_BlockDone(cached_block
* block
,
1258 cache_transaction
* transaction
)
1260 if (block
== NULL
) {
1261 // An error occured when trying to write this block
1265 if (fCache
->num_dirty_blocks
> 0)
1266 fCache
->num_dirty_blocks
--;
1268 if (_Data(block
) == block
->current_data
)
1269 block
->is_dirty
= false;
1271 _UnmarkWriting(block
);
1273 cache_transaction
* previous
= block
->previous_transaction
;
1274 if (previous
!= NULL
) {
1275 previous
->blocks
.Remove(block
);
1276 block
->previous_transaction
= NULL
;
1278 if (block
->original_data
!= NULL
&& block
->transaction
== NULL
) {
1279 // This block is not part of a transaction, so it does not need
1280 // its original pointer anymore.
1281 fCache
->Free(block
->original_data
);
1282 block
->original_data
= NULL
;
1285 // Has the previous transaction been finished with that write?
1286 if (--previous
->num_blocks
== 0) {
1287 TRACE(("cache transaction %" B_PRId32
" finished!\n", previous
->id
));
1288 T(Action("written", fCache
, previous
));
1290 notify_transaction_listeners(fCache
, previous
,
1291 TRANSACTION_WRITTEN
);
1293 if (transaction
!= NULL
) {
1294 // This function is called while iterating transaction_hash. We
1295 // use RemoveUnchecked so the iterator is still valid. A regular
1296 // Remove can trigger a resize of the hash table which would
1297 // result in the linked items in the table changing order.
1298 fCache
->transaction_hash
->RemoveUnchecked(transaction
);
1300 fCache
->transaction_hash
->Remove(previous
);
1302 delete_transaction(fCache
, previous
);
1303 fDeletedTransaction
= true;
1306 if (block
->transaction
== NULL
&& block
->ref_count
== 0 && !block
->unused
) {
1307 // the block is no longer used
1308 ASSERT(block
->original_data
== NULL
&& block
->parent_data
== NULL
);
1309 block
->unused
= true;
1310 fCache
->unused_blocks
.Add(block
);
1311 fCache
->unused_block_count
++;
1314 TB2(BlockData(fCache
, block
, "after write"));
1319 BlockWriter::_UnmarkWriting(cached_block
* block
)
1321 block
->busy_writing
= false;
1322 if (block
->previous_transaction
!= NULL
)
1323 block
->previous_transaction
->busy_writing_count
--;
1324 fCache
->busy_writing_count
--;
1326 if ((fCache
->busy_writing_waiters
&& fCache
->busy_writing_count
== 0)
1327 || block
->busy_writing_waiters
) {
1328 fCache
->busy_writing_waiters
= false;
1329 block
->busy_writing_waiters
= false;
1330 fCache
->busy_writing_condition
.NotifyAll();
1336 BlockWriter::_CompareBlocks(const void* _blockA
, const void* _blockB
)
1338 cached_block
* blockA
= *(cached_block
**)_blockA
;
1339 cached_block
* blockB
= *(cached_block
**)_blockB
;
1341 off_t diff
= blockA
->block_number
- blockB
->block_number
;
1345 return diff
< 0 ? -1 : 0;
1349 // #pragma mark - block_cache
1352 block_cache::block_cache(int _fd
, off_t numBlocks
, size_t blockSize
,
1357 max_blocks(numBlocks
),
1358 block_size(blockSize
),
1359 next_transaction_id(1),
1360 last_transaction(NULL
),
1361 transaction_hash(NULL
),
1363 unused_block_count(0),
1364 busy_reading_count(0),
1365 busy_reading_waiters(false),
1366 busy_writing_count(0),
1367 busy_writing_waiters(0),
1368 num_dirty_blocks(0),
1374 /*! Should be called with the cache's lock held. */
1375 block_cache::~block_cache()
1377 unregister_low_resource_handler(&_LowMemoryHandler
, this);
1379 delete transaction_hash
;
1382 delete_object_cache(buffer_cache
);
1384 mutex_destroy(&lock
);
1391 busy_reading_condition
.Init(this, "cache block busy_reading");
1392 busy_writing_condition
.Init(this, "cache block busy writing");
1393 condition_variable
.Init(this, "cache transaction sync");
1394 mutex_init(&lock
, "block cache");
1396 buffer_cache
= create_object_cache_etc("block cache buffers", block_size
,
1397 8, 0, 0, 0, CACHE_LARGE_SLAB
, NULL
, NULL
, NULL
, NULL
);
1398 if (buffer_cache
== NULL
)
1401 hash
= new BlockTable();
1402 if (hash
== NULL
|| hash
->Init(1024) != B_OK
)
1405 transaction_hash
= new(std::nothrow
) TransactionTable();
1406 if (transaction_hash
== NULL
|| transaction_hash
->Init(16) != B_OK
)
1409 return register_low_resource_handler(&_LowMemoryHandler
, this,
1410 B_KERNEL_RESOURCE_PAGES
| B_KERNEL_RESOURCE_MEMORY
1411 | B_KERNEL_RESOURCE_ADDRESS_SPACE
, 0);
1416 block_cache::Free(void* buffer
)
1419 object_cache_free(buffer_cache
, buffer
, 0);
1424 block_cache::Allocate()
1426 void* block
= object_cache_alloc(buffer_cache
, 0);
1430 // recycle existing before allocating a new one
1431 RemoveUnusedBlocks(100);
1433 return object_cache_alloc(buffer_cache
, 0);
1438 block_cache::FreeBlock(cached_block
* block
)
1440 Free(block
->current_data
);
1442 if (block
->original_data
!= NULL
|| block
->parent_data
!= NULL
) {
1443 panic("block_cache::FreeBlock(): %" B_PRIdOFF
", original %p, parent %p\n",
1444 block
->block_number
, block
->original_data
, block
->parent_data
);
1447 #if BLOCK_CACHE_DEBUG_CHANGED
1448 Free(block
->compare
);
1451 object_cache_free(sBlockCache
, block
, 0);
1455 /*! Allocates a new block for \a blockNumber, ready for use */
1457 block_cache::NewBlock(off_t blockNumber
)
1459 cached_block
* block
= NULL
;
1461 if (low_resource_state(B_KERNEL_RESOURCE_PAGES
| B_KERNEL_RESOURCE_MEMORY
1462 | B_KERNEL_RESOURCE_ADDRESS_SPACE
) != B_NO_LOW_RESOURCE
) {
1463 // recycle existing instead of allocating a new one
1464 block
= _GetUnusedBlock();
1466 if (block
== NULL
) {
1467 block
= (cached_block
*)object_cache_alloc(sBlockCache
, 0);
1468 if (block
!= NULL
) {
1469 block
->current_data
= Allocate();
1470 if (block
->current_data
== NULL
) {
1471 object_cache_free(sBlockCache
, block
, 0);
1475 TB(Error(this, blockNumber
, "allocation failed"));
1476 dprintf("block allocation failed, unused list is %sempty.\n",
1477 unused_blocks
.IsEmpty() ? "" : "not ");
1479 // allocation failed, try to reuse an unused block
1480 block
= _GetUnusedBlock();
1481 if (block
== NULL
) {
1482 TB(Error(this, blockNumber
, "get unused failed"));
1483 FATAL(("could not allocate block!\n"));
1489 block
->block_number
= blockNumber
;
1490 block
->ref_count
= 0;
1491 block
->last_accessed
= 0;
1492 block
->transaction_next
= NULL
;
1493 block
->transaction
= block
->previous_transaction
= NULL
;
1494 block
->original_data
= NULL
;
1495 block
->parent_data
= NULL
;
1496 block
->busy_reading
= false;
1497 block
->busy_writing
= false;
1498 block
->is_writing
= false;
1499 block
->is_dirty
= false;
1500 block
->unused
= false;
1501 block
->discard
= false;
1502 block
->busy_reading_waiters
= false;
1503 block
->busy_writing_waiters
= false;
1504 #if BLOCK_CACHE_DEBUG_CHANGED
1505 block
->compare
= NULL
;
1513 block_cache::FreeBlockParentData(cached_block
* block
)
1515 ASSERT(block
->parent_data
!= NULL
);
1516 if (block
->parent_data
!= block
->current_data
)
1517 Free(block
->parent_data
);
1518 block
->parent_data
= NULL
;
1523 block_cache::RemoveUnusedBlocks(int32 count
, int32 minSecondsOld
)
1525 TRACE(("block_cache: remove up to %" B_PRId32
" unused blocks\n", count
));
1527 for (block_list::Iterator iterator
= unused_blocks
.GetIterator();
1528 cached_block
* block
= iterator
.Next();) {
1529 if (minSecondsOld
>= block
->LastAccess()) {
1530 // The list is sorted by last access
1533 if (block
->busy_reading
|| block
->busy_writing
)
1536 TB(Flush(this, block
));
1537 TRACE((" remove block %" B_PRIdOFF
", last accessed %" B_PRId32
"\n",
1538 block
->block_number
, block
->last_accessed
));
1540 // this can only happen if no transactions are used
1541 if (block
->is_dirty
&& !block
->discard
) {
1542 if (block
->busy_writing
)
1545 BlockWriter::WriteBlock(this, block
);
1548 // remove block from lists
1550 unused_block_count
--;
1560 block_cache::RemoveBlock(cached_block
* block
)
1562 hash
->Remove(block
);
1567 /*! Discards the block from a transaction (this method must not be called
1568 for blocks not part of a transaction).
1571 block_cache::DiscardBlock(cached_block
* block
)
1573 ASSERT(block
->discard
);
1574 ASSERT(block
->previous_transaction
== NULL
);
1576 if (block
->parent_data
!= NULL
)
1577 FreeBlockParentData(block
);
1579 if (block
->original_data
!= NULL
) {
1580 Free(block
->original_data
);
1581 block
->original_data
= NULL
;
1589 block_cache::_LowMemoryHandler(void* data
, uint32 resources
, int32 level
)
1591 TRACE(("block_cache: low memory handler called with level %" B_PRId32
"\n", level
));
1593 // free some blocks according to the low memory state
1594 // (if there is enough memory left, we don't free any)
1596 block_cache
* cache
= (block_cache
*)data
;
1598 int32 secondsOld
= 0;
1600 case B_NO_LOW_RESOURCE
:
1602 case B_LOW_RESOURCE_NOTE
:
1603 free
= cache
->unused_block_count
/ 8;
1606 case B_LOW_RESOURCE_WARNING
:
1607 free
= cache
->unused_block_count
/ 4;
1610 case B_LOW_RESOURCE_CRITICAL
:
1611 free
= cache
->unused_block_count
/ 2;
1616 MutexLocker
locker(&cache
->lock
);
1618 if (!locker
.IsLocked()) {
1619 // If our block_cache were deleted, it could be that we had
1620 // been called before that deletion went through, therefore,
1621 // acquiring its lock might fail.
1625 #ifdef TRACE_BLOCK_CACHE
1626 uint32 oldUnused
= cache
->unused_block_count
;
1629 cache
->RemoveUnusedBlocks(free
, secondsOld
);
1631 TRACE(("block_cache::_LowMemoryHandler(): %p: unused: %" B_PRIu32
" -> %" B_PRIu32
"\n",
1632 cache
, oldUnused
, cache
->unused_block_count
));
1637 block_cache::_GetUnusedBlock()
1639 TRACE(("block_cache: get unused block\n"));
1641 for (block_list::Iterator iterator
= unused_blocks
.GetIterator();
1642 cached_block
* block
= iterator
.Next();) {
1643 TB(Flush(this, block
, true));
1644 // this can only happen if no transactions are used
1645 if (block
->is_dirty
&& !block
->busy_writing
&& !block
->discard
)
1646 BlockWriter::WriteBlock(this, block
);
1648 // remove block from lists
1650 unused_block_count
--;
1651 hash
->Remove(block
);
1653 ASSERT(block
->original_data
== NULL
&& block
->parent_data
== NULL
);
1654 block
->unused
= false;
1656 // TODO: see if compare data is handled correctly here!
1657 #if BLOCK_CACHE_DEBUG_CHANGED
1658 if (block
->compare
!= NULL
)
1659 Free(block
->compare
);
1668 // #pragma mark - private block functions
1671 /*! Cache must be locked.
1674 mark_block_busy_reading(block_cache
* cache
, cached_block
* block
)
1676 block
->busy_reading
= true;
1677 cache
->busy_reading_count
++;
1681 /*! Cache must be locked.
1684 mark_block_unbusy_reading(block_cache
* cache
, cached_block
* block
)
1686 block
->busy_reading
= false;
1687 cache
->busy_reading_count
--;
1689 if ((cache
->busy_reading_waiters
&& cache
->busy_reading_count
== 0)
1690 || block
->busy_reading_waiters
) {
1691 cache
->busy_reading_waiters
= false;
1692 block
->busy_reading_waiters
= false;
1693 cache
->busy_reading_condition
.NotifyAll();
1698 /*! Cache must be locked.
1701 wait_for_busy_reading_block(block_cache
* cache
, cached_block
* block
)
1703 while (block
->busy_reading
) {
1704 // wait for at least the specified block to be read in
1705 ConditionVariableEntry entry
;
1706 cache
->busy_reading_condition
.Add(&entry
);
1707 block
->busy_reading_waiters
= true;
1709 mutex_unlock(&cache
->lock
);
1713 mutex_lock(&cache
->lock
);
1718 /*! Cache must be locked.
1721 wait_for_busy_reading_blocks(block_cache
* cache
)
1723 while (cache
->busy_reading_count
!= 0) {
1724 // wait for all blocks to be read in
1725 ConditionVariableEntry entry
;
1726 cache
->busy_reading_condition
.Add(&entry
);
1727 cache
->busy_reading_waiters
= true;
1729 mutex_unlock(&cache
->lock
);
1733 mutex_lock(&cache
->lock
);
1738 /*! Cache must be locked.
1741 wait_for_busy_writing_block(block_cache
* cache
, cached_block
* block
)
1743 while (block
->busy_writing
) {
1744 // wait for all blocks to be written back
1745 ConditionVariableEntry entry
;
1746 cache
->busy_writing_condition
.Add(&entry
);
1747 block
->busy_writing_waiters
= true;
1749 mutex_unlock(&cache
->lock
);
1753 mutex_lock(&cache
->lock
);
1758 /*! Cache must be locked.
1761 wait_for_busy_writing_blocks(block_cache
* cache
)
1763 while (cache
->busy_writing_count
!= 0) {
1764 // wait for all blocks to be written back
1765 ConditionVariableEntry entry
;
1766 cache
->busy_writing_condition
.Add(&entry
);
1767 cache
->busy_writing_waiters
= true;
1769 mutex_unlock(&cache
->lock
);
1773 mutex_lock(&cache
->lock
);
1778 /*! Removes a reference from the specified \a block. If this was the last
1779 reference, the block is moved into the unused list.
1780 In low memory situations, it will also free some blocks from that list,
1781 but not necessarily the \a block it just released.
1784 put_cached_block(block_cache
* cache
, cached_block
* block
)
1786 #if BLOCK_CACHE_DEBUG_CHANGED
1787 if (!block
->is_dirty
&& block
->compare
!= NULL
1788 && memcmp(block
->current_data
, block
->compare
, cache
->block_size
)) {
1789 dprintf("new block:\n");
1790 dump_block((const char*)block
->current_data
, 256, " ");
1791 dprintf("unchanged block:\n");
1792 dump_block((const char*)block
->compare
, 256, " ");
1793 BlockWriter::WriteBlock(cache
, block
);
1794 panic("block_cache: supposed to be clean block was changed!\n");
1796 cache
->Free(block
->compare
);
1797 block
->compare
= NULL
;
1800 TB(Put(cache
, block
));
1802 if (block
->ref_count
< 1) {
1803 panic("Invalid ref_count for block %p, cache %p\n", block
, cache
);
1807 if (--block
->ref_count
== 0
1808 && block
->transaction
== NULL
&& block
->previous_transaction
== NULL
) {
1809 // This block is not used anymore, and not part of any transaction
1810 block
->is_writing
= false;
1812 if (block
->discard
) {
1813 cache
->RemoveBlock(block
);
1815 // put this block in the list of unused blocks
1816 ASSERT(!block
->unused
);
1817 block
->unused
= true;
1819 ASSERT(block
->original_data
== NULL
&& block
->parent_data
== NULL
);
1820 cache
->unused_blocks
.Add(block
);
1821 cache
->unused_block_count
++;
1828 put_cached_block(block_cache
* cache
, off_t blockNumber
)
1830 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
1831 panic("put_cached_block: invalid block number %" B_PRIdOFF
" (max %" B_PRIdOFF
")",
1832 blockNumber
, cache
->max_blocks
- 1);
1835 cached_block
* block
= cache
->hash
->Lookup(blockNumber
);
1837 put_cached_block(cache
, block
);
1839 TB(Error(cache
, blockNumber
, "put unknown"));
1844 /*! Retrieves the block \a blockNumber from the hash table, if it's already
1845 there, or reads it from the disk.
1846 You need to have the cache locked when calling this function.
1848 \param _allocated tells you whether or not a new block has been allocated
1849 to satisfy your request.
1850 \param readBlock if \c false, the block will not be read in case it was
1851 not already in the cache. The block you retrieve may contain random
1852 data. If \c true, the cache will be temporarily unlocked while the
1855 static cached_block
*
1856 get_cached_block(block_cache
* cache
, off_t blockNumber
, bool* _allocated
,
1857 bool readBlock
= true)
1859 ASSERT_LOCKED_MUTEX(&cache
->lock
);
1861 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
1862 panic("get_cached_block: invalid block number %" B_PRIdOFF
" (max %" B_PRIdOFF
")",
1863 blockNumber
, cache
->max_blocks
- 1);
1868 cached_block
* block
= cache
->hash
->Lookup(blockNumber
);
1869 *_allocated
= false;
1871 if (block
== NULL
) {
1872 // put block into cache
1873 block
= cache
->NewBlock(blockNumber
);
1877 cache
->hash
->Insert(block
);
1879 } else if (block
->busy_reading
) {
1880 // The block is currently busy_reading - wait and try again later
1881 wait_for_busy_reading_block(cache
, block
);
1885 if (block
->unused
) {
1886 //TRACE(("remove block %" B_PRIdOFF " from unused\n", blockNumber));
1887 block
->unused
= false;
1888 cache
->unused_blocks
.Remove(block
);
1889 cache
->unused_block_count
--;
1892 if (*_allocated
&& readBlock
) {
1893 // read block into cache
1894 int32 blockSize
= cache
->block_size
;
1896 mark_block_busy_reading(cache
, block
);
1897 mutex_unlock(&cache
->lock
);
1899 ssize_t bytesRead
= read_pos(cache
->fd
, blockNumber
* blockSize
,
1900 block
->current_data
, blockSize
);
1902 mutex_lock(&cache
->lock
);
1903 if (bytesRead
< blockSize
) {
1904 cache
->RemoveBlock(block
);
1905 TB(Error(cache
, blockNumber
, "read failed", bytesRead
));
1907 TRACE_ALWAYS(("could not read block %" B_PRIdOFF
": bytesRead: %zd, error: %s\n",
1908 blockNumber
, bytesRead
, strerror(errno
)));
1911 TB(Read(cache
, block
));
1913 mark_block_unbusy_reading(cache
, block
);
1917 block
->last_accessed
= system_time() / 1000000L;
1923 /*! Returns the writable block data for the requested blockNumber.
1924 If \a cleared is true, the block is not read from disk; an empty block
1927 This is the only method to insert a block into a transaction. It makes
1928 sure that the previous block contents are preserved in that case.
1931 get_writable_cached_block(block_cache
* cache
, off_t blockNumber
, off_t base
,
1932 off_t length
, int32 transactionID
, bool cleared
)
1934 TRACE(("get_writable_cached_block(blockNumber = %" B_PRIdOFF
", transaction = %" B_PRId32
")\n",
1935 blockNumber
, transactionID
));
1937 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
1938 panic("get_writable_cached_block: invalid block number %" B_PRIdOFF
" (max %" B_PRIdOFF
")",
1939 blockNumber
, cache
->max_blocks
- 1);
1943 cached_block
* block
= get_cached_block(cache
, blockNumber
, &allocated
,
1948 if (block
->busy_writing
)
1949 wait_for_busy_writing_block(cache
, block
);
1951 block
->discard
= false;
1953 // if there is no transaction support, we just return the current block
1954 if (transactionID
== -1) {
1956 mark_block_busy_reading(cache
, block
);
1957 mutex_unlock(&cache
->lock
);
1959 memset(block
->current_data
, 0, cache
->block_size
);
1961 mutex_lock(&cache
->lock
);
1962 mark_block_unbusy_reading(cache
, block
);
1965 block
->is_writing
= true;
1967 if (!block
->is_dirty
) {
1968 cache
->num_dirty_blocks
++;
1969 block
->is_dirty
= true;
1970 // mark the block as dirty
1973 TB(Get(cache
, block
));
1974 return block
->current_data
;
1977 cache_transaction
* transaction
= block
->transaction
;
1979 if (transaction
!= NULL
&& transaction
->id
!= transactionID
) {
1980 // TODO: we have to wait here until the other transaction is done.
1981 // Maybe we should even panic, since we can't prevent any deadlocks.
1982 panic("get_writable_cached_block(): asked to get busy writable block "
1983 "(transaction %" B_PRId32
")\n", block
->transaction
->id
);
1984 put_cached_block(cache
, block
);
1987 if (transaction
== NULL
&& transactionID
!= -1) {
1988 // get new transaction
1989 transaction
= lookup_transaction(cache
, transactionID
);
1990 if (transaction
== NULL
) {
1991 panic("get_writable_cached_block(): invalid transaction %" B_PRId32
"!\n",
1993 put_cached_block(cache
, block
);
1996 if (!transaction
->open
) {
1997 panic("get_writable_cached_block(): transaction already done!\n");
1998 put_cached_block(cache
, block
);
2002 block
->transaction
= transaction
;
2004 // attach the block to the transaction block list
2005 block
->transaction_next
= transaction
->first_block
;
2006 transaction
->first_block
= block
;
2007 transaction
->num_blocks
++;
2009 if (transaction
!= NULL
)
2010 transaction
->last_used
= system_time();
2012 bool wasUnchanged
= block
->original_data
== NULL
2013 || block
->previous_transaction
!= NULL
;
2015 if (!(allocated
&& cleared
) && block
->original_data
== NULL
) {
2016 // we already have data, so we need to preserve it
2017 block
->original_data
= cache
->Allocate();
2018 if (block
->original_data
== NULL
) {
2019 TB(Error(cache
, blockNumber
, "allocate original failed"));
2020 FATAL(("could not allocate original_data\n"));
2021 put_cached_block(cache
, block
);
2025 mark_block_busy_reading(cache
, block
);
2026 mutex_unlock(&cache
->lock
);
2028 memcpy(block
->original_data
, block
->current_data
, cache
->block_size
);
2030 mutex_lock(&cache
->lock
);
2031 mark_block_unbusy_reading(cache
, block
);
2033 if (block
->parent_data
== block
->current_data
) {
2034 // remember any previous contents for the parent transaction
2035 block
->parent_data
= cache
->Allocate();
2036 if (block
->parent_data
== NULL
) {
2037 // TODO: maybe we should just continue the current transaction in
2039 TB(Error(cache
, blockNumber
, "allocate parent failed"));
2040 FATAL(("could not allocate parent\n"));
2041 put_cached_block(cache
, block
);
2045 mark_block_busy_reading(cache
, block
);
2046 mutex_unlock(&cache
->lock
);
2048 memcpy(block
->parent_data
, block
->current_data
, cache
->block_size
);
2050 mutex_lock(&cache
->lock
);
2051 mark_block_unbusy_reading(cache
, block
);
2053 transaction
->sub_num_blocks
++;
2054 } else if (transaction
!= NULL
&& transaction
->has_sub_transaction
2055 && block
->parent_data
== NULL
&& wasUnchanged
)
2056 transaction
->sub_num_blocks
++;
2059 mark_block_busy_reading(cache
, block
);
2060 mutex_unlock(&cache
->lock
);
2062 memset(block
->current_data
, 0, cache
->block_size
);
2064 mutex_lock(&cache
->lock
);
2065 mark_block_unbusy_reading(cache
, block
);
2068 block
->is_dirty
= true;
2069 TB(Get(cache
, block
));
2070 TB2(BlockData(cache
, block
, "get writable"));
2072 return block
->current_data
;
2076 #if DEBUG_BLOCK_CACHE
2080 dump_block(cached_block
* block
)
2082 kprintf("%08lx %9" B_PRIdOFF
" %08lx %08lx %08lx %5" B_PRId32
" %6" B_PRId32
2083 " %c%c%c%c%c%c %08lx %08lx\n",
2084 (addr_t
)block
, block
->block_number
,
2085 (addr_t
)block
->current_data
, (addr_t
)block
->original_data
,
2086 (addr_t
)block
->parent_data
, block
->ref_count
, block
->LastAccess(),
2087 block
->busy_reading
? 'r' : '-', block
->busy_writing
? 'w' : '-',
2088 block
->is_writing
? 'W' : '-', block
->is_dirty
? 'D' : '-',
2089 block
->unused
? 'U' : '-', block
->discard
? 'D' : '-',
2090 (addr_t
)block
->transaction
,
2091 (addr_t
)block
->previous_transaction
);
2096 dump_block_long(cached_block
* block
)
2098 kprintf("BLOCK %p\n", block
);
2099 kprintf(" current data: %p\n", block
->current_data
);
2100 kprintf(" original data: %p\n", block
->original_data
);
2101 kprintf(" parent data: %p\n", block
->parent_data
);
2102 #if BLOCK_CACHE_DEBUG_CHANGED
2103 kprintf(" compare data: %p\n", block
->compare
);
2105 kprintf(" ref_count: %" B_PRId32
"\n", block
->ref_count
);
2106 kprintf(" accessed: %" B_PRId32
"\n", block
->LastAccess());
2107 kprintf(" flags: ");
2108 if (block
->busy_reading
)
2109 kprintf(" busy_reading");
2110 if (block
->busy_writing
)
2111 kprintf(" busy_writing");
2112 if (block
->is_writing
)
2113 kprintf(" is-writing");
2114 if (block
->is_dirty
)
2115 kprintf(" is-dirty");
2119 kprintf(" discard");
2121 if (block
->transaction
!= NULL
) {
2122 kprintf(" transaction: %p (%" B_PRId32
")\n", block
->transaction
,
2123 block
->transaction
->id
);
2124 if (block
->transaction_next
!= NULL
) {
2125 kprintf(" next in transaction: %" B_PRIdOFF
"\n",
2126 block
->transaction_next
->block_number
);
2129 if (block
->previous_transaction
!= NULL
) {
2130 kprintf(" previous transaction: %p (%" B_PRId32
")\n",
2131 block
->previous_transaction
,
2132 block
->previous_transaction
->id
);
2135 set_debug_variable("_current", (addr_t
)block
->current_data
);
2136 set_debug_variable("_original", (addr_t
)block
->original_data
);
2137 set_debug_variable("_parent", (addr_t
)block
->parent_data
);
2142 dump_cached_block(int argc
, char** argv
)
2145 kprintf("usage: %s <block-address>\n", argv
[0]);
2149 dump_block_long((struct cached_block
*)(addr_t
)parse_expression(argv
[1]));
2155 dump_cache(int argc
, char** argv
)
2157 bool showTransactions
= false;
2158 bool showBlocks
= false;
2160 while (argv
[i
] != NULL
&& argv
[i
][0] == '-') {
2161 for (char* arg
= &argv
[i
][1]; arg
[0]; arg
++) {
2167 showTransactions
= true;
2170 print_debugger_command_usage(argv
[0]);
2178 print_debugger_command_usage(argv
[0]);
2182 block_cache
* cache
= (struct block_cache
*)(addr_t
)parse_expression(argv
[i
]);
2183 if (cache
== NULL
) {
2184 kprintf("invalid cache address\n");
2188 off_t blockNumber
= -1;
2190 blockNumber
= parse_expression(argv
[i
+ 1]);
2191 cached_block
* block
= cache
->hash
->Lookup(blockNumber
);
2193 dump_block_long(block
);
2195 kprintf("block %" B_PRIdOFF
" not found\n", blockNumber
);
2199 kprintf("BLOCK CACHE: %p\n", cache
);
2201 kprintf(" fd: %d\n", cache
->fd
);
2202 kprintf(" max_blocks: %" B_PRIdOFF
"\n", cache
->max_blocks
);
2203 kprintf(" block_size: %zu\n", cache
->block_size
);
2204 kprintf(" next_transaction_id: %" B_PRId32
"\n", cache
->next_transaction_id
);
2205 kprintf(" buffer_cache: %p\n", cache
->buffer_cache
);
2206 kprintf(" busy_reading: %" B_PRIu32
", %s waiters\n", cache
->busy_reading_count
,
2207 cache
->busy_reading_waiters
? "has" : "no");
2208 kprintf(" busy_writing: %" B_PRIu32
", %s waiters\n", cache
->busy_writing_count
,
2209 cache
->busy_writing_waiters
? "has" : "no");
2211 if (!cache
->pending_notifications
.IsEmpty()) {
2212 kprintf(" pending notifications:\n");
2214 NotificationList::Iterator iterator
2215 = cache
->pending_notifications
.GetIterator();
2216 while (iterator
.HasNext()) {
2217 cache_notification
* notification
= iterator
.Next();
2219 kprintf(" %p %5" B_PRIx32
" %p - %p\n", notification
,
2220 notification
->events_pending
, notification
->hook
,
2221 notification
->data
);
2225 if (showTransactions
) {
2226 kprintf(" transactions:\n");
2227 kprintf("address id state blocks main sub\n");
2229 TransactionTable::Iterator
iterator(cache
->transaction_hash
);
2231 while (iterator
.HasNext()) {
2232 cache_transaction
* transaction
= iterator
.Next();
2233 kprintf("%p %5" B_PRId32
" %-7s %5" B_PRId32
" %5" B_PRId32
" %5"
2234 B_PRId32
"\n", transaction
, transaction
->id
,
2235 transaction
->open
? "open" : "closed",
2236 transaction
->num_blocks
, transaction
->main_num_blocks
,
2237 transaction
->sub_num_blocks
);
2242 kprintf(" blocks:\n");
2243 kprintf("address block no. current original parent refs access "
2244 "flags transact prev. trans\n");
2247 uint32 referenced
= 0;
2250 uint32 discarded
= 0;
2251 BlockTable::Iterator
iterator(cache
->hash
);
2252 while (iterator
.HasNext()) {
2253 cached_block
* block
= iterator
.Next();
2257 if (block
->is_dirty
)
2261 if (block
->ref_count
)
2266 kprintf(" %" B_PRIu32
" blocks total, %" B_PRIu32
" dirty, %" B_PRIu32
2267 " discarded, %" B_PRIu32
" referenced, %" B_PRIu32
" busy, %" B_PRIu32
2269 count
, dirty
, discarded
, referenced
, cache
->busy_reading_count
,
2270 cache
->unused_block_count
);
2276 dump_transaction(int argc
, char** argv
)
2278 bool showBlocks
= false;
2280 if (argc
> 1 && !strcmp(argv
[1], "-b")) {
2285 if (argc
- i
< 1 || argc
- i
> 2) {
2286 print_debugger_command_usage(argv
[0]);
2290 cache_transaction
* transaction
= NULL
;
2292 if (argc
- i
== 1) {
2293 transaction
= (cache_transaction
*)(addr_t
)parse_expression(argv
[i
]);
2295 block_cache
* cache
= (block_cache
*)(addr_t
)parse_expression(argv
[i
]);
2296 int32 id
= parse_expression(argv
[i
+ 1]);
2297 transaction
= lookup_transaction(cache
, id
);
2298 if (transaction
== NULL
) {
2299 kprintf("No transaction with ID %" B_PRId32
" found.\n", id
);
2304 kprintf("TRANSACTION %p\n", transaction
);
2306 kprintf(" id: %" B_PRId32
"\n", transaction
->id
);
2307 kprintf(" num block: %" B_PRId32
"\n", transaction
->num_blocks
);
2308 kprintf(" main num block: %" B_PRId32
"\n", transaction
->main_num_blocks
);
2309 kprintf(" sub num block: %" B_PRId32
"\n", transaction
->sub_num_blocks
);
2310 kprintf(" has sub: %d\n", transaction
->has_sub_transaction
);
2311 kprintf(" state: %s\n", transaction
->open
? "open" : "closed");
2312 kprintf(" idle: %" B_PRId64
" secs\n",
2313 (system_time() - transaction
->last_used
) / 1000000);
2315 kprintf(" listeners:\n");
2317 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
2318 while (iterator
.HasNext()) {
2319 cache_listener
* listener
= iterator
.Next();
2321 kprintf(" %p %5" B_PRIx32
" %p - %p\n", listener
, listener
->events_pending
,
2322 listener
->hook
, listener
->data
);
2328 kprintf(" blocks:\n");
2329 kprintf("address block no. current original parent refs access "
2330 "flags transact prev. trans\n");
2332 cached_block
* block
= transaction
->first_block
;
2333 while (block
!= NULL
) {
2335 block
= block
->transaction_next
;
2340 block_list::Iterator blockIterator
= transaction
->blocks
.GetIterator();
2341 while (blockIterator
.HasNext()) {
2342 block
= blockIterator
.Next();
2351 dump_caches(int argc
, char** argv
)
2353 kprintf("Block caches:\n");
2354 DoublyLinkedList
<block_cache
>::Iterator i
= sCaches
.GetIterator();
2355 while (i
.HasNext()) {
2356 block_cache
* cache
= i
.Next();
2357 if (cache
== (block_cache
*)&sMarkCache
)
2360 kprintf(" %p\n", cache
);
2367 #if BLOCK_CACHE_BLOCK_TRACING >= 2
2369 dump_block_data(int argc
, char** argv
)
2371 using namespace BlockTracing
;
2373 // Determine which blocks to show
2375 bool printStackTrace
= true;
2378 while (i
< argc
&& argv
[i
][0] == '-') {
2379 char* arg
= &argv
[i
][1];
2383 which
|= BlockData::kCurrent
;
2386 which
|= BlockData::kParent
;
2389 which
|= BlockData::kOriginal
;
2393 kprintf("invalid block specifier (only o/c/p are "
2403 which
= BlockData::kCurrent
| BlockData::kParent
| BlockData::kOriginal
;
2406 print_debugger_command_usage(argv
[0]);
2410 // Get the range of blocks to print
2412 int64 from
= parse_expression(argv
[i
]);
2415 to
= parse_expression(argv
[i
+ 1]);
2420 uint32 size
= LONG_MAX
;
2422 offset
= parse_expression(argv
[i
+ 2]);
2424 size
= parse_expression(argv
[i
+ 3]);
2426 TraceEntryIterator iterator
;
2427 iterator
.MoveTo(from
- 1);
2429 static char sBuffer
[1024];
2430 LazyTraceOutput
out(sBuffer
, sizeof(sBuffer
), TRACE_OUTPUT_TEAM_ID
);
2432 while (TraceEntry
* entry
= iterator
.Next()) {
2433 int32 index
= iterator
.Index();
2437 Action
* action
= dynamic_cast<Action
*>(entry
);
2438 if (action
!= NULL
) {
2440 out
.DumpEntry(action
);
2444 BlockData
* blockData
= dynamic_cast<BlockData
*>(entry
);
2445 if (blockData
== NULL
)
2450 const char* dump
= out
.DumpEntry(entry
);
2451 int length
= strlen(dump
);
2452 if (length
> 0 && dump
[length
- 1] == '\n')
2455 kprintf("%5" B_PRId32
". %.*s\n", index
, length
, dump
);
2457 if (printStackTrace
) {
2459 entry
->DumpStackTrace(out
);
2461 kputs(out
.Buffer());
2464 blockData
->DumpBlocks(which
, offset
, size
);
2469 #endif // BLOCK_CACHE_BLOCK_TRACING >= 2
2472 #endif // DEBUG_BLOCK_CACHE
2475 /*! Traverses through the block_cache list, and returns one cache after the
2476 other. The cache returned is automatically locked when you get it, and
2477 unlocked with the next call to this function. Ignores caches that are in
2479 Returns \c NULL when the end of the list is reached.
2482 get_next_locked_block_cache(block_cache
* last
)
2484 MutexLocker
_(sCachesLock
);
2488 mutex_unlock(&last
->lock
);
2490 cache
= sCaches
.GetNext((block_cache
*)&sMarkCache
);
2491 sCaches
.Remove((block_cache
*)&sMarkCache
);
2493 cache
= sCaches
.Head();
2495 if (cache
!= NULL
) {
2496 mutex_lock(&cache
->lock
);
2497 sCaches
.Insert(sCaches
.GetNext(cache
), (block_cache
*)&sMarkCache
);
2504 /*! Background thread that continuously checks for pending notifications of
2506 Every two seconds, it will also write back up to 64 blocks per cache.
2509 block_notifier_and_writer(void* /*data*/)
2511 const bigtime_t kTimeout
= 2000000LL;
2512 bigtime_t timeout
= kTimeout
;
2515 bigtime_t start
= system_time();
2517 status_t status
= acquire_sem_etc(sEventSemaphore
, 1,
2518 B_RELATIVE_TIMEOUT
, timeout
);
2519 if (status
== B_OK
) {
2520 flush_pending_notifications();
2521 timeout
-= system_time() - start
;
2525 // write 64 blocks of each block_cache every two seconds
2526 // TODO: change this once we have an I/O scheduler
2529 object_cache_get_usage(sBlockCache
, &usedMemory
);
2531 block_cache
* cache
= NULL
;
2532 while ((cache
= get_next_locked_block_cache(cache
)) != NULL
) {
2533 BlockWriter
writer(cache
, 64);
2535 size_t cacheUsedMemory
;
2536 object_cache_get_usage(cache
->buffer_cache
, &cacheUsedMemory
);
2537 usedMemory
+= cacheUsedMemory
;
2539 if (cache
->num_dirty_blocks
) {
2540 // This cache is not using transactions, we'll scan the blocks
2542 BlockTable::Iterator
iterator(cache
->hash
);
2544 while (iterator
.HasNext()) {
2545 cached_block
* block
= iterator
.Next();
2546 if (block
->CanBeWritten() && !writer
.Add(block
))
2551 TransactionTable::Iterator
iterator(cache
->transaction_hash
);
2553 while (iterator
.HasNext()) {
2554 cache_transaction
* transaction
= iterator
.Next();
2555 if (transaction
->open
) {
2556 if (system_time() > transaction
->last_used
2557 + kTransactionIdleTime
) {
2558 // Transaction is open but idle
2559 notify_transaction_listeners(cache
, transaction
,
2566 // we ignore this one
2567 if (!writer
.Add(transaction
, hasLeftOvers
))
2574 if ((block_cache_used_memory() / B_PAGE_SIZE
)
2575 > vm_page_num_pages() / 2) {
2576 // Try to reduce memory usage to half of the available
2578 cache
->RemoveUnusedBlocks(1000, 10);
2582 MutexLocker
_(sCachesMemoryUseLock
);
2583 sUsedMemory
= usedMemory
;
2586 // never can get here
2591 /*! Notify function for wait_for_notifications(). */
2593 notify_sync(int32 transactionID
, int32 event
, void* _cache
)
2595 block_cache
* cache
= (block_cache
*)_cache
;
2597 cache
->condition_variable
.NotifyOne();
2601 /*! Must be called with the sCachesLock held. */
2603 is_valid_cache(block_cache
* cache
)
2605 ASSERT_LOCKED_MUTEX(&sCachesLock
);
2607 DoublyLinkedList
<block_cache
>::Iterator iterator
= sCaches
.GetIterator();
2608 while (iterator
.HasNext()) {
2609 if (cache
== iterator
.Next())
2617 /*! Waits until all pending notifications are carried out.
2618 Safe to be called from the block writer/notifier thread.
2619 You must not hold the \a cache lock when calling this function.
2622 wait_for_notifications(block_cache
* cache
)
2624 MutexLocker
locker(sCachesLock
);
2626 if (find_thread(NULL
) == sNotifierWriterThread
) {
2627 // We're the notifier thread, don't wait, but flush all pending
2628 // notifications directly.
2629 if (is_valid_cache(cache
))
2630 flush_pending_notifications(cache
);
2634 // add sync notification
2635 cache_notification notification
;
2636 set_notification(NULL
, notification
, TRANSACTION_WRITTEN
, notify_sync
,
2639 ConditionVariableEntry entry
;
2640 cache
->condition_variable
.Add(&entry
);
2642 add_notification(cache
, ¬ification
, TRANSACTION_WRITTEN
, false);
2645 // wait for notification hook to be called
2651 block_cache_init(void)
2653 sBlockCache
= create_object_cache_etc("cached blocks", sizeof(cached_block
),
2654 8, 0, 0, 0, CACHE_LARGE_SLAB
, NULL
, NULL
, NULL
, NULL
);
2655 if (sBlockCache
== NULL
)
2658 new (&sCaches
) DoublyLinkedList
<block_cache
>;
2659 // manually call constructor
2661 sEventSemaphore
= create_sem(0, "block cache event");
2662 if (sEventSemaphore
< B_OK
)
2663 return sEventSemaphore
;
2665 sNotifierWriterThread
= spawn_kernel_thread(&block_notifier_and_writer
,
2666 "block notifier/writer", B_LOW_PRIORITY
, NULL
);
2667 if (sNotifierWriterThread
>= B_OK
)
2668 resume_thread(sNotifierWriterThread
);
2670 #if DEBUG_BLOCK_CACHE
2671 add_debugger_command_etc("block_caches", &dump_caches
,
2672 "dumps all block caches", "\n", 0);
2673 add_debugger_command_etc("block_cache", &dump_cache
,
2674 "dumps a specific block cache",
2675 "[-bt] <cache-address> [block-number]\n"
2676 " -t lists the transactions\n"
2677 " -b lists all blocks\n", 0);
2678 add_debugger_command("cached_block", &dump_cached_block
,
2679 "dumps the specified cached block");
2680 add_debugger_command_etc("transaction", &dump_transaction
,
2681 "dumps a specific transaction", "[-b] ((<cache> <id>) | <transaction>)\n"
2682 "Either use a block cache pointer and an ID or a pointer to the transaction.\n"
2683 " -b lists all blocks that are part of this transaction\n", 0);
2684 # if BLOCK_CACHE_BLOCK_TRACING >= 2
2685 add_debugger_command_etc("block_cache_data", &dump_block_data
,
2686 "dumps the data blocks logged for the actions",
2687 "[-cpo] <from> [<to> [<offset> [<size>]]]\n"
2688 "If no data specifier is used, all blocks are shown by default.\n"
2689 " -c the current data is shown, if available.\n"
2690 " -p the parent data is shown, if available.\n"
2691 " -o the original data is shown, if available.\n"
2692 " <from> first index of tracing entries to show.\n"
2693 " <to> if given, the last entry. If not, only <from> is shown.\n"
2694 " <offset> the offset of the block data.\n"
2695 " <from> the size of the block data that is dumped\n", 0);
2697 #endif // DEBUG_BLOCK_CACHE
2704 block_cache_used_memory(void)
2706 MutexLocker
_(sCachesMemoryUseLock
);
2711 // #pragma mark - public transaction API
2715 cache_start_transaction(void* _cache
)
2717 block_cache
* cache
= (block_cache
*)_cache
;
2718 TransactionLocker
locker(cache
);
2720 if (cache
->last_transaction
&& cache
->last_transaction
->open
) {
2721 panic("last transaction (%" B_PRId32
") still open!\n",
2722 cache
->last_transaction
->id
);
2725 cache_transaction
* transaction
= new(std::nothrow
) cache_transaction
;
2726 if (transaction
== NULL
)
2729 transaction
->id
= atomic_add(&cache
->next_transaction_id
, 1);
2730 cache
->last_transaction
= transaction
;
2732 TRACE(("cache_start_transaction(): id %" B_PRId32
" started\n", transaction
->id
));
2733 T(Action("start", cache
, transaction
));
2735 cache
->transaction_hash
->Insert(transaction
);
2737 return transaction
->id
;
2742 cache_sync_transaction(void* _cache
, int32 id
)
2744 block_cache
* cache
= (block_cache
*)_cache
;
2747 TRACE(("cache_sync_transaction(id %" B_PRId32
")\n", id
));
2750 TransactionLocker
locker(cache
);
2753 BlockWriter
writer(cache
);
2754 TransactionTable::Iterator
iterator(cache
->transaction_hash
);
2756 while (iterator
.HasNext()) {
2757 // close all earlier transactions which haven't been closed yet
2758 cache_transaction
* transaction
= iterator
.Next();
2760 if (transaction
->busy_writing_count
!= 0) {
2764 if (transaction
->id
<= id
&& !transaction
->open
) {
2765 // write back all of their remaining dirty blocks
2766 T(Action("sync", cache
, transaction
));
2769 writer
.Add(transaction
, hasLeftOvers
);
2772 // This transaction contains blocks that a previous
2773 // transaction is trying to write back in this write run
2779 status_t status
= writer
.Write();
2784 wait_for_notifications(cache
);
2785 // make sure that all pending TRANSACTION_WRITTEN notifications
2786 // are handled after we return
2792 cache_end_transaction(void* _cache
, int32 id
,
2793 transaction_notification_hook hook
, void* data
)
2795 block_cache
* cache
= (block_cache
*)_cache
;
2796 TransactionLocker
locker(cache
);
2798 TRACE(("cache_end_transaction(id = %" B_PRId32
")\n", id
));
2800 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
2801 if (transaction
== NULL
) {
2802 panic("cache_end_transaction(): invalid transaction ID\n");
2806 // Write back all pending transaction blocks
2807 status_t status
= write_blocks_in_previous_transaction(cache
, transaction
);
2811 notify_transaction_listeners(cache
, transaction
, TRANSACTION_ENDED
);
2814 && add_transaction_listener(cache
, transaction
, TRANSACTION_WRITTEN
,
2815 hook
, data
) != B_OK
) {
2819 T(Action("end", cache
, transaction
));
2821 // iterate through all blocks and free the unchanged original contents
2824 for (cached_block
* block
= transaction
->first_block
; block
!= NULL
;
2826 next
= block
->transaction_next
;
2827 ASSERT(block
->previous_transaction
== NULL
);
2829 if (block
->discard
) {
2830 // This block has been discarded in the transaction
2831 cache
->DiscardBlock(block
);
2832 transaction
->num_blocks
--;
2836 if (block
->original_data
!= NULL
) {
2837 cache
->Free(block
->original_data
);
2838 block
->original_data
= NULL
;
2840 if (block
->parent_data
!= NULL
) {
2841 ASSERT(transaction
->has_sub_transaction
);
2842 cache
->FreeBlockParentData(block
);
2845 // move the block to the previous transaction list
2846 transaction
->blocks
.Add(block
);
2848 block
->previous_transaction
= transaction
;
2849 block
->transaction_next
= NULL
;
2850 block
->transaction
= NULL
;
2853 transaction
->open
= false;
2859 cache_abort_transaction(void* _cache
, int32 id
)
2861 block_cache
* cache
= (block_cache
*)_cache
;
2862 TransactionLocker
locker(cache
);
2864 TRACE(("cache_abort_transaction(id = %" B_PRId32
")\n", id
));
2866 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
2867 if (transaction
== NULL
) {
2868 panic("cache_abort_transaction(): invalid transaction ID\n");
2872 T(Abort(cache
, transaction
));
2873 notify_transaction_listeners(cache
, transaction
, TRANSACTION_ABORTED
);
2875 // iterate through all blocks and restore their original contents
2877 cached_block
* block
= transaction
->first_block
;
2879 for (; block
!= NULL
; block
= next
) {
2880 next
= block
->transaction_next
;
2882 if (block
->original_data
!= NULL
) {
2883 TRACE(("cache_abort_transaction(id = %" B_PRId32
"): restored contents of "
2884 "block %" B_PRIdOFF
"\n", transaction
->id
, block
->block_number
));
2885 memcpy(block
->current_data
, block
->original_data
,
2887 cache
->Free(block
->original_data
);
2888 block
->original_data
= NULL
;
2890 if (transaction
->has_sub_transaction
&& block
->parent_data
!= NULL
)
2891 cache
->FreeBlockParentData(block
);
2893 block
->transaction_next
= NULL
;
2894 block
->transaction
= NULL
;
2895 block
->discard
= false;
2896 if (block
->previous_transaction
== NULL
)
2897 block
->is_dirty
= false;
2900 cache
->transaction_hash
->Remove(transaction
);
2901 delete_transaction(cache
, transaction
);
2906 /*! Acknowledges the current parent transaction, and starts a new transaction
2907 from its sub transaction.
2908 The new transaction also gets a new transaction ID.
2911 cache_detach_sub_transaction(void* _cache
, int32 id
,
2912 transaction_notification_hook hook
, void* data
)
2914 block_cache
* cache
= (block_cache
*)_cache
;
2915 TransactionLocker
locker(cache
);
2917 TRACE(("cache_detach_sub_transaction(id = %" B_PRId32
")\n", id
));
2919 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
2920 if (transaction
== NULL
) {
2921 panic("cache_detach_sub_transaction(): invalid transaction ID\n");
2924 if (!transaction
->has_sub_transaction
)
2927 // iterate through all blocks and free the unchanged original contents
2929 status_t status
= write_blocks_in_previous_transaction(cache
, transaction
);
2933 // create a new transaction for the sub transaction
2934 cache_transaction
* newTransaction
= new(std::nothrow
) cache_transaction
;
2935 if (newTransaction
== NULL
)
2938 newTransaction
->id
= atomic_add(&cache
->next_transaction_id
, 1);
2939 T(Detach(cache
, transaction
, newTransaction
));
2941 notify_transaction_listeners(cache
, transaction
, TRANSACTION_ENDED
);
2943 if (add_transaction_listener(cache
, transaction
, TRANSACTION_WRITTEN
, hook
,
2945 delete newTransaction
;
2949 cached_block
* last
= NULL
;
2951 for (cached_block
* block
= transaction
->first_block
; block
!= NULL
;
2953 next
= block
->transaction_next
;
2954 ASSERT(block
->previous_transaction
== NULL
);
2956 if (block
->discard
) {
2957 cache
->DiscardBlock(block
);
2958 transaction
->main_num_blocks
--;
2962 if (block
->parent_data
!= NULL
) {
2963 // The block changed in the parent - free the original data, since
2964 // they will be replaced by what is in current.
2965 ASSERT(block
->original_data
!= NULL
);
2966 cache
->Free(block
->original_data
);
2968 if (block
->parent_data
!= block
->current_data
) {
2969 // The block had been changed in both transactions
2970 block
->original_data
= block
->parent_data
;
2972 // The block has only been changed in the parent
2973 block
->original_data
= NULL
;
2976 // move the block to the previous transaction list
2977 transaction
->blocks
.Add(block
);
2978 block
->previous_transaction
= transaction
;
2979 block
->parent_data
= NULL
;
2982 if (block
->original_data
!= NULL
) {
2983 // This block had been changed in the current sub transaction,
2984 // we need to move this block over to the new transaction.
2985 ASSERT(block
->parent_data
== NULL
);
2988 newTransaction
->first_block
= block
;
2990 last
->transaction_next
= block
;
2992 block
->transaction
= newTransaction
;
2995 block
->transaction
= NULL
;
2997 block
->transaction_next
= NULL
;
3000 newTransaction
->num_blocks
= transaction
->sub_num_blocks
;
3002 transaction
->open
= false;
3003 transaction
->has_sub_transaction
= false;
3004 transaction
->num_blocks
= transaction
->main_num_blocks
;
3005 transaction
->sub_num_blocks
= 0;
3007 cache
->transaction_hash
->Insert(newTransaction
);
3008 cache
->last_transaction
= newTransaction
;
3010 return newTransaction
->id
;
3015 cache_abort_sub_transaction(void* _cache
, int32 id
)
3017 block_cache
* cache
= (block_cache
*)_cache
;
3018 TransactionLocker
locker(cache
);
3020 TRACE(("cache_abort_sub_transaction(id = %" B_PRId32
")\n", id
));
3022 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3023 if (transaction
== NULL
) {
3024 panic("cache_abort_sub_transaction(): invalid transaction ID\n");
3027 if (!transaction
->has_sub_transaction
)
3030 T(Abort(cache
, transaction
));
3031 notify_transaction_listeners(cache
, transaction
, TRANSACTION_ABORTED
);
3033 // revert all changes back to the version of the parent
3035 cached_block
* block
= transaction
->first_block
;
3036 cached_block
* last
= NULL
;
3038 for (; block
!= NULL
; block
= next
) {
3039 next
= block
->transaction_next
;
3041 if (block
->parent_data
== NULL
) {
3042 // The parent transaction didn't change the block, but the sub
3043 // transaction did - we need to revert to the original data.
3044 // The block is no longer part of the transaction
3045 if (block
->original_data
!= NULL
) {
3046 // The block might not have original data if was empty
3047 memcpy(block
->current_data
, block
->original_data
,
3052 last
->transaction_next
= next
;
3054 transaction
->first_block
= next
;
3056 block
->transaction_next
= NULL
;
3057 block
->transaction
= NULL
;
3058 transaction
->num_blocks
--;
3060 if (block
->previous_transaction
== NULL
) {
3061 cache
->Free(block
->original_data
);
3062 block
->original_data
= NULL
;
3063 block
->is_dirty
= false;
3065 if (block
->ref_count
== 0) {
3066 // Move the block into the unused list if possible
3067 block
->unused
= true;
3068 cache
->unused_blocks
.Add(block
);
3069 cache
->unused_block_count
++;
3073 if (block
->parent_data
!= block
->current_data
) {
3074 // The block has been changed and must be restored - the block
3075 // is still dirty and part of the transaction
3076 TRACE(("cache_abort_sub_transaction(id = %" B_PRId32
"): "
3077 "restored contents of block %" B_PRIdOFF
"\n",
3078 transaction
->id
, block
->block_number
));
3079 memcpy(block
->current_data
, block
->parent_data
,
3081 cache
->Free(block
->parent_data
);
3082 // The block stays dirty
3084 block
->parent_data
= NULL
;
3088 block
->discard
= false;
3091 // all subsequent changes will go into the main transaction
3092 transaction
->has_sub_transaction
= false;
3093 transaction
->sub_num_blocks
= 0;
3100 cache_start_sub_transaction(void* _cache
, int32 id
)
3102 block_cache
* cache
= (block_cache
*)_cache
;
3103 TransactionLocker
locker(cache
);
3105 TRACE(("cache_start_sub_transaction(id = %" B_PRId32
")\n", id
));
3107 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3108 if (transaction
== NULL
) {
3109 panic("cache_start_sub_transaction(): invalid transaction ID %" B_PRId32
"\n",
3114 notify_transaction_listeners(cache
, transaction
, TRANSACTION_ENDED
);
3116 // move all changed blocks up to the parent
3118 cached_block
* block
= transaction
->first_block
;
3120 for (; block
!= NULL
; block
= next
) {
3121 next
= block
->transaction_next
;
3123 if (block
->parent_data
!= NULL
) {
3124 // There already is an older sub transaction - we acknowledge
3125 // its changes and move its blocks up to the parent
3126 ASSERT(transaction
->has_sub_transaction
);
3127 cache
->FreeBlockParentData(block
);
3129 if (block
->discard
) {
3130 // This block has been discarded in the parent transaction.
3131 // Just throw away any changes made in this transaction, so that
3132 // it can still be reverted to its original contents if needed
3133 ASSERT(block
->previous_transaction
== NULL
);
3134 if (block
->original_data
!= NULL
) {
3135 memcpy(block
->current_data
, block
->original_data
,
3137 block
->original_data
= NULL
;
3142 // we "allocate" the parent data lazily, that means, we don't copy
3143 // the data (and allocate memory for it) until we need to
3144 block
->parent_data
= block
->current_data
;
3147 // all subsequent changes will go into the sub transaction
3148 transaction
->has_sub_transaction
= true;
3149 transaction
->main_num_blocks
= transaction
->num_blocks
;
3150 transaction
->sub_num_blocks
= 0;
3151 T(Action("start-sub", cache
, transaction
));
3157 /*! Adds a transaction listener that gets notified when the transaction
3158 is ended, aborted, written, or idle as specified by \a events.
3159 The listener gets automatically removed when the transaction ends.
3162 cache_add_transaction_listener(void* _cache
, int32 id
, int32 events
,
3163 transaction_notification_hook hook
, void* data
)
3165 block_cache
* cache
= (block_cache
*)_cache
;
3166 TransactionLocker
locker(cache
);
3168 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3169 if (transaction
== NULL
)
3172 return add_transaction_listener(cache
, transaction
, events
, hook
, data
);
3177 cache_remove_transaction_listener(void* _cache
, int32 id
,
3178 transaction_notification_hook hookFunction
, void* data
)
3180 block_cache
* cache
= (block_cache
*)_cache
;
3181 TransactionLocker
locker(cache
);
3183 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3184 if (transaction
== NULL
)
3187 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
3188 while (iterator
.HasNext()) {
3189 cache_listener
* listener
= iterator
.Next();
3190 if (listener
->data
== data
&& listener
->hook
== hookFunction
) {
3193 if (listener
->events_pending
!= 0) {
3194 MutexLocker
_(sNotificationsLock
);
3195 if (listener
->events_pending
!= 0)
3196 cache
->pending_notifications
.Remove(listener
);
3203 return B_ENTRY_NOT_FOUND
;
3208 cache_next_block_in_transaction(void* _cache
, int32 id
, bool mainOnly
,
3209 long* _cookie
, off_t
* _blockNumber
, void** _data
, void** _unchangedData
)
3211 cached_block
* block
= (cached_block
*)*_cookie
;
3212 block_cache
* cache
= (block_cache
*)_cache
;
3213 TransactionLocker
locker(cache
);
3215 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3216 if (transaction
== NULL
|| !transaction
->open
)
3220 block
= transaction
->first_block
;
3222 block
= block
->transaction_next
;
3224 if (transaction
->has_sub_transaction
) {
3226 // find next block that the parent changed
3227 while (block
!= NULL
&& block
->parent_data
== NULL
)
3228 block
= block
->transaction_next
;
3230 // find next non-discarded block
3231 while (block
!= NULL
&& block
->discard
)
3232 block
= block
->transaction_next
;
3237 return B_ENTRY_NOT_FOUND
;
3240 *_blockNumber
= block
->block_number
;
3242 *_data
= mainOnly
? block
->parent_data
: block
->current_data
;
3244 *_unchangedData
= block
->original_data
;
3246 *_cookie
= (addr_t
)block
;
3252 cache_blocks_in_transaction(void* _cache
, int32 id
)
3254 block_cache
* cache
= (block_cache
*)_cache
;
3255 TransactionLocker
locker(cache
);
3257 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3258 if (transaction
== NULL
)
3261 return transaction
->num_blocks
;
3265 /*! Returns the number of blocks that are part of the main transaction. If this
3266 transaction does not have a sub transaction yet, this is the same value as
3267 cache_blocks_in_transaction() would return.
3270 cache_blocks_in_main_transaction(void* _cache
, int32 id
)
3272 block_cache
* cache
= (block_cache
*)_cache
;
3273 TransactionLocker
locker(cache
);
3275 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3276 if (transaction
== NULL
)
3279 if (transaction
->has_sub_transaction
)
3280 return transaction
->main_num_blocks
;
3282 return transaction
->num_blocks
;
3287 cache_blocks_in_sub_transaction(void* _cache
, int32 id
)
3289 block_cache
* cache
= (block_cache
*)_cache
;
3290 TransactionLocker
locker(cache
);
3292 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
3293 if (transaction
== NULL
)
3296 return transaction
->sub_num_blocks
;
3300 // #pragma mark - public block cache API
3304 block_cache_delete(void* _cache
, bool allowWrites
)
3306 block_cache
* cache
= (block_cache
*)_cache
;
3309 block_cache_sync(cache
);
3311 mutex_lock(&sCachesLock
);
3312 sCaches
.Remove(cache
);
3313 mutex_unlock(&sCachesLock
);
3315 mutex_lock(&cache
->lock
);
3317 // wait for all blocks to become unbusy
3318 wait_for_busy_reading_blocks(cache
);
3319 wait_for_busy_writing_blocks(cache
);
3323 cached_block
* block
= cache
->hash
->Clear(true);
3324 while (block
!= NULL
) {
3325 cached_block
* next
= block
->next
;
3326 cache
->FreeBlock(block
);
3330 // free all transactions (they will all be aborted)
3332 cache_transaction
* transaction
= cache
->transaction_hash
->Clear(true);
3333 while (transaction
!= NULL
) {
3334 cache_transaction
* next
= transaction
->next
;
3344 block_cache_create(int fd
, off_t numBlocks
, size_t blockSize
, bool readOnly
)
3346 block_cache
* cache
= new(std::nothrow
) block_cache(fd
, numBlocks
, blockSize
,
3351 if (cache
->Init() != B_OK
) {
3356 MutexLocker
_(sCachesLock
);
3364 block_cache_sync(void* _cache
)
3366 block_cache
* cache
= (block_cache
*)_cache
;
3368 // We will sync all dirty blocks to disk that have a completed
3369 // transaction or no transaction only
3371 MutexLocker
locker(&cache
->lock
);
3373 BlockWriter
writer(cache
);
3374 BlockTable::Iterator
iterator(cache
->hash
);
3376 while (iterator
.HasNext()) {
3377 cached_block
* block
= iterator
.Next();
3378 if (block
->CanBeWritten())
3382 status_t status
= writer
.Write();
3386 wait_for_notifications(cache
);
3387 // make sure that all pending TRANSACTION_WRITTEN notifications
3388 // are handled after we return
3394 block_cache_sync_etc(void* _cache
, off_t blockNumber
, size_t numBlocks
)
3396 block_cache
* cache
= (block_cache
*)_cache
;
3398 // We will sync all dirty blocks to disk that have a completed
3399 // transaction or no transaction only
3401 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
3402 panic("block_cache_sync_etc: invalid block number %" B_PRIdOFF
3403 " (max %" B_PRIdOFF
")",
3404 blockNumber
, cache
->max_blocks
- 1);
3408 MutexLocker
locker(&cache
->lock
);
3409 BlockWriter
writer(cache
);
3411 for (; numBlocks
> 0; numBlocks
--, blockNumber
++) {
3412 cached_block
* block
= cache
->hash
->Lookup(blockNumber
);
3416 if (block
->CanBeWritten())
3420 status_t status
= writer
.Write();
3424 wait_for_notifications(cache
);
3425 // make sure that all pending TRANSACTION_WRITTEN notifications
3426 // are handled after we return
3431 /*! Discards a block from the current transaction or from the cache.
3432 You have to call this function when you no longer use a block, ie. when it
3433 might be reclaimed by the file cache in order to make sure they won't
3437 block_cache_discard(void* _cache
, off_t blockNumber
, size_t numBlocks
)
3439 // TODO: this could be a nice place to issue the ATA trim command
3440 block_cache
* cache
= (block_cache
*)_cache
;
3441 TransactionLocker
locker(cache
);
3443 BlockWriter
writer(cache
);
3445 for (size_t i
= 0; i
< numBlocks
; i
++, blockNumber
++) {
3446 cached_block
* block
= cache
->hash
->Lookup(blockNumber
);
3447 if (block
!= NULL
&& block
->previous_transaction
!= NULL
)
3452 // TODO: this can fail, too!
3454 blockNumber
-= numBlocks
;
3455 // reset blockNumber to its original value
3457 for (size_t i
= 0; i
< numBlocks
; i
++, blockNumber
++) {
3458 cached_block
* block
= cache
->hash
->Lookup(blockNumber
);
3462 ASSERT(block
->previous_transaction
== NULL
);
3464 if (block
->unused
) {
3465 cache
->unused_blocks
.Remove(block
);
3466 cache
->unused_block_count
--;
3467 cache
->RemoveBlock(block
);
3469 if (block
->transaction
!= NULL
&& block
->parent_data
!= NULL
3470 && block
->parent_data
!= block
->current_data
) {
3471 panic("Discarded block %" B_PRIdOFF
" has already been changed in this "
3472 "transaction!", blockNumber
);
3475 // mark it as discarded (in the current transaction only, if any)
3476 block
->discard
= true;
3483 block_cache_make_writable(void* _cache
, off_t blockNumber
, int32 transaction
)
3485 block_cache
* cache
= (block_cache
*)_cache
;
3486 MutexLocker
locker(&cache
->lock
);
3488 if (cache
->read_only
) {
3489 panic("tried to make block writable on a read-only cache!");
3493 // TODO: this can be done better!
3494 void* block
= get_writable_cached_block(cache
, blockNumber
,
3495 blockNumber
, 1, transaction
, false);
3496 if (block
!= NULL
) {
3497 put_cached_block((block_cache
*)_cache
, blockNumber
);
3506 block_cache_get_writable_etc(void* _cache
, off_t blockNumber
, off_t base
,
3507 off_t length
, int32 transaction
)
3509 block_cache
* cache
= (block_cache
*)_cache
;
3510 MutexLocker
locker(&cache
->lock
);
3512 TRACE(("block_cache_get_writable_etc(block = %" B_PRIdOFF
", transaction = %" B_PRId32
")\n",
3513 blockNumber
, transaction
));
3514 if (cache
->read_only
)
3515 panic("tried to get writable block on a read-only cache!");
3517 return get_writable_cached_block(cache
, blockNumber
, base
, length
,
3518 transaction
, false);
3523 block_cache_get_writable(void* _cache
, off_t blockNumber
, int32 transaction
)
3525 return block_cache_get_writable_etc(_cache
, blockNumber
,
3526 blockNumber
, 1, transaction
);
3531 block_cache_get_empty(void* _cache
, off_t blockNumber
, int32 transaction
)
3533 block_cache
* cache
= (block_cache
*)_cache
;
3534 MutexLocker
locker(&cache
->lock
);
3536 TRACE(("block_cache_get_empty(block = %" B_PRIdOFF
", transaction = %" B_PRId32
")\n",
3537 blockNumber
, transaction
));
3538 if (cache
->read_only
)
3539 panic("tried to get empty writable block on a read-only cache!");
3541 return get_writable_cached_block((block_cache
*)_cache
, blockNumber
,
3542 blockNumber
, 1, transaction
, true);
3547 block_cache_get_etc(void* _cache
, off_t blockNumber
, off_t base
, off_t length
)
3549 block_cache
* cache
= (block_cache
*)_cache
;
3550 MutexLocker
locker(&cache
->lock
);
3553 cached_block
* block
= get_cached_block(cache
, blockNumber
, &allocated
);
3557 #if BLOCK_CACHE_DEBUG_CHANGED
3558 if (block
->compare
== NULL
)
3559 block
->compare
= cache
->Allocate();
3560 if (block
->compare
!= NULL
)
3561 memcpy(block
->compare
, block
->current_data
, cache
->block_size
);
3563 TB(Get(cache
, block
));
3565 return block
->current_data
;
3570 block_cache_get(void* _cache
, off_t blockNumber
)
3572 return block_cache_get_etc(_cache
, blockNumber
, blockNumber
, 1);
3576 /*! Changes the internal status of a writable block to \a dirty. This can be
3577 helpful in case you realize you don't need to change that block anymore
3578 for whatever reason.
3580 Note, you must only use this function on blocks that were acquired
3584 block_cache_set_dirty(void* _cache
, off_t blockNumber
, bool dirty
,
3587 block_cache
* cache
= (block_cache
*)_cache
;
3588 MutexLocker
locker(&cache
->lock
);
3590 cached_block
* block
= cache
->hash
->Lookup(blockNumber
);
3593 if (block
->is_dirty
== dirty
) {
3594 // there is nothing to do for us
3598 // TODO: not yet implemented
3600 panic("block_cache_set_dirty(): not yet implemented that way!\n");
3607 block_cache_put(void* _cache
, off_t blockNumber
)
3609 block_cache
* cache
= (block_cache
*)_cache
;
3610 MutexLocker
locker(&cache
->lock
);
3612 put_cached_block(cache
, blockNumber
);