blob: 96ef9dac9373b42ebe6008aaa9cc066063a17896 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sqlite_migration_vfs/page_migration_tracker.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/thread_annotations.h"
namespace sqlite_migration_vfs {
PageMigrationTracker::PageMigrationTracker(MigrationTarget target,
size_t total_pages)
: target_(target), total_pages_(total_pages) {
size_t total_blocks =
(total_pages == 0) ? 0 : (total_pages - 1) / kPagesPerBlock + 1;
blocks_.reserve(total_blocks);
for (size_t i = 0; i < total_blocks; ++i) {
blocks_.push_back(std::make_unique<BlockMigrationTracker>());
}
}
PageMigrationTracker::~PageMigrationTracker() = default;
bool PageMigrationTracker::IsMigrated(size_t page) const {
CHECK_GE(page, 1u);
// Pages allocated beyond the initial snapshot are considered migrated so
// that foreground writes immediately double-write to both database files.
if (page > total_pages_) {
return true;
}
size_t block_index = (page - 1) / kPagesPerBlock;
return blocks_[block_index]->IsMigrated();
}
// NO_THREAD_SAFETY_ANALYSIS: Clang's static thread-safety analyzer cannot
// track locks on dynamically-indexed vector elements (`blocks_[i]`) that are
// acquired here and released later in `MarkMigrationAsCompletedAndUnlockIt()`,
// nor does `EXCLUSIVE_TRYLOCK_FUNCTION` support `std::optional` return types.
std::optional<PageMigrationTracker::Block>
PageMigrationTracker::GetNextBlockToMigrateAndLockIt(
const std::optional<Block>& previous_block) NO_THREAD_SAFETY_ANALYSIS {
size_t start_index = 0;
if (previous_block.has_value()) {
CHECK_GE(previous_block->start_page, 1u);
size_t prev_index = (previous_block->start_page - 1) / kPagesPerBlock;
CHECK_LT(prev_index, blocks_.size());
// Start after `previous_block` so a block that failed migration does not
// get retried immediately and starve the blocks after it; it is picked up
// again when the scan wraps around.
start_index = (prev_index + 1) % blocks_.size();
}
for (size_t offset = 0; offset < blocks_.size(); ++offset) {
if (IsMigrationComplete()) {
return std::nullopt;
}
size_t i = (start_index + offset) % blocks_.size();
if (blocks_[i]->TryLockForMigration()) {
if (!has_started_migration_.exchange(true, std::memory_order_relaxed)) {
start_time_ = base::TimeTicks::Now();
RecordTotalBlocksIntendedToMigrate(target_, blocks_.size());
RecordMigrationStatus(target_, MigrationStatus::kStarted);
}
size_t start_page = i * kPagesPerBlock + 1;
size_t end = (i + 1) * kPagesPerBlock;
size_t end_page = (end > total_pages_) ? total_pages_ : end;
return Block{
.start_page = start_page,
.end_page = end_page,
.lock_time = base::TimeTicks::Now(),
};
}
}
return std::nullopt;
}
void PageMigrationTracker::MarkMigrationAsCompletedAndUnlockIt(
const Block& block,
BlockMigrationResult result) NO_THREAD_SAFETY_ANALYSIS {
CHECK_GE(block.start_page, 1u);
size_t block_index = (block.start_page - 1) / kPagesPerBlock;
CHECK_LT(block_index, blocks_.size());
RecordBlockMigrationResult(target_, result);
CHECK(!block.lock_time.is_null());
RecordBlockMigrationTime(target_, base::TimeTicks::Now() - block.lock_time);
bool success = (result == BlockMigrationResult::kSuccess);
blocks_[block_index]->Unlock(success);
if (success) {
migrated_blocks_count_.fetch_add(1, std::memory_order_relaxed);
if (IsMigrationComplete()) {
if (!has_completed_migration_.exchange(true, std::memory_order_relaxed)) {
RecordMigrationStatus(target_, MigrationStatus::kCompleted);
CHECK(!start_time_.is_null());
RecordTotalMigrationTime(target_, base::TimeTicks::Now() - start_time_);
}
}
}
}
bool PageMigrationTracker::IsMigrationComplete() const {
return migrated_blocks_count_.load(std::memory_order_relaxed) >=
blocks_.size();
}
} // namespace sqlite_migration_vfs