blob: 7d0e1ea19143d440c41df1446e6c308d0dc0de21 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/indexed_db/instance/leveldb/cleanup_scheduler.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "content/browser/indexed_db/instance/leveldb/compaction_task.h"
#include "content/browser/indexed_db/instance/leveldb/tombstone_sweeper.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
namespace content::indexed_db::level_db {
BASE_FEATURE(IdbInSessionDbCleanup,
base::FEATURE_DISABLED_BY_DEFAULT);
namespace {
constexpr base::TimeDelta kTimeBetweenRuns = base::Minutes(30);
} // namespace
LevelDBCleanupScheduler::LevelDBCleanupScheduler(leveldb::DB* database,
Delegate* delegate)
: database_(database),
delegate_(delegate),
// The last_run_ is initialized to a past value so short lived apps
// which accrue a lot of tombstones will get a chance to have their
// tombstones cleaned.
last_run_(base::TimeTicks::Min()) {}
LevelDBCleanupScheduler::~LevelDBCleanupScheduler() {
if (running_state_) {
base::UmaHistogramEnumeration(
"IndexedDB.LevelDBCleanupScheduler.PrematureTerminationPhase",
running_state_->cleanup_phase);
base::UmaHistogramCounts10000(
"IndexedDB.LevelDBCleanupScheduler.CleanerPostponedCount",
running_state_->postpone_count);
}
}
void LevelDBCleanupScheduler::OnTransactionStart() {
++active_transactions_count_;
if (running_state_ &&
running_state_->clean_up_scheduling_timer_.IsRunning()) {
++running_state_->postpone_count;
running_state_->clean_up_scheduling_timer_.Stop();
}
}
void LevelDBCleanupScheduler::OnTransactionComplete() {
--active_transactions_count_;
if (active_transactions_count_ == 0 && running_state_ &&
!running_state_->clean_up_scheduling_timer_.IsRunning()) {
// Schedule a run if there are no active transactions and
// `kTimeBetweenRuns` has been exceeded. The check for `time_since_last_run`
// is only required when it's the first time the run is being scheduled
// and has not been paused by `OnTransactionStart`. However, it will
// always be true if the run was paused, as the condition was met when
// it was scheduled the first time.
base::TimeDelta time_since_last_run = base::TimeTicks::Now() - last_run_;
if (time_since_last_run > kTimeBetweenRuns) {
ScheduleNextCleanupTask();
}
}
}
void LevelDBCleanupScheduler::Initialize() {
if (!base::FeatureList::IsEnabled(kIdbInSessionDbCleanup)) {
return;
}
if (running_state_) {
return;
}
running_state_.emplace();
}
void LevelDBCleanupScheduler::ScheduleNextCleanupTask() {
CHECK(running_state_);
running_state_->clean_up_scheduling_timer_.Start(
FROM_HERE, kDeferTime,
base::BindRepeating(&LevelDBCleanupScheduler::RunCleanupTask,
base::Unretained(this)));
}
void LevelDBCleanupScheduler::RunCleanupTask() {
CHECK(running_state_);
bool tombstone_sweeper_run_complete = false;
base::TimeTicks time_before_round = base::TimeTicks::Now();
switch (running_state_->cleanup_phase) {
case Phase::kRunScheduled:
running_state_->cleanup_phase = Phase::kTombstoneSweeper;
ABSL_FALLTHROUGH_INTENDED;
case Phase::kTombstoneSweeper:
tombstone_sweeper_run_complete = RunTombstoneSweeper();
running_state_->tombstone_sweeper_duration +=
base::TimeTicks::Now() - time_before_round;
// If not `tombstone_sweeper_run_complete`, we stay in this
// `kTombstoneSweeper` phase and schedule another round of tombstone
// sweeper. If done, we reset the timer and move to db compaction.
if (tombstone_sweeper_run_complete) {
base::UmaHistogramTimes(
"IndexedDB.LevelDBCleanupScheduler.TombstoneSweeperDuration",
running_state_->tombstone_sweeper_duration);
running_state_->cleanup_phase = Phase::kDatabaseCompaction;
}
ScheduleNextCleanupTask();
break;
case Phase::kDatabaseCompaction:
IndexedDBCompactionTask(database_).RunRound();
running_state_->db_compaction_duration =
base::TimeTicks::Now() - time_before_round;
base::UmaHistogramTimes(
"IndexedDB.LevelDBCleanupScheduler.DBCompactionDuration",
running_state_->db_compaction_duration);
running_state_->cleanup_phase = Phase::kLoggingAndCleanup;
ScheduleNextCleanupTask();
break;
case Phase::kLoggingAndCleanup:
LogAndResetState();
break;
}
}
void LevelDBCleanupScheduler::LogAndResetState() {
CHECK(running_state_);
base::UmaHistogramCounts10000(
"IndexedDB.LevelDBCleanupScheduler.CleanerPostponedCount",
running_state_->postpone_count);
// Reset the cleanup state
last_run_ = base::TimeTicks::Now();
running_state_.reset();
// Update the timers for traditional sweeper.
delegate_->UpdateEarliestSweepTime();
delegate_->UpdateEarliestCompactionTime();
}
bool LevelDBCleanupScheduler::RunTombstoneSweeper() {
CHECK(running_state_);
if (!running_state_->tombstone_sweeper) {
delegate_->GetCompleteMetadata(&running_state_->metadata_vector);
running_state_->tombstone_sweeper =
std::make_unique<LevelDbTombstoneSweeper>(database_);
running_state_->tombstone_sweeper->SetMetadata(
&running_state_->metadata_vector);
}
return running_state_->tombstone_sweeper->RunRound();
}
LevelDBCleanupScheduler::RunningState::RunningState() = default;
LevelDBCleanupScheduler::RunningState::~RunningState() = default;
} // namespace content::indexed_db::level_db