diff --git a/src/client/cluster_client_wb.cpp b/src/client/cluster_client_wb.cpp index 1fa49bde..bb7d0305 100644 --- a/src/client/cluster_client_wb.cpp +++ b/src/client/cluster_client_wb.cpp @@ -131,6 +131,7 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu writeback_bytes -= op->len; } writeback_queue_size++; + writeback_queue.push_back({ op->inode, new_end }); } break; } @@ -165,6 +166,7 @@ void writeback_cache_t::copy_write(cluster_op_t *op, int state, uint64_t new_flu { writeback_queue_size++; } + writeback_queue.push_back({ op->inode, new_end }); } auto new_dirty_it = dirty_buffers.emplace_hint(dirty_it, (object_id){ .inode = op->inode, diff --git a/src/test/test_cluster_client.cpp b/src/test/test_cluster_client.cpp index af0f4739..a6e5e5b2 100644 --- a/src/test/test_cluster_client.cpp +++ b/src/test/test_cluster_client.cpp @@ -564,11 +564,100 @@ void test_writeback_merge() printf("[ok] writeback merge test\n"); } +// Verifies that writeback_queue contains an entry for each sequence of +// CACHE_DIRTY buffers, otherwise flushing never reaches it +static void check_writeback_queued(writeback_cache_t *wb) +{ + int runs = 0; + int not_queued = 0; + auto it = wb->dirty_buffers.begin(); + while (it != wb->dirty_buffers.end()) + { + if (it->second.state != CACHE_DIRTY) + { + it++; + continue; + } + auto run_start = it; + uint64_t expected_next = run_start->first.stripe; + bool expected_is_del = (run_start->second.buf == NULL); + auto run_end = it; + while (run_end != wb->dirty_buffers.end() && + run_end->second.state == CACHE_DIRTY && + run_end->first.inode == run_start->first.inode && + run_end->first.stripe == expected_next && + (run_end->second.buf == NULL) == expected_is_del) + { + expected_next = run_end->first.stripe + run_end->second.len; + run_end++; + } + runs++; + bool queued = false; + for (auto & a: wb->writeback_queue) + { + if (a.inode == run_start->first.inode && + a.stripe >= run_start->first.stripe && + a.stripe < expected_next) + { + queued = true; + break; + } + } + if (!queued) + { + printf("FAIL: CACHE_DIRTY inode %jx range %ju-%ju is missing from the writeback queue\n", + run_start->first.inode, run_start->first.stripe, expected_next); + not_queued++; + } + it = run_end; + } + assert(!not_queued); + assert(wb->writeback_queue_size == runs); +} + +void test_writeback_queue_split() +{ + // Regression test for the future case of client_enable_writeback disabled at runtime + writeback_cache_t *wb = new writeback_cache_t; + // CACHE_DIRTY [1000..5000] -- buffered with writeback on + copy_write_for_test(wb, 1000, 4000, CACHE_DIRTY, 0); + assert(wb->writeback_queue_size == 1); + check_writeback_queued(wb); + // CACHE_DIRTY [10000..14000] -- buffered with writeback on + copy_write_for_test(wb, 10000, 4000, CACHE_DIRTY, 0); + assert(wb->writeback_queue_size == 2); + check_writeback_queued(wb); + // CACHE_DIRTY [20000..24000] -- buffered with writeback on + copy_write_for_test(wb, 20000, 4000, CACHE_DIRTY, 0); + assert(wb->writeback_queue_size == 3); + check_writeback_queued(wb); + // Writeback is now off; a CACHE_REPEATING write at [2000..3000] splits the run: + // [1000..2000] CACHE_DIRTY, [2000..3000] CACHE_REPEATING, [3000..5000] CACHE_DIRTY + // The right half [3000..5000] must have its own anchor in writeback_queue. + copy_write_for_test(wb, 2000, 1000, CACHE_REPEATING, 1); + assert(wb->dirty_buffers.size() == 5); + assert(wb->writeback_queue_size == 4); + check_writeback_queued(wb); + // Second split - only the end part is left + copy_write_for_test(wb, 10000, 1000, CACHE_REPEATING, 1); + assert(wb->dirty_buffers.size() == 6); + assert(wb->writeback_queue_size == 4); + check_writeback_queued(wb); + // Third split - only the beginning part is left + copy_write_for_test(wb, 23000, 1000, CACHE_REPEATING, 1); + assert(wb->dirty_buffers.size() == 7); + assert(wb->writeback_queue_size == 4); + check_writeback_queued(wb); + delete wb; + printf("[ok] test_writeback_queue_split\n"); +} + int main(int narg, char *args[]) { test1(); test2(); test_writeback(); test_writeback_merge(); + test_writeback_queue_split(); return 0; }