2 * Copyright 2004-2008, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
10 #include "DoublyLinkedList.h"
11 #include "fssh_atomic.h"
12 #include "fssh_errno.h"
13 #include "fssh_fs_cache.h"
14 #include "fssh_kernel_export.h"
15 #include "fssh_lock.h"
16 #include "fssh_string.h"
17 #include "fssh_unistd.h"
21 // TODO: this is a naive but growing implementation to test the API:
22 // 1) block reading/writing is not at all optimized for speed, it will
23 // just read and write single blocks.
24 // 2) the locking could be improved; getting a block should not need to
25 // wait for blocks to be written
26 // TODO: the retrieval/copy of the original data could be delayed until the
27 // new data must be written, ie. in low memory situations.
29 //#define TRACE_BLOCK_CACHE
30 #ifdef TRACE_BLOCK_CACHE
31 # define TRACE(x) fssh_dprintf x
38 // This macro is used for fatal situations that are acceptable in a running
39 // system, like out of memory situations - should only panic for debugging.
40 #define FATAL(x) fssh_panic x
43 #define offsetof(struct, member) 0
44 // TODO: I don't know why the offsetof() macro doesn't work in this context,
45 // but (0) is okay here...
53 //#define DEBUG_CHANGED
57 struct cache_transaction
;
60 typedef DoublyLinkedListLink
<cached_block
> block_link
;
63 cached_block
* next
; // next in hash
64 cached_block
* transaction_next
;
66 fssh_off_t block_number
;
80 cache_transaction
* transaction
;
81 cache_transaction
* previous_transaction
;
83 static int Compare(void* _cacheEntry
, const void* _block
);
84 static uint32_t Hash(void* _cacheEntry
, const void* _block
, uint32_t range
);
87 typedef DoublyLinkedList
<cached_block
,
88 DoublyLinkedListMemberGetLink
<cached_block
,
89 &cached_block::link
> > block_list
;
91 struct cache_notification
: DoublyLinkedListLinkImpl
<cache_notification
> {
92 int32_t transaction_id
;
93 int32_t events_pending
;
95 fssh_transaction_notification_hook hook
;
97 bool delete_after_event
;
100 typedef DoublyLinkedList
<cache_notification
> NotificationList
;
106 fssh_off_t max_blocks
;
107 fssh_size_t block_size
;
108 int32_t allocated_block_count
;
109 int32_t next_transaction_id
;
110 cache_transaction
* last_transaction
;
111 hash_table
* transaction_hash
;
113 block_list unused_blocks
;
117 NotificationList pending_notifications
;
119 block_cache(int fd
, fssh_off_t numBlocks
,
120 fssh_size_t blockSize
, bool readOnly
);
123 fssh_status_t
Init();
125 void Free(void* buffer
);
127 void RemoveUnusedBlocks(int32_t maxAccessed
= INT32_MAX
,
128 int32_t count
= INT32_MAX
);
129 void RemoveBlock(cached_block
* block
);
130 void DiscardBlock(cached_block
* block
);
131 void FreeBlock(cached_block
* block
);
132 cached_block
* NewBlock(fssh_off_t blockNumber
);
135 static const int32_t kMaxBlockCount
= 1024;
137 struct cache_listener
;
138 typedef DoublyLinkedListLink
<cache_listener
> listener_link
;
140 struct cache_listener
: cache_notification
{
144 typedef DoublyLinkedList
<cache_listener
,
145 DoublyLinkedListMemberGetLink
<cache_listener
,
146 &cache_listener::link
> > ListenerList
;
148 struct cache_transaction
{
151 cache_transaction
* next
;
154 int32_t main_num_blocks
;
155 int32_t sub_num_blocks
;
156 cached_block
* first_block
;
158 fssh_transaction_notification_hook notification_hook
;
159 void* notification_data
;
160 ListenerList listeners
;
162 bool has_sub_transaction
;
166 static fssh_status_t
write_cached_block(block_cache
* cache
, cached_block
* block
,
167 bool deleteTransaction
= true);
170 static fssh_mutex sNotificationsLock
;
173 // #pragma mark - notifications/listener
176 /*! Checks whether or not this is an event that closes a transaction. */
178 is_closing_event(int32_t event
)
180 return (event
& (FSSH_TRANSACTION_ABORTED
| FSSH_TRANSACTION_ENDED
)) != 0;
185 is_written_event(int32_t event
)
187 return (event
& FSSH_TRANSACTION_WRITTEN
) != 0;
191 /*! From the specified \a notification, it will remove the lowest pending
192 event, and return that one in \a _event.
193 If there is no pending event anymore, it will return \c false.
196 get_next_pending_event(cache_notification
* notification
, int32_t* _event
)
198 for (int32_t eventMask
= 1; eventMask
<= FSSH_TRANSACTION_IDLE
; eventMask
<<= 1) {
199 int32_t pending
= fssh_atomic_and(¬ification
->events_pending
,
202 bool more
= (pending
& ~eventMask
) != 0;
204 if ((pending
& eventMask
) != 0) {
215 flush_pending_notifications(block_cache
* cache
)
218 MutexLocker
locker(sNotificationsLock
);
220 cache_notification
* notification
= cache
->pending_notifications
.Head();
221 if (notification
== NULL
)
224 bool deleteAfterEvent
= false;
226 if (!get_next_pending_event(notification
, &event
)) {
227 // remove the notification if this was the last pending event
228 cache
->pending_notifications
.Remove(notification
);
229 deleteAfterEvent
= notification
->delete_after_event
;
233 // Notify listener, we need to copy the notification, as it might
234 // be removed when we unlock the list.
235 cache_notification copy
= *notification
;
238 copy
.hook(copy
.transaction_id
, event
, copy
.data
);
243 if (deleteAfterEvent
)
249 /*! Initializes the \a notification as specified. */
251 set_notification(cache_transaction
* transaction
,
252 cache_notification
¬ification
, int32_t events
,
253 fssh_transaction_notification_hook hook
, void* data
)
255 notification
.transaction_id
= transaction
!= NULL
? transaction
->id
: -1;
256 notification
.events_pending
= 0;
257 notification
.events
= events
;
258 notification
.hook
= hook
;
259 notification
.data
= data
;
260 notification
.delete_after_event
= false;
264 /*! Makes sure the notification is deleted. It either deletes it directly,
265 when possible, or marks it for deletion if the notification is pending.
268 delete_notification(cache_notification
* notification
)
270 MutexLocker
locker(sNotificationsLock
);
272 if (notification
->events_pending
!= 0)
273 notification
->delete_after_event
= true;
279 /*! Adds the notification to the pending notifications list, or, if it's
280 already part of it, updates its events_pending field.
281 Also marks the notification to be deleted if \a deleteNotification
283 Triggers the notifier thread to run.
286 add_notification(block_cache
* cache
, cache_notification
* notification
,
287 int32_t event
, bool deleteNotification
)
289 if (notification
->hook
== NULL
)
292 int32_t pending
= fssh_atomic_or(¬ification
->events_pending
, event
);
294 // not yet part of the notification list
295 MutexLocker
locker(sNotificationsLock
);
296 if (deleteNotification
)
297 notification
->delete_after_event
= true;
298 cache
->pending_notifications
.Add(notification
);
299 } else if (deleteNotification
) {
300 // we might need to delete it ourselves if we're late
301 delete_notification(notification
);
306 /*! Notifies all interested listeners of this transaction about the \a event.
307 If \a event is a closing event (ie. TRANSACTION_ENDED, and
308 TRANSACTION_ABORTED), all listeners except those listening to
309 TRANSACTION_WRITTEN will be removed.
312 notify_transaction_listeners(block_cache
* cache
, cache_transaction
* transaction
,
315 bool isClosing
= is_closing_event(event
);
316 bool isWritten
= is_written_event(event
);
318 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
319 while (iterator
.HasNext()) {
320 cache_listener
* listener
= iterator
.Next();
322 bool remove
= (isClosing
&& !is_written_event(listener
->events
))
323 || (isWritten
&& is_written_event(listener
->events
));
327 if ((listener
->events
& event
) != 0)
328 add_notification(cache
, listener
, event
, remove
);
330 delete_notification(listener
);
333 // This must work asynchronously in the kernel, but since we're not using
334 // most transaction events, we can do it here.
335 flush_pending_notifications(cache
);
339 /*! Removes and deletes all listeners that are still monitoring this
343 remove_transaction_listeners(block_cache
* cache
, cache_transaction
* transaction
)
345 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
346 while (iterator
.HasNext()) {
347 cache_listener
* listener
= iterator
.Next();
350 delete_notification(listener
);
356 add_transaction_listener(block_cache
* cache
, cache_transaction
* transaction
,
357 int32_t events
, fssh_transaction_notification_hook hookFunction
, void* data
)
359 ListenerList::Iterator iterator
= transaction
->listeners
.GetIterator();
360 while (iterator
.HasNext()) {
361 cache_listener
* listener
= iterator
.Next();
363 if (listener
->data
== data
&& listener
->hook
== hookFunction
) {
364 // this listener already exists, just update it
365 listener
->events
|= events
;
370 cache_listener
* listener
= new(std::nothrow
) cache_listener
;
371 if (listener
== NULL
)
372 return FSSH_B_NO_MEMORY
;
374 set_notification(transaction
, *listener
, events
, hookFunction
, data
);
375 transaction
->listeners
.Add(listener
);
380 // #pragma mark - private transaction
383 cache_transaction::cache_transaction()
389 notification_hook
= NULL
;
390 notification_data
= NULL
;
392 has_sub_transaction
= false;
397 transaction_compare(void* _transaction
, const void* _id
)
399 cache_transaction
* transaction
= (cache_transaction
*)_transaction
;
400 const int32_t* id
= (const int32_t*)_id
;
402 return transaction
->id
- *id
;
407 transaction_hash(void* _transaction
, const void* _id
, uint32_t range
)
409 cache_transaction
* transaction
= (cache_transaction
*)_transaction
;
410 const int32_t* id
= (const int32_t*)_id
;
412 if (transaction
!= NULL
)
413 return transaction
->id
% range
;
415 return (uint32_t)*id
% range
;
420 delete_transaction(block_cache
* cache
, cache_transaction
* transaction
)
422 if (cache
->last_transaction
== transaction
)
423 cache
->last_transaction
= NULL
;
425 remove_transaction_listeners(cache
, transaction
);
430 static cache_transaction
*
431 lookup_transaction(block_cache
* cache
, int32_t id
)
433 return (cache_transaction
*)hash_lookup(cache
->transaction_hash
, &id
);
437 // #pragma mark - cached_block
441 cached_block::Compare(void* _cacheEntry
, const void* _block
)
443 cached_block
* cacheEntry
= (cached_block
*)_cacheEntry
;
444 const fssh_off_t
* block
= (const fssh_off_t
*)_block
;
446 fssh_off_t diff
= cacheEntry
->block_number
- *block
;
450 return diff
< 0 ? -1 : 0;
456 cached_block::Hash(void* _cacheEntry
, const void* _block
, uint32_t range
)
458 cached_block
* cacheEntry
= (cached_block
*)_cacheEntry
;
459 const fssh_off_t
* block
= (const fssh_off_t
*)_block
;
461 if (cacheEntry
!= NULL
)
462 return cacheEntry
->block_number
% range
;
464 return (uint64_t)*block
% range
;
468 // #pragma mark - block_cache
471 block_cache::block_cache(int _fd
, fssh_off_t numBlocks
, fssh_size_t blockSize
,
476 max_blocks(numBlocks
),
477 block_size(blockSize
),
478 allocated_block_count(0),
479 next_transaction_id(1),
480 last_transaction(NULL
),
481 transaction_hash(NULL
),
487 block_cache::~block_cache()
489 hash_uninit(transaction_hash
);
492 fssh_mutex_destroy(&lock
);
499 fssh_mutex_init(&lock
, "block cache");
500 if (lock
.sem
< FSSH_B_OK
)
503 hash
= hash_init(128, offsetof(cached_block
, next
), &cached_block::Compare
,
504 &cached_block::Hash
);
506 return FSSH_B_NO_MEMORY
;
508 transaction_hash
= hash_init(16, offsetof(cache_transaction
, next
),
509 &transaction_compare
, &FSShell::transaction_hash
);
510 if (transaction_hash
== NULL
)
511 return FSSH_B_NO_MEMORY
;
518 block_cache::Free(void* buffer
)
528 block_cache::Allocate()
530 return malloc(block_size
);
535 block_cache::FreeBlock(cached_block
* block
)
537 Free(block
->current_data
);
539 if (block
->original_data
!= NULL
|| block
->parent_data
!= NULL
) {
540 fssh_panic("block_cache::FreeBlock(): %" FSSH_B_PRIdOFF
541 ", original %p, parent %p\n", block
->block_number
,
542 block
->original_data
, block
->parent_data
);
546 Free(block
->compare
);
553 /*! Allocates a new block for \a blockNumber, ready for use */
555 block_cache::NewBlock(fssh_off_t blockNumber
)
557 cached_block
* block
= new(nothrow
) cached_block
;
559 FATAL(("could not allocate block!\n"));
563 // if we hit the limit of blocks to cache¸ try to free one or more
564 if (allocated_block_count
>= kMaxBlockCount
) {
565 RemoveUnusedBlocks(INT32_MAX
,
566 allocated_block_count
- kMaxBlockCount
+ 1);
569 block
->current_data
= Allocate();
570 if (block
->current_data
== NULL
) {
571 FATAL(("could not allocate block data!\n"));
576 block
->block_number
= blockNumber
;
577 block
->ref_count
= 0;
579 block
->transaction_next
= NULL
;
580 block
->transaction
= block
->previous_transaction
= NULL
;
581 block
->original_data
= NULL
;
582 block
->parent_data
= NULL
;
583 block
->is_dirty
= false;
584 block
->unused
= false;
585 block
->discard
= false;
587 block
->compare
= NULL
;
590 allocated_block_count
++;
597 block_cache::RemoveUnusedBlocks(int32_t maxAccessed
, int32_t count
)
599 TRACE(("block_cache: remove up to %ld unused blocks\n", count
));
601 for (block_list::Iterator iterator
= unused_blocks
.GetIterator();
602 cached_block
*block
= iterator
.Next();) {
603 if (maxAccessed
< block
->accessed
)
606 TRACE((" remove block %Ld, accessed %ld times\n",
607 block
->block_number
, block
->accessed
));
609 // this can only happen if no transactions are used
610 if (block
->is_dirty
&& !block
->discard
)
611 write_cached_block(this, block
, false);
613 // remove block from lists
624 block_cache::RemoveBlock(cached_block
* block
)
626 hash_remove(hash
, block
);
631 /*! Discards the block from a transaction (this method must not be called
632 for blocks not part of a transaction).
635 block_cache::DiscardBlock(cached_block
* block
)
637 if (block
->parent_data
!= NULL
&& block
->parent_data
!= block
->current_data
)
638 Free(block
->parent_data
);
640 block
->parent_data
= NULL
;
642 if (block
->original_data
!= NULL
) {
643 Free(block
->original_data
);
644 block
->original_data
= NULL
;
651 // #pragma mark - private block functions
654 /*! Removes a reference from the specified \a block. If this was the last
655 reference, the block is moved into the unused list.
656 In low memory situations, it will also free some blocks from that list,
657 but not necessarily the \a block it just released.
660 put_cached_block(block_cache
* cache
, cached_block
* block
)
663 if (!block
->is_dirty
&& block
->compare
!= NULL
664 && memcmp(block
->current_data
, block
->compare
, cache
->block_size
)) {
665 fssh_dprintf("new block:\n");
666 fssh_dump_block((const char*)block
->current_data
, 256, " ");
667 fssh_dprintf("unchanged block:\n");
668 fssh_dump_block((const char*)block
->compare
, 256, " ");
669 write_cached_block(cache
, block
);
670 fssh_panic("block_cache: supposed to be clean block was changed!\n");
672 cache
->Free(block
->compare
);
673 block
->compare
= NULL
;
677 if (--block
->ref_count
== 0
678 && block
->transaction
== NULL
&& block
->previous_transaction
== NULL
) {
679 // This block is not used anymore, and not part of any transaction
680 if (block
->discard
) {
681 cache
->RemoveBlock(block
);
683 // put this block in the list of unused blocks
684 block
->unused
= true;
685 cache
->unused_blocks
.Add(block
);
689 if (cache
->allocated_block_count
> kMaxBlockCount
) {
690 cache
->RemoveUnusedBlocks(INT32_MAX
,
691 cache
->allocated_block_count
- kMaxBlockCount
);
697 put_cached_block(block_cache
* cache
, fssh_off_t blockNumber
)
699 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
700 fssh_panic("put_cached_block: invalid block number %" FSSH_B_PRIdOFF
701 " (max %" FSSH_B_PRIdOFF
")", blockNumber
, cache
->max_blocks
- 1);
704 cached_block
* block
= (cached_block
*)hash_lookup(cache
->hash
, &blockNumber
);
706 put_cached_block(cache
, block
);
710 /*! Retrieves the block \a blockNumber from the hash table, if it's already
711 there, or reads it from the disk.
713 \param _allocated tells you whether or not a new block has been allocated
714 to satisfy your request.
715 \param readBlock if \c false, the block will not be read in case it was
716 not already in the cache. The block you retrieve may contain random
720 get_cached_block(block_cache
* cache
, fssh_off_t blockNumber
, bool* _allocated
,
721 bool readBlock
= true)
723 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
724 fssh_panic("get_cached_block: invalid block number %" FSSH_B_PRIdOFF
725 " (max %" FSSH_B_PRIdOFF
")", blockNumber
, cache
->max_blocks
- 1);
729 cached_block
* block
= (cached_block
*)hash_lookup(cache
->hash
,
734 // read block into cache
735 block
= cache
->NewBlock(blockNumber
);
739 hash_insert(cache
->hash
, block
);
743 if (*_allocated
&& readBlock
) {
744 int32_t blockSize
= cache
->block_size
;
746 if (fssh_read_pos(cache
->fd
, blockNumber
* blockSize
, block
->current_data
,
747 blockSize
) < blockSize
) {
748 cache
->RemoveBlock(block
);
749 FATAL(("could not read block %" FSSH_B_PRIdOFF
"\n", blockNumber
));
755 //TRACE(("remove block %Ld from unused\n", blockNumber));
756 block
->unused
= false;
757 cache
->unused_blocks
.Remove(block
);
767 /*! Returns the writable block data for the requested blockNumber.
768 If \a cleared is true, the block is not read from disk; an empty block
771 This is the only method to insert a block into a transaction. It makes
772 sure that the previous block contents are preserved in that case.
775 get_writable_cached_block(block_cache
* cache
, fssh_off_t blockNumber
, fssh_off_t base
,
776 fssh_off_t length
, int32_t transactionID
, bool cleared
)
778 TRACE(("get_writable_cached_block(blockNumber = %Ld, transaction = %d)\n",
779 blockNumber
, transactionID
));
781 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
782 fssh_panic("get_writable_cached_block: invalid block number %"
783 FSSH_B_PRIdOFF
" (max %" FSSH_B_PRIdOFF
")", blockNumber
,
784 cache
->max_blocks
- 1);
788 cached_block
* block
= get_cached_block(cache
, blockNumber
, &allocated
,
793 block
->discard
= false;
795 // if there is no transaction support, we just return the current block
796 if (transactionID
== -1) {
798 fssh_memset(block
->current_data
, 0, cache
->block_size
);
800 block
->is_dirty
= true;
801 // mark the block as dirty
803 return block
->current_data
;
806 cache_transaction
* transaction
= block
->transaction
;
808 if (transaction
!= NULL
&& transaction
->id
!= transactionID
) {
809 // TODO: we have to wait here until the other transaction is done.
810 // Maybe we should even panic, since we can't prevent any deadlocks.
811 fssh_panic("get_writable_cached_block(): asked to get busy writable block (transaction %d)\n", (int)transaction
->id
);
812 put_cached_block(cache
, block
);
815 if (transaction
== NULL
&& transactionID
!= -1) {
816 // get new transaction
817 transaction
= lookup_transaction(cache
, transactionID
);
818 if (transaction
== NULL
) {
819 fssh_panic("get_writable_cached_block(): invalid transaction %d!\n",
821 put_cached_block(cache
, block
);
824 if (!transaction
->open
) {
825 fssh_panic("get_writable_cached_block(): transaction already done!\n");
826 put_cached_block(cache
, block
);
830 block
->transaction
= transaction
;
832 // attach the block to the transaction block list
833 block
->transaction_next
= transaction
->first_block
;
834 transaction
->first_block
= block
;
835 transaction
->num_blocks
++;
838 bool wasUnchanged
= block
->original_data
== NULL
839 || block
->previous_transaction
!= NULL
;
841 if (!(allocated
&& cleared
) && block
->original_data
== NULL
) {
842 // we already have data, so we need to preserve it
843 block
->original_data
= cache
->Allocate();
844 if (block
->original_data
== NULL
) {
845 FATAL(("could not allocate original_data\n"));
846 put_cached_block(cache
, block
);
850 fssh_memcpy(block
->original_data
, block
->current_data
, cache
->block_size
);
852 if (block
->parent_data
== block
->current_data
) {
853 // remember any previous contents for the parent transaction
854 block
->parent_data
= cache
->Allocate();
855 if (block
->parent_data
== NULL
) {
856 // TODO: maybe we should just continue the current transaction in this case...
857 FATAL(("could not allocate parent\n"));
858 put_cached_block(cache
, block
);
862 fssh_memcpy(block
->parent_data
, block
->current_data
, cache
->block_size
);
863 transaction
->sub_num_blocks
++;
864 } else if (transaction
!= NULL
&& transaction
->has_sub_transaction
865 && block
->parent_data
== NULL
&& wasUnchanged
)
866 transaction
->sub_num_blocks
++;
869 fssh_memset(block
->current_data
, 0, cache
->block_size
);
871 block
->is_dirty
= true;
873 return block
->current_data
;
877 /*! Writes the specified \a block back to disk. It will always only write back
878 the oldest change of the block if it is part of more than one transaction.
879 It will automatically send out TRANSACTION_WRITTEN notices, as well as
880 delete transactions when they are no longer used, and \a deleteTransaction
884 write_cached_block(block_cache
* cache
, cached_block
* block
,
885 bool deleteTransaction
)
887 cache_transaction
* previous
= block
->previous_transaction
;
888 int32_t blockSize
= cache
->block_size
;
890 void* data
= previous
&& block
->original_data
891 ? block
->original_data
: block
->current_data
;
892 // we first need to write back changes from previous transactions
894 TRACE(("write_cached_block(block %Ld)\n", block
->block_number
));
896 fssh_ssize_t written
= fssh_write_pos(cache
->fd
, block
->block_number
* blockSize
,
899 if (written
< blockSize
) {
900 FATAL(("could not write back block %" FSSH_B_PRIdOFF
" (%s)\n",
901 block
->block_number
, fssh_strerror(fssh_get_errno())));
902 return FSSH_B_IO_ERROR
;
905 if (data
== block
->current_data
)
906 block
->is_dirty
= false;
908 if (previous
!= NULL
) {
909 previous
->blocks
.Remove(block
);
910 block
->previous_transaction
= NULL
;
912 if (block
->original_data
!= NULL
&& block
->transaction
== NULL
) {
913 // This block is not part of a transaction, so it does not need
914 // its original pointer anymore.
915 cache
->Free(block
->original_data
);
916 block
->original_data
= NULL
;
919 // Has the previous transation been finished with that write?
920 if (--previous
->num_blocks
== 0) {
921 TRACE(("cache transaction %ld finished!\n", previous
->id
));
923 notify_transaction_listeners(cache
, previous
, FSSH_TRANSACTION_WRITTEN
);
925 if (deleteTransaction
) {
926 hash_remove(cache
->transaction_hash
, previous
);
927 delete_transaction(cache
, previous
);
931 if (block
->transaction
== NULL
&& block
->ref_count
== 0 && !block
->unused
) {
932 // the block is no longer used
933 block
->unused
= true;
934 cache
->unused_blocks
.Add(block
);
941 /*! Waits until all pending notifications are carried out.
942 Safe to be called from the block writer/notifier thread.
943 You must not hold the \a cache lock when calling this function.
946 wait_for_notifications(block_cache
* cache
)
948 // TODO: nothing to wait for here.
955 fssh_mutex_init(&sNotificationsLock
, "block cache notifications");
960 } // namespace FSShell
963 // #pragma mark - public transaction API
966 using namespace FSShell
;
970 fssh_cache_start_transaction(void* _cache
)
972 block_cache
* cache
= (block_cache
*)_cache
;
973 MutexLocker
locker(&cache
->lock
);
975 if (cache
->last_transaction
&& cache
->last_transaction
->open
) {
976 fssh_panic("last transaction (%d) still open!\n",
977 (int)cache
->last_transaction
->id
);
980 cache_transaction
* transaction
= new(nothrow
) cache_transaction
;
981 if (transaction
== NULL
)
982 return FSSH_B_NO_MEMORY
;
984 transaction
->id
= fssh_atomic_add(&cache
->next_transaction_id
, 1);
985 cache
->last_transaction
= transaction
;
987 TRACE(("cache_start_transaction(): id %d started\n", transaction
->id
));
989 hash_insert(cache
->transaction_hash
, transaction
);
991 return transaction
->id
;
996 fssh_cache_sync_transaction(void* _cache
, int32_t id
)
998 block_cache
* cache
= (block_cache
*)_cache
;
999 MutexLocker
locker(&cache
->lock
);
1000 fssh_status_t status
= FSSH_B_ENTRY_NOT_FOUND
;
1002 TRACE(("cache_sync_transaction(id %d)\n", id
));
1004 hash_iterator iterator
;
1005 hash_open(cache
->transaction_hash
, &iterator
);
1007 cache_transaction
* transaction
;
1008 while ((transaction
= (cache_transaction
*)hash_next(
1009 cache
->transaction_hash
, &iterator
)) != NULL
) {
1010 // close all earlier transactions which haven't been closed yet
1012 if (transaction
->id
<= id
&& !transaction
->open
) {
1013 // write back all of their remaining dirty blocks
1014 while (transaction
->num_blocks
> 0) {
1015 status
= write_cached_block(cache
, transaction
->blocks
.Head(),
1017 if (status
!= FSSH_B_OK
)
1021 hash_remove_current(cache
->transaction_hash
, &iterator
);
1022 delete_transaction(cache
, transaction
);
1026 hash_close(cache
->transaction_hash
, &iterator
, false);
1029 wait_for_notifications(cache
);
1030 // make sure that all pending FSSH_TRANSACTION_WRITTEN notifications
1031 // are handled after we return
1037 fssh_cache_end_transaction(void* _cache
, int32_t id
,
1038 fssh_transaction_notification_hook hook
, void* data
)
1040 block_cache
* cache
= (block_cache
*)_cache
;
1041 MutexLocker
locker(&cache
->lock
);
1043 TRACE(("cache_end_transaction(id = %d)\n", id
));
1045 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1046 if (transaction
== NULL
) {
1047 fssh_panic("cache_end_transaction(): invalid transaction ID\n");
1048 return FSSH_B_BAD_VALUE
;
1051 notify_transaction_listeners(cache
, transaction
, FSSH_TRANSACTION_ENDED
);
1053 if (add_transaction_listener(cache
, transaction
, FSSH_TRANSACTION_WRITTEN
,
1054 hook
, data
) != FSSH_B_OK
) {
1055 return FSSH_B_NO_MEMORY
;
1058 // iterate through all blocks and free the unchanged original contents
1060 cached_block
* block
= transaction
->first_block
;
1062 for (; block
!= NULL
; block
= next
) {
1063 next
= block
->transaction_next
;
1065 if (block
->previous_transaction
!= NULL
) {
1066 // need to write back pending changes
1067 write_cached_block(cache
, block
);
1069 if (block
->discard
) {
1070 // This block has been discarded in the transaction
1071 cache
->DiscardBlock(block
);
1072 transaction
->num_blocks
--;
1076 if (block
->original_data
!= NULL
) {
1077 cache
->Free(block
->original_data
);
1078 block
->original_data
= NULL
;
1080 if (transaction
->has_sub_transaction
) {
1081 if (block
->parent_data
!= block
->current_data
)
1082 cache
->Free(block
->parent_data
);
1083 block
->parent_data
= NULL
;
1086 // move the block to the previous transaction list
1087 transaction
->blocks
.Add(block
);
1089 block
->previous_transaction
= transaction
;
1090 block
->transaction_next
= NULL
;
1091 block
->transaction
= NULL
;
1094 transaction
->open
= false;
1100 fssh_cache_abort_transaction(void* _cache
, int32_t id
)
1102 block_cache
* cache
= (block_cache
*)_cache
;
1103 MutexLocker
locker(&cache
->lock
);
1105 TRACE(("cache_abort_transaction(id = %ld)\n", id
));
1107 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1108 if (transaction
== NULL
) {
1109 fssh_panic("cache_abort_transaction(): invalid transaction ID\n");
1110 return FSSH_B_BAD_VALUE
;
1113 notify_transaction_listeners(cache
, transaction
, FSSH_TRANSACTION_ABORTED
);
1115 // iterate through all blocks and restore their original contents
1117 cached_block
* block
= transaction
->first_block
;
1119 for (; block
!= NULL
; block
= next
) {
1120 next
= block
->transaction_next
;
1122 if (block
->original_data
!= NULL
) {
1123 TRACE(("cache_abort_transaction(id = %ld): restored contents of block %Ld\n",
1124 transaction
->id
, block
->block_number
));
1125 fssh_memcpy(block
->current_data
, block
->original_data
, cache
->block_size
);
1126 cache
->Free(block
->original_data
);
1127 block
->original_data
= NULL
;
1129 if (transaction
->has_sub_transaction
) {
1130 if (block
->parent_data
!= block
->current_data
)
1131 cache
->Free(block
->parent_data
);
1132 block
->parent_data
= NULL
;
1135 block
->transaction_next
= NULL
;
1136 block
->transaction
= NULL
;
1137 block
->discard
= false;
1140 hash_remove(cache
->transaction_hash
, transaction
);
1141 delete_transaction(cache
, transaction
);
1146 /*! Acknowledges the current parent transaction, and starts a new transaction
1147 from its sub transaction.
1148 The new transaction also gets a new transaction ID.
1151 fssh_cache_detach_sub_transaction(void* _cache
, int32_t id
,
1152 fssh_transaction_notification_hook hook
, void* data
)
1154 block_cache
* cache
= (block_cache
*)_cache
;
1155 MutexLocker
locker(&cache
->lock
);
1157 TRACE(("cache_detach_sub_transaction(id = %d)\n", id
));
1159 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1160 if (transaction
== NULL
) {
1161 fssh_panic("cache_detach_sub_transaction(): invalid transaction ID\n");
1162 return FSSH_B_BAD_VALUE
;
1164 if (!transaction
->has_sub_transaction
)
1165 return FSSH_B_BAD_VALUE
;
1167 // create a new transaction for the sub transaction
1168 cache_transaction
* newTransaction
= new(nothrow
) cache_transaction
;
1169 if (newTransaction
== NULL
)
1170 return FSSH_B_NO_MEMORY
;
1172 newTransaction
->id
= fssh_atomic_add(&cache
->next_transaction_id
, 1);
1174 notify_transaction_listeners(cache
, transaction
, FSSH_TRANSACTION_ENDED
);
1176 if (add_transaction_listener(cache
, transaction
, FSSH_TRANSACTION_WRITTEN
,
1177 hook
, data
) != FSSH_B_OK
) {
1178 delete newTransaction
;
1179 return FSSH_B_NO_MEMORY
;
1182 // iterate through all blocks and free the unchanged original contents
1184 cached_block
* block
= transaction
->first_block
;
1185 cached_block
* last
= NULL
;
1187 for (; block
!= NULL
; block
= next
) {
1188 next
= block
->transaction_next
;
1190 if (block
->previous_transaction
!= NULL
) {
1191 // need to write back pending changes
1192 write_cached_block(cache
, block
);
1194 if (block
->discard
) {
1195 cache
->DiscardBlock(block
);
1196 transaction
->main_num_blocks
--;
1200 if (block
->original_data
!= NULL
&& block
->parent_data
!= NULL
) {
1201 // free the original data if the parent data of the transaction
1202 // will be made current - but keep them otherwise
1203 cache
->Free(block
->original_data
);
1204 block
->original_data
= NULL
;
1206 if (block
->parent_data
== NULL
1207 || block
->parent_data
!= block
->current_data
) {
1208 // we need to move this block over to the new transaction
1209 block
->original_data
= block
->parent_data
;
1211 newTransaction
->first_block
= block
;
1213 last
->transaction_next
= block
;
1215 block
->transaction
= newTransaction
;
1218 block
->transaction
= NULL
;
1220 if (block
->parent_data
!= NULL
) {
1221 // move the block to the previous transaction list
1222 transaction
->blocks
.Add(block
);
1223 block
->previous_transaction
= transaction
;
1224 block
->parent_data
= NULL
;
1227 block
->transaction_next
= NULL
;
1230 newTransaction
->num_blocks
= transaction
->sub_num_blocks
;
1232 transaction
->open
= false;
1233 transaction
->has_sub_transaction
= false;
1234 transaction
->num_blocks
= transaction
->main_num_blocks
;
1235 transaction
->sub_num_blocks
= 0;
1237 hash_insert(cache
->transaction_hash
, newTransaction
);
1238 cache
->last_transaction
= newTransaction
;
1240 return newTransaction
->id
;
1245 fssh_cache_abort_sub_transaction(void* _cache
, int32_t id
)
1247 block_cache
* cache
= (block_cache
*)_cache
;
1248 MutexLocker
locker(&cache
->lock
);
1250 TRACE(("cache_abort_sub_transaction(id = %ld)\n", id
));
1252 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1253 if (transaction
== NULL
) {
1254 fssh_panic("cache_abort_sub_transaction(): invalid transaction ID\n");
1255 return FSSH_B_BAD_VALUE
;
1257 if (!transaction
->has_sub_transaction
)
1258 return FSSH_B_BAD_VALUE
;
1260 notify_transaction_listeners(cache
, transaction
, FSSH_TRANSACTION_ABORTED
);
1262 // revert all changes back to the version of the parent
1264 cached_block
* block
= transaction
->first_block
;
1266 for (; block
!= NULL
; block
= next
) {
1267 next
= block
->transaction_next
;
1269 if (block
->parent_data
== NULL
) {
1270 if (block
->original_data
!= NULL
) {
1271 // the parent transaction didn't change the block, but the sub
1272 // transaction did - we need to revert from the original data
1273 fssh_memcpy(block
->current_data
, block
->original_data
,
1276 } else if (block
->parent_data
!= block
->current_data
) {
1277 // the block has been changed and must be restored
1278 TRACE(("cache_abort_sub_transaction(id = %ld): restored contents of block %Ld\n",
1279 transaction
->id
, block
->block_number
));
1280 fssh_memcpy(block
->current_data
, block
->parent_data
,
1282 cache
->Free(block
->parent_data
);
1285 block
->parent_data
= NULL
;
1286 block
->discard
= false;
1289 // all subsequent changes will go into the main transaction
1290 transaction
->has_sub_transaction
= false;
1291 transaction
->sub_num_blocks
= 0;
1298 fssh_cache_start_sub_transaction(void* _cache
, int32_t id
)
1300 block_cache
* cache
= (block_cache
*)_cache
;
1301 MutexLocker
locker(&cache
->lock
);
1303 TRACE(("cache_start_sub_transaction(id = %d)\n", id
));
1305 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1306 if (transaction
== NULL
) {
1307 fssh_panic("cache_start_sub_transaction(): invalid transaction ID %d\n", (int)id
);
1308 return FSSH_B_BAD_VALUE
;
1311 notify_transaction_listeners(cache
, transaction
, FSSH_TRANSACTION_ENDED
);
1313 // move all changed blocks up to the parent
1315 cached_block
* block
= transaction
->first_block
;
1316 cached_block
* last
= NULL
;
1318 for (; block
!= NULL
; block
= next
) {
1319 next
= block
->transaction_next
;
1321 if (block
->discard
) {
1322 // This block has been discarded in the parent transaction
1324 last
->transaction_next
= next
;
1326 transaction
->first_block
= next
;
1328 cache
->DiscardBlock(block
);
1329 transaction
->num_blocks
--;
1332 if (transaction
->has_sub_transaction
1333 && block
->parent_data
!= NULL
1334 && block
->parent_data
!= block
->current_data
) {
1335 // there already is an older sub transaction - we acknowledge
1336 // its changes and move its blocks up to the parent
1337 cache
->Free(block
->parent_data
);
1340 // we "allocate" the parent data lazily, that means, we don't copy
1341 // the data (and allocate memory for it) until we need to
1342 block
->parent_data
= block
->current_data
;
1346 // all subsequent changes will go into the sub transaction
1347 transaction
->has_sub_transaction
= true;
1348 transaction
->main_num_blocks
= transaction
->num_blocks
;
1349 transaction
->sub_num_blocks
= 0;
1355 /*! Adds a transaction listener that gets notified when the transaction
1356 is ended or aborted.
1357 The listener gets automatically removed in this case.
1360 fssh_cache_add_transaction_listener(void* _cache
, int32_t id
, int32_t events
,
1361 fssh_transaction_notification_hook hookFunction
, void* data
)
1363 // TODO: this is currently not used in a critical context in BFS
1369 fssh_cache_remove_transaction_listener(void* _cache
, int32_t id
,
1370 fssh_transaction_notification_hook hookFunction
, void* data
)
1372 // TODO: this is currently not used in a critical context in BFS
1378 fssh_cache_next_block_in_transaction(void* _cache
, int32_t id
, bool mainOnly
,
1379 long* _cookie
, fssh_off_t
* _blockNumber
, void** _data
,
1380 void** _unchangedData
)
1382 cached_block
* block
= (cached_block
*)*_cookie
;
1383 block_cache
* cache
= (block_cache
*)_cache
;
1385 MutexLocker
locker(&cache
->lock
);
1387 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1388 if (transaction
== NULL
|| !transaction
->open
)
1389 return FSSH_B_BAD_VALUE
;
1392 block
= transaction
->first_block
;
1394 block
= block
->transaction_next
;
1396 if (transaction
->has_sub_transaction
) {
1398 // find next block that the parent changed
1399 while (block
!= NULL
&& block
->parent_data
== NULL
)
1400 block
= block
->transaction_next
;
1402 // find next non-discarded block
1403 while (block
!= NULL
&& block
->discard
)
1404 block
= block
->transaction_next
;
1409 return FSSH_B_ENTRY_NOT_FOUND
;
1412 *_blockNumber
= block
->block_number
;
1414 *_data
= mainOnly
? block
->parent_data
: block
->current_data
;
1416 *_unchangedData
= block
->original_data
;
1418 *_cookie
= (fssh_addr_t
)block
;
1424 fssh_cache_blocks_in_transaction(void* _cache
, int32_t id
)
1426 block_cache
* cache
= (block_cache
*)_cache
;
1427 MutexLocker
locker(&cache
->lock
);
1429 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1430 if (transaction
== NULL
)
1431 return FSSH_B_BAD_VALUE
;
1433 return transaction
->num_blocks
;
1438 fssh_cache_blocks_in_main_transaction(void* _cache
, int32_t id
)
1440 block_cache
* cache
= (block_cache
*)_cache
;
1441 MutexLocker
locker(&cache
->lock
);
1443 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1444 if (transaction
== NULL
)
1445 return FSSH_B_BAD_VALUE
;
1447 return transaction
->main_num_blocks
;
1452 fssh_cache_blocks_in_sub_transaction(void* _cache
, int32_t id
)
1454 block_cache
* cache
= (block_cache
*)_cache
;
1455 MutexLocker
locker(&cache
->lock
);
1457 cache_transaction
* transaction
= lookup_transaction(cache
, id
);
1458 if (transaction
== NULL
)
1459 return FSSH_B_BAD_VALUE
;
1461 return transaction
->sub_num_blocks
;
1466 fssh_cache_has_block_in_transaction(void* _cache
, int32_t id
,
1467 fssh_off_t blockNumber
)
1469 block_cache
* cache
= (block_cache
*)_cache
;
1470 MutexLocker
locker(&cache
->lock
);
1472 cached_block
* block
= (cached_block
*)hash_lookup(cache
->hash
, &blockNumber
);
1474 return (block
!= NULL
&& block
->transaction
!= NULL
1475 && block
->transaction
->id
== id
);
1479 // #pragma mark - public block cache API
1483 fssh_block_cache_delete(void* _cache
, bool allowWrites
)
1485 block_cache
* cache
= (block_cache
*)_cache
;
1488 fssh_block_cache_sync(cache
);
1490 fssh_mutex_lock(&cache
->lock
);
1494 uint32_t cookie
= 0;
1495 cached_block
* block
;
1496 while ((block
= (cached_block
*)hash_remove_first(cache
->hash
,
1497 &cookie
)) != NULL
) {
1498 cache
->FreeBlock(block
);
1501 // free all transactions (they will all be aborted)
1504 cache_transaction
* transaction
;
1505 while ((transaction
= (cache_transaction
*)hash_remove_first(
1506 cache
->transaction_hash
, &cookie
)) != NULL
) {
1515 fssh_block_cache_create(int fd
, fssh_off_t numBlocks
, fssh_size_t blockSize
, bool readOnly
)
1517 block_cache
* cache
= new(std::nothrow
) block_cache(fd
, numBlocks
, blockSize
,
1522 if (cache
->Init() != FSSH_B_OK
) {
1532 fssh_block_cache_sync(void* _cache
)
1534 block_cache
* cache
= (block_cache
*)_cache
;
1536 // we will sync all dirty blocks to disk that have a completed
1537 // transaction or no transaction only
1539 MutexLocker
locker(&cache
->lock
);
1540 hash_iterator iterator
;
1541 hash_open(cache
->hash
, &iterator
);
1543 cached_block
* block
;
1544 while ((block
= (cached_block
*)hash_next(cache
->hash
, &iterator
)) != NULL
) {
1545 if (block
->previous_transaction
!= NULL
1546 || (block
->transaction
== NULL
&& block
->is_dirty
)) {
1547 fssh_status_t status
= write_cached_block(cache
, block
);
1548 if (status
!= FSSH_B_OK
)
1553 hash_close(cache
->hash
, &iterator
, false);
1559 fssh_block_cache_sync_etc(void* _cache
, fssh_off_t blockNumber
,
1560 fssh_size_t numBlocks
)
1562 block_cache
* cache
= (block_cache
*)_cache
;
1564 // we will sync all dirty blocks to disk that have a completed
1565 // transaction or no transaction only
1567 if (blockNumber
< 0 || blockNumber
>= cache
->max_blocks
) {
1568 fssh_panic("block_cache_sync_etc: invalid block number %" FSSH_B_PRIdOFF
1569 " (max %" FSSH_B_PRIdOFF
")", blockNumber
, cache
->max_blocks
- 1);
1570 return FSSH_B_BAD_VALUE
;
1573 MutexLocker
locker(&cache
->lock
);
1575 for (; numBlocks
> 0; numBlocks
--, blockNumber
++) {
1576 cached_block
* block
= (cached_block
*)hash_lookup(cache
->hash
,
1581 if (block
->previous_transaction
!= NULL
1582 || (block
->transaction
== NULL
&& block
->is_dirty
)) {
1583 fssh_status_t status
= write_cached_block(cache
, block
);
1584 if (status
!= FSSH_B_OK
)
1594 fssh_block_cache_discard(void* _cache
, fssh_off_t blockNumber
,
1595 fssh_size_t numBlocks
)
1597 block_cache
* cache
= (block_cache
*)_cache
;
1598 MutexLocker
locker(&cache
->lock
);
1600 for (; numBlocks
> 0; numBlocks
--, blockNumber
++) {
1601 cached_block
* block
= (cached_block
*)hash_lookup(cache
->hash
,
1606 if (block
->previous_transaction
!= NULL
)
1607 write_cached_block(cache
, block
);
1609 if (block
->unused
) {
1610 cache
->unused_blocks
.Remove(block
);
1611 cache
->RemoveBlock(block
);
1613 if (block
->transaction
!= NULL
&& block
->parent_data
!= NULL
1614 && block
->parent_data
!= block
->current_data
) {
1615 fssh_panic("Discarded block %" FSSH_B_PRIdOFF
" has already "
1616 "been changed in this transaction!", blockNumber
);
1619 // mark it as discarded (in the current transaction only, if any)
1620 block
->discard
= true;
1627 fssh_block_cache_make_writable(void* _cache
, fssh_off_t blockNumber
,
1628 int32_t transaction
)
1630 block_cache
* cache
= (block_cache
*)_cache
;
1631 MutexLocker
locker(&cache
->lock
);
1633 if (cache
->read_only
)
1634 fssh_panic("tried to make block writable on a read-only cache!");
1636 // TODO: this can be done better!
1637 void* block
= get_writable_cached_block(cache
, blockNumber
,
1638 blockNumber
, 1, transaction
, false);
1639 if (block
!= NULL
) {
1640 put_cached_block((block_cache
*)_cache
, blockNumber
);
1644 return FSSH_B_ERROR
;
1649 fssh_block_cache_get_writable_etc(void* _cache
, fssh_off_t blockNumber
, fssh_off_t base
,
1650 fssh_off_t length
, int32_t transaction
)
1652 block_cache
* cache
= (block_cache
*)_cache
;
1653 MutexLocker
locker(&cache
->lock
);
1655 TRACE(("block_cache_get_writable_etc(block = %Ld, transaction = %ld)\n",
1656 blockNumber
, transaction
));
1657 if (cache
->read_only
)
1658 fssh_panic("tried to get writable block on a read-only cache!");
1660 return get_writable_cached_block(cache
, blockNumber
, base
, length
,
1661 transaction
, false);
1666 fssh_block_cache_get_writable(void* _cache
, fssh_off_t blockNumber
,
1667 int32_t transaction
)
1669 return fssh_block_cache_get_writable_etc(_cache
, blockNumber
,
1670 blockNumber
, 1, transaction
);
1675 fssh_block_cache_get_empty(void* _cache
, fssh_off_t blockNumber
,
1676 int32_t transaction
)
1678 block_cache
* cache
= (block_cache
*)_cache
;
1679 MutexLocker
locker(&cache
->lock
);
1681 TRACE(("block_cache_get_empty(block = %Ld, transaction = %ld)\n",
1682 blockNumber
, transaction
));
1683 if (cache
->read_only
)
1684 fssh_panic("tried to get empty writable block on a read-only cache!");
1686 return get_writable_cached_block((block_cache
*)_cache
, blockNumber
,
1687 blockNumber
, 1, transaction
, true);
1692 fssh_block_cache_get_etc(void* _cache
, fssh_off_t blockNumber
, fssh_off_t base
,
1695 block_cache
* cache
= (block_cache
*)_cache
;
1696 MutexLocker
locker(&cache
->lock
);
1699 cached_block
* block
= get_cached_block(cache
, blockNumber
, &allocated
);
1703 #ifdef DEBUG_CHANGED
1704 if (block
->compare
== NULL
)
1705 block
->compare
= cache
->Allocate();
1706 if (block
->compare
!= NULL
)
1707 memcpy(block
->compare
, block
->current_data
, cache
->block_size
);
1709 return block
->current_data
;
1714 fssh_block_cache_get(void* _cache
, fssh_off_t blockNumber
)
1716 return fssh_block_cache_get_etc(_cache
, blockNumber
, blockNumber
, 1);
1720 /*! Changes the internal status of a writable block to \a dirty. This can be
1721 helpful in case you realize you don't need to change that block anymore
1722 for whatever reason.
1724 Note, you must only use this function on blocks that were acquired
1728 fssh_block_cache_set_dirty(void* _cache
, fssh_off_t blockNumber
, bool dirty
,
1729 int32_t transaction
)
1731 block_cache
* cache
= (block_cache
*)_cache
;
1732 MutexLocker
locker(&cache
->lock
);
1734 cached_block
* block
= (cached_block
*)hash_lookup(cache
->hash
,
1737 return FSSH_B_BAD_VALUE
;
1738 if (block
->is_dirty
== dirty
) {
1739 // there is nothing to do for us
1743 // TODO: not yet implemented
1745 fssh_panic("block_cache_set_dirty(): not yet implemented that way!\n");
1752 fssh_block_cache_put(void* _cache
, fssh_off_t blockNumber
)
1754 block_cache
* cache
= (block_cache
*)_cache
;
1755 MutexLocker
locker(&cache
->lock
);
1757 put_cached_block(cache
, blockNumber
);