From 071c33846d576edc1e59d26b1199eba968e71ca2 Mon Sep 17 00:00:00 2001 From: Andrew Kryczka Date: Tue, 31 Jan 2023 16:57:49 -0800 Subject: [PATCH] Allow canceling manual compaction while waiting for conflicting compaction (#11165) Summary: This PR adds logic to the `RunManualCompaction()` loop to check for cancellation before waiting on any conflicting compactions to finish. In case of cancellation, `RunManualCompaction()` no longer waits on conflicting compactions Pull Request resolved: https://github.com/facebook/rocksdb/pull/11165 Test Plan: repro test case Reviewed By: cbi42 Differential Revision: D42864058 Pulled By: ajkr fbshipit-source-id: ea4dd1a8f294abe212905495a8fbe8f07fca3f5a --- HISTORY.md | 1 + db/db_compaction_test.cc | 53 ++++++++++++++++++++++++++ db/db_impl/db_impl_compaction_flush.cc | 15 +++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 78bf465bf..d16554eb8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,7 @@ * Fixed an issue in `Get` and `MultiGet` when user-defined timestamps is enabled in combination with BlobDB. * Fixed some atypical behaviors for `LockWAL()` such as allowing concurrent/recursive use and not expecting `UnlockWAL()` after non-OK result. See API comments. * Fixed a feature interaction bug where for blobs `GetEntity` would expose the blob reference instead of the blob value. +* Fixed `DisableManualCompaction()` and `CompactRangeOptions::canceled` to cancel compactions even when they are waiting on conflicting compactions to finish ### Feature Removal * Remove RocksDB Lite. diff --git a/db/db_compaction_test.cc b/db/db_compaction_test.cc index 70b47b0cd..55852aacd 100644 --- a/db/db_compaction_test.cc +++ b/db/db_compaction_test.cc @@ -3584,6 +3584,59 @@ TEST_P(DBCompactionTestWithParam, FullCompactionInBottomPriThreadPool) { Env::Default()->SetBackgroundThreads(0, Env::Priority::BOTTOM); } +TEST_F(DBCompactionTest, CancelCompactionWaitingOnConflict) { + // This test verifies cancellation of a compaction waiting to be scheduled due + // to conflict with a running compaction. + // + // A `CompactRange()` in universal compacts all files, waiting for files to + // become available if they are locked for another compaction. This test + // triggers an automatic compaction that blocks a `CompactRange()`, and + // verifies that `DisableManualCompaction()` can successfully cancel the + // `CompactRange()` without waiting for the automatic compaction to finish. + const int kNumSortedRuns = 4; + + Options options = CurrentOptions(); + options.compaction_style = kCompactionStyleUniversal; + options.level0_file_num_compaction_trigger = kNumSortedRuns; + options.memtable_factory.reset( + test::NewSpecialSkipListFactory(KNumKeysByGenerateNewFile - 1)); + Reopen(options); + + test::SleepingBackgroundTask auto_compaction_sleeping_task; + // Block automatic compaction when it runs in the callback + ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( + "CompactionJob::Run():Start", + [&](void* /*arg*/) { auto_compaction_sleeping_task.DoSleep(); }); + ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); + + // Fill overlapping files in L0 to trigger an automatic compaction + Random rnd(301); + for (int i = 0; i < kNumSortedRuns; ++i) { + int key_idx = 0; + GenerateNewFile(&rnd, &key_idx, true /* nowait */); + } + auto_compaction_sleeping_task.WaitUntilSleeping(); + + // Make sure the manual compaction has seen the conflict before being canceled + ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency( + {{"ColumnFamilyData::CompactRange:Return", + "DBCompactionTest::CancelCompactionWaitingOnConflict:" + "PreDisableManualCompaction"}}); + auto manual_compaction_thread = port::Thread([this]() { + ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr) + .IsIncomplete()); + }); + + // Cancel it. Thread should be joinable, i.e., manual compaction was unblocked + // despite finding a conflict with an automatic compaction that is still + // running + TEST_SYNC_POINT( + "DBCompactionTest::CancelCompactionWaitingOnConflict:" + "PreDisableManualCompaction"); + db_->DisableManualCompaction(); + manual_compaction_thread.join(); +} + TEST_F(DBCompactionTest, OptimizedDeletionObsoleting) { // Deletions can be dropped when compacted to non-last level if they fall // outside the lower-level files' key-ranges. diff --git a/db/db_impl/db_impl_compaction_flush.cc b/db/db_impl/db_impl_compaction_flush.cc index ae6ea5bfa..e23be062a 100644 --- a/db/db_impl/db_impl_compaction_flush.cc +++ b/db/db_impl/db_impl_compaction_flush.cc @@ -1972,8 +1972,19 @@ Status DBImpl::RunManualCompaction( manual.begin, manual.end, &manual.manual_end, &manual_conflict, max_file_num_to_ignore, trim_ts)) == nullptr && manual_conflict))) { - // Running either this or some other manual compaction - bg_cv_.Wait(); + if (!scheduled) { + // There is a conflicting compaction + if (manual_compaction_paused_ > 0 || manual.canceled == true) { + // Stop waiting since it was canceled. Pretend the error came from + // compaction so the below cleanup/error handling code can process it. + manual.done = true; + manual.status = + Status::Incomplete(Status::SubCode::kManualCompactionPaused); + } + } + if (!manual.done) { + bg_cv_.Wait(); + } if (manual_compaction_paused_ > 0 && scheduled && !unscheduled) { assert(thread_pool_priority != Env::Priority::TOTAL); // unschedule all manual compactions