Author: axeld Date: 2010-02-17 17:36:40 +0100 (Wed, 17 Feb 2010) New Revision: 35510 Changeset: http://dev.haiku-os.org/changeset/35510/haiku Ticket: http://dev.haiku-os.org/ticket/5412 Ticket: http://dev.haiku-os.org/ticket/5415 Modified: haiku/trunk/src/system/kernel/cache/block_cache.cpp Log: * cache_sync_transaction() now puts all blocks into the BlockWriter, and therefore does not unlock anymore while iterating over the transactions. This gave other threads the opportunity to finish a transaction, causing bug #5412. Also, the BlockWriter will now always close transactions on its own, and you need to pass the transaction hash iterator to Add(). * Also, transactions that contain blocks that are currently written back will be ignored by the block writer, as well as cache_sync_transaction(). This fixes bug #5415. * Improved error handling if BlockWriter fails to write back blocks. Most notably, they are no longer left busy_writing, and the functions calling it do proper error reporting (besides block_cache_discard() that does not return any erro code; I've added a TODO note there for now). * The BlockWriter now starts with a larger array once it has to allocate one. * One can now limit the number of blocks that go into a BlockWriter. This is used by the block writer thread, that shouldn't always write back everything every two seconds. * Also, the fixed array is larger now (leaving enough space such that the block writer/notifier does not need to allocate anything). * And finally, if allocating the array fails, the BlockWriter falls back to the synchronous write back used previously. IOW it will never write back less blocks than you ask for. * Added static BlockWriter::WriteBlock() method replacing write_cached_block(). * Forgot to rename block_cache::busy_count to busy_reading_count. Modified: haiku/trunk/src/system/kernel/cache/block_cache.cpp =================================================================== --- haiku/trunk/src/system/kernel/cache/block_cache.cpp 2010-02-17 16:00:05 UTC (rev 35509) +++ haiku/trunk/src/system/kernel/cache/block_cache.cpp 2010-02-17 16:36:40 UTC (rev 35510) @@ -29,10 +29,8 @@ // TODO: this is a naive but growing implementation to test the API: -// 1) block reading/writing is not at all optimized for speed, it will -// just read and write single blocks. -// 2) the locking could be improved; getting a block should not need to -// wait for blocks to be written +// block reading/writing is not at all optimized for speed, it will +// just read and write single blocks. // TODO: the retrieval/copy of the original data could be delayed until the // new data must be written, ie. in low memory situations. @@ -117,7 +115,7 @@ block_list unused_blocks; ConditionVariable busy_reading_condition; - uint32 busy_count; + uint32 busy_reading_count; bool busy_reading_waiters; ConditionVariable busy_writing_condition; @@ -178,33 +176,49 @@ bool open; bool has_sub_transaction; bigtime_t last_used; + int32 busy_writing_count; }; class BlockWriter { public: BlockWriter(block_cache* cache, - bool deleteTransaction = true); + size_t max = SIZE_MAX); ~BlockWriter(); - bool Add(cached_block* block); + bool Add(cached_block* block, + hash_iterator* iterator = NULL); + bool Add(cache_transaction* transaction, + hash_iterator* iterator); - status_t Write(); + status_t Write(hash_iterator* iterator = NULL, + bool canUnlock = true); + bool DeletedTransaction() const + { return fDeletedTransaction; } + + static status_t WriteBlock(block_cache* cache, + cached_block* block); + private: void* _Data(cached_block* block) const; status_t _WriteBlock(cached_block* block); - void _BlockDone(cached_block* block); + void _BlockDone(cached_block* block, + hash_iterator* iterator); + void _UnmarkWriting(cached_block* block); private: - static const size_t kBufferSize = 16; + static const size_t kBufferSize = 64; block_cache* fCache; cached_block* fBuffer[kBufferSize]; cached_block** fBlocks; size_t fCount; + size_t fTotal; + size_t fCapacity; size_t fMax; - bool fDeleteTransaction; + status_t fStatus; + bool fDeletedTransaction; }; @@ -642,10 +656,6 @@ #endif -static status_t write_cached_block(block_cache* cache, cached_block* block, - bool deleteTransaction = true); - - static DoublyLinkedList<block_cache> sCaches; static mutex sCachesLock = MUTEX_INITIALIZER("block caches"); static mutex sCachesMemoryUseLock @@ -901,6 +911,7 @@ open = true; has_sub_transaction = false; last_used = system_time(); + busy_writing_count = 0; } @@ -1001,13 +1012,16 @@ // #pragma mark - BlockWriter -BlockWriter::BlockWriter(block_cache* cache, bool deleteTransaction) +BlockWriter::BlockWriter(block_cache* cache, size_t max) : fCache(cache), fBlocks(fBuffer), fCount(0), - fMax(kBufferSize), - fDeleteTransaction(deleteTransaction) + fTotal(0), + fCapacity(kBufferSize), + fMax(max), + fStatus(B_OK), + fDeletedTransaction(false) { } @@ -1019,74 +1033,134 @@ } +/*! Adds the specified block to the to be written array. If no more blocks can + be added, false is returned, otherwise true. +*/ bool -BlockWriter::Add(cached_block* block) +BlockWriter::Add(cached_block* block, hash_iterator* iterator) { ASSERT(block->CanBeWritten()); - if (fCount >= fMax) { + if (fTotal == fMax) + return false; + + if (fCount >= fCapacity) { // Enlarge array if necessary cached_block** newBlocks; - size_t max = fMax * 2; + size_t newCapacity = max_c(256, fCapacity * 2); if (fBlocks == fBuffer) - newBlocks = (cached_block**)malloc(max * sizeof(void*)); - else - newBlocks = (cached_block**)realloc(fBlocks, max * sizeof(void*)); + newBlocks = (cached_block**)malloc(newCapacity * sizeof(void*)); + else { + newBlocks = (cached_block**)realloc(fBlocks, + newCapacity * sizeof(void*)); + } - if (newBlocks == NULL) - return false; + if (newBlocks == NULL) { + // Allocating a larger array failed - we need to write back what + // we have synchronously now (this will also clear the array) + Write(iterator, false); + } else { + if (fBlocks == fBuffer) + memcpy(newBlocks, fBuffer, kBufferSize * sizeof(void*)); - if (fBlocks == fBuffer) - memcpy(newBlocks, fBuffer, kBufferSize * sizeof(void*)); - - fBlocks = newBlocks; - fMax = max; + fBlocks = newBlocks; + fCapacity = newCapacity; + } } fBlocks[fCount++] = block; + fTotal++; block->busy_writing = true; fCache->busy_writing_count++; + if (block->previous_transaction != NULL) + block->previous_transaction->busy_writing_count++; return true; } +/*! Adds all blocks of the specified transaction to the to be written array. + If no more blocks can be added, false is returned, otherwise true. +*/ +bool +BlockWriter::Add(cache_transaction* transaction, hash_iterator* iterator) +{ + ASSERT(!transaction->open); + + if (transaction->busy_writing_count != 0) + return true; + + block_list::Iterator blockIterator = transaction->blocks.GetIterator(); + while (cached_block* block = blockIterator.Next()) { + if (!Add(block)) + return false; + + if (DeletedTransaction()) + break; + } + + return true; +} + + /*! Cache must be locked when calling this method, but it will be unlocked while the blocks are written back. */ status_t -BlockWriter::Write() +BlockWriter::Write(hash_iterator* iterator, bool canUnlock) { if (fCount == 0) return B_OK; - mutex_unlock(&fCache->lock); + if (canUnlock) + mutex_unlock(&fCache->lock); // Sort blocks in their on-disk order // TODO: ideally, this should be handled by the I/O scheduler qsort(fBlocks, fCount, sizeof(void*), &compare_blocks); - status_t status = B_OK; + fDeletedTransaction = false; for (uint32 i = 0; i < fCount; i++) { - status_t blockStatus = _WriteBlock(fBlocks[i]); - if (blockStatus != B_OK) { - if (status == B_OK) - status = blockStatus; + status_t status = _WriteBlock(fBlocks[i]); + if (status != B_OK) { + // propagate to global error handling + if (fStatus == B_OK) + fStatus = status; + + _UnmarkWriting(fBlocks[i]); fBlocks[i] = NULL; // This block will not be marked clean } } - mutex_lock(&fCache->lock); + if (canUnlock) + mutex_lock(&fCache->lock); for (uint32 i = 0; i < fCount; i++) - _BlockDone(fBlocks[i]); + _BlockDone(fBlocks[i], iterator); - return status; + fCount = 0; + return fStatus; } +/*! Writes the specified \a block back to disk. It will always only write back + the oldest change of the block if it is part of more than one transaction. + It will automatically send out TRANSACTION_WRITTEN notices, as well as + delete transactions when they are no longer used, and \a deleteTransaction + is \c true. +*/ +/*static*/ status_t +BlockWriter::WriteBlock(block_cache* cache, cached_block* block) +{ + BlockWriter writer(cache); + + writer.Add(block); + return writer.Write(); +} + + void* BlockWriter::_Data(cached_block* block) const { @@ -1125,7 +1199,7 @@ void -BlockWriter::_BlockDone(cached_block* block) +BlockWriter::_BlockDone(cached_block* block, hash_iterator* iterator) { if (block == NULL) { // An error occured when trying to write this block @@ -1138,6 +1212,8 @@ if (_Data(block) == block->current_data) block->is_dirty = false; + _UnmarkWriting(block); + cache_transaction* previous = block->previous_transaction; if (previous != NULL) { previous->blocks.Remove(block); @@ -1158,10 +1234,13 @@ notify_transaction_listeners(fCache, previous, TRANSACTION_WRITTEN); - if (fDeleteTransaction) { + if (iterator != NULL) + hash_remove_current(fCache->transaction_hash, iterator); + else hash_remove(fCache->transaction_hash, previous); - delete_transaction(fCache, previous); - } + + delete_transaction(fCache, previous); + fDeletedTransaction = true; } } if (block->transaction == NULL && block->ref_count == 0) { @@ -1170,7 +1249,16 @@ fCache->unused_blocks.Add(block); } + TB2(BlockData(fCache, block, "after write")); +} + + +void +BlockWriter::_UnmarkWriting(cached_block* block) +{ block->busy_writing = false; + if (block->previous_transaction != NULL) + block->previous_transaction->busy_writing_count--; fCache->busy_writing_count--; if ((fCache->busy_writing_waiters && fCache->busy_writing_count == 0) @@ -1179,8 +1267,6 @@ block->busy_writing_waiters = false; fCache->busy_writing_condition.NotifyAll(); } - - TB2(BlockData(fCache, block, "after write")); } @@ -1198,7 +1284,7 @@ last_transaction(NULL), transaction_hash(NULL), buffer_cache(NULL), - busy_count(0), + busy_reading_count(0), busy_reading_waiters(false), busy_writing_count(0), busy_writing_waiters(0), @@ -1374,7 +1460,8 @@ if (block->is_dirty && !block->discard) { if (block->busy_writing) continue; - write_cached_block(this, block, false); + + BlockWriter::WriteBlock(this, block); } // remove block from lists @@ -1469,7 +1556,7 @@ TB(Flush(this, block, true)); // this can only happen if no transactions are used if (block->is_dirty && !block->busy_writing && !block->discard) - write_cached_block(this, block, false); + BlockWriter::WriteBlock(this, block); // remove block from lists iterator.Remove(); @@ -1498,7 +1585,7 @@ mark_block_busy_reading(block_cache* cache, cached_block* block) { block->busy_reading = true; - cache->busy_count++; + cache->busy_reading_count++; } @@ -1508,9 +1595,9 @@ mark_block_unbusy_reading(block_cache* cache, cached_block* block) { block->busy_reading = false; - cache->busy_count--; + cache->busy_reading_count--; - if ((cache->busy_reading_waiters && cache->busy_count == 0) + if ((cache->busy_reading_waiters && cache->busy_reading_count == 0) || block->busy_reading_waiters) { cache->busy_reading_waiters = false; block->busy_reading_waiters = false; @@ -1544,7 +1631,7 @@ static void wait_for_busy_reading_blocks(block_cache* cache) { - while (cache->busy_count != 0) { + while (cache->busy_reading_count != 0) { // wait for all blocks to be read in ConditionVariableEntry entry; cache->busy_reading_condition.Add(&entry); @@ -1614,7 +1701,7 @@ dump_block((const char*)block->current_data, 256, " "); dprintf("unchanged block:\n"); dump_block((const char*)block->compare, 256, " "); - write_cached_block(cache, block); + BlockWriter::WriteBlock(cache, block); panic("block_cache: supposed to be clean block was changed!\n"); cache->Free(block->compare); @@ -1913,23 +2000,6 @@ } -/*! Writes the specified \a block back to disk. It will always only write back - the oldest change of the block if it is part of more than one transaction. - It will automatically send out TRANSACTION_WRITTEN notices, as well as - delete transactions when they are no longer used, and \a deleteTransaction - is \c true. -*/ -static status_t -write_cached_block(block_cache* cache, cached_block* block, - bool deleteTransaction) -{ - BlockWriter writer(cache, deleteTransaction); - - writer.Add(block); - return writer.Write(); -} - - #if DEBUG_BLOCK_CACHE @@ -2052,10 +2122,14 @@ kprintf("BLOCK CACHE: %p\n", cache); - kprintf(" fd: %d\n", cache->fd); - kprintf(" max_blocks: %Ld\n", cache->max_blocks); - kprintf(" block_size: %lu\n", cache->block_size); + kprintf(" fd: %d\n", cache->fd); + kprintf(" max_blocks: %Ld\n", cache->max_blocks); + kprintf(" block_size: %lu\n", cache->block_size); kprintf(" next_transaction_id: %ld\n", cache->next_transaction_id); + kprintf(" busy_reading: %lu, %s waiters\n", cache->busy_reading_count, + cache->busy_reading_waiters ? "has" : "no"); + kprintf(" busy_writing: %lu, %s waiters\n", cache->busy_writing_count, + cache->busy_writing_waiters ? "has" : "no"); if (!cache->pending_notifications.IsEmpty()) { kprintf(" pending notifications:\n"); @@ -2116,7 +2190,7 @@ kprintf(" %ld blocks total, %ld dirty, %ld discarded, %ld referenced, %ld " "busy, %ld in unused.\n", count, dirty, discarded, referenced, - cache->busy_count, cache->unused_blocks.Count()); + cache->busy_reading_count, cache->unused_blocks.Count()); hash_close(cache->hash, &iterator, false); return 0; @@ -2380,7 +2454,7 @@ block_cache* cache = NULL; while ((cache = get_next_locked_block_cache(cache)) != NULL) { - BlockWriter writer(cache); + BlockWriter writer(cache, 64); size_t cacheUsedMemory; object_cache_get_usage(cache->buffer_cache, &cacheUsedMemory); @@ -2403,11 +2477,10 @@ } else { hash_iterator iterator; hash_open(cache->transaction_hash, &iterator); - bool stop = false; cache_transaction* transaction; while ((transaction = (cache_transaction*)hash_next( - cache->transaction_hash, &iterator)) != NULL && !stop) { + cache->transaction_hash, &iterator)) != NULL) { if (transaction->open) { if (system_time() > transaction->last_used + kTransactionIdleTime) { @@ -2418,16 +2491,8 @@ continue; } - block_list::Iterator iterator - = transaction->blocks.GetIterator(); - - while (iterator.HasNext()) { - cached_block* block = iterator.Next(); - if (block->CanBeWritten() && !writer.Add(block)) { - stop = true; - break; - } - } + if (!writer.Add(transaction, &iterator)) + break; } hash_close(cache->transaction_hash, &iterator, false); @@ -2599,45 +2664,42 @@ cache_sync_transaction(void* _cache, int32 id) { block_cache* cache = (block_cache*)_cache; - TransactionLocker locker(cache); - status_t status = B_ENTRY_NOT_FOUND; + bool hadBusy; TRACE(("cache_sync_transaction(id %ld)\n", id)); - hash_iterator iterator; - hash_open(cache->transaction_hash, &iterator); + do { + TransactionLocker locker(cache); + hadBusy = false; - cache_transaction* transaction; - while ((transaction = (cache_transaction*)hash_next( - cache->transaction_hash, &iterator)) != NULL) { - // close all earlier transactions which haven't been closed yet + BlockWriter writer(cache); + hash_iterator iterator; + hash_open(cache->transaction_hash, &iterator); - if (transaction->id <= id && !transaction->open) { - // write back all of their remaining dirty blocks - T(Action("sync", cache, transaction)); - while (transaction->num_blocks > 0) { - BlockWriter writer(cache, false); - block_list::Iterator iterator - = transaction->blocks.GetIterator(); + cache_transaction* transaction; + while ((transaction = (cache_transaction*)hash_next( + cache->transaction_hash, &iterator)) != NULL) { + // close all earlier transactions which haven't been closed yet - while (cached_block* block = iterator.Next()) { - if (!writer.Add(block)) - break; - } + if (transaction->busy_writing_count != 0) { + hadBusy = true; + continue; + } + if (transaction->id <= id && !transaction->open) { + // write back all of their remaining dirty blocks + T(Action("sync", cache, transaction)); - status = writer.Write(); - if (status != B_OK) - return status; + writer.Add(transaction, &iterator); } - - hash_remove_current(cache->transaction_hash, &iterator); - delete_transaction(cache, transaction); } - } - hash_close(cache->transaction_hash, &iterator, false); - locker.Unlock(); + hash_close(cache->transaction_hash, &iterator, false); + status_t status = writer.Write(); + if (status != B_OK) + return status; + } while (hadBusy); + wait_for_notifications(cache); // make sure that all pending TRANSACTION_WRITTEN notifications // are handled after we return @@ -2660,6 +2722,21 @@ return B_BAD_VALUE; } + // Write back all pending transaction blocks + + BlockWriter writer(cache); + cached_block* block = transaction->first_block; + for (; block != NULL; block = block->transaction_next) { + if (block->previous_transaction != NULL) { + // need to write back pending changes + writer.Add(block); + } + } + + status_t status = writer.Write(); + if (status != B_OK) + return status; + notify_transaction_listeners(cache, transaction, TRANSACTION_ENDED); if (add_transaction_listener(cache, transaction, TRANSACTION_WRITTEN, hook, @@ -2671,17 +2748,6 @@ // iterate through all blocks and free the unchanged original contents - BlockWriter writer(cache); - cached_block* block = transaction->first_block; - for (; block != NULL; block = block->transaction_next) { - if (block->previous_transaction != NULL) { - // need to write back pending changes - writer.Add(block); - } - } - - writer.Write(); - cached_block* next; for (block = transaction->first_block; block != NULL; block = next) { next = block->transaction_next; @@ -2786,6 +2852,21 @@ if (!transaction->has_sub_transaction) return B_BAD_VALUE; + // iterate through all blocks and free the unchanged original contents + + BlockWriter writer(cache); + cached_block* block = transaction->first_block; + for (; block != NULL; block = block->transaction_next) { + if (block->previous_transaction != NULL) { + // need to write back pending changes + writer.Add(block); + } + } + + status_t status = writer.Write(); + if (status != B_OK) + return status; + // create a new transaction for the sub transaction cache_transaction* newTransaction = new(std::nothrow) cache_transaction; if (newTransaction == NULL) @@ -2802,19 +2883,6 @@ return B_NO_MEMORY; } - // iterate through all blocks and free the unchanged original contents - - BlockWriter writer(cache); - cached_block* block = transaction->first_block; - for (; block != NULL; block = block->transaction_next) { - if (block->previous_transaction != NULL) { - // need to write back pending changes - writer.Add(block); - } - } - - writer.Write(); - cached_block* last = NULL; cached_block* next; for (block = transaction->first_block; block != NULL; block = next) { @@ -3276,6 +3344,7 @@ } writer.Write(); + // TODO: this can fail, too! for (; numBlocks > 0; numBlocks--, blockNumber++) { cached_block* block = (cached_block*)hash_lookup(cache->hash,