blob: c71d8891f5344fa3bdb6b50d761c1386469f39a5 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/scopes/leveldb_scopes_tasks.h"
#include <memory>
#include <string>
#include <utility>
#include "base/compiler_specific.h"
#include "base/memory/ptr_util.h"
#include "content/browser/indexed_db/scopes/leveldb_scopes_coding.h"
#include "content/browser/indexed_db/scopes/scopes_metadata.pb.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
namespace content {
namespace {
bool IsKeyBeforeEndOfRange(const leveldb::Comparator* comparator,
const leveldb::Slice& key,
const leveldb::Slice& end) {
return comparator->Compare(key, end) < 0;
}
} // namespace
LevelDBScopesTask::LevelDBScopesTask(scoped_refptr<LevelDBState> level_db,
size_t max_write_batch_size_bytes)
: level_db_(std::move(level_db)),
max_write_batch_size_bytes_(max_write_batch_size_bytes) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
LevelDBScopesTask::~LevelDBScopesTask() = default;
leveldb::Status LevelDBScopesTask::SubmitWriteBatch(
const leveldb::WriteOptions& write_options) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
leveldb::Status s = level_db_->db()->Write(write_options, &write_batch_);
// Clear the |write_batch_| unconditionally. If the operation failed, then the
// entire task will be abandoned anyways, and retried when the database is
// reopened.
write_batch_.Clear();
return s;
}
leveldb::Status LevelDBScopesTask::MaybeSubmitWriteBatch(
const leveldb::WriteOptions& write_options) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
leveldb::Status s = leveldb::Status::OK();
if (write_batch_.ApproximateSize() > max_write_batch_size_bytes_)
s = SubmitWriteBatch(write_options);
return s;
}
leveldb::Status LevelDBScopesTask::DeleteRange(
leveldb::Slice range_start,
leveldb::Slice range_end,
const leveldb::ReadOptions& read_options,
const leveldb::WriteOptions& write_options) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::unique_ptr<leveldb::Iterator> iterator =
base::WrapUnique(level_db_->db()->NewIterator(read_options));
iterator->Seek(range_start);
leveldb::Status s;
for (; iterator->Valid() && IsKeyBeforeEndOfRange(level_db_->comparator(),
iterator->key(), range_end);
iterator->Next()) {
write_batch_.Delete(iterator->key());
s = MaybeSubmitWriteBatch(write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
}
if (!iterator->status().ok())
return iterator->status();
return SubmitWriteBatch(write_options);
}
CleanupScopeTask::CleanupScopeTask(scoped_refptr<LevelDBState> level_db,
std::vector<uint8_t> metadata_prefix,
int64_t scope_number,
CleanupMode mode,
size_t max_write_batch_size_bytes)
: LevelDBScopesTask(std::move(level_db), max_write_batch_size_bytes),
metadata_prefix_(std::move(metadata_prefix)),
scope_number_(scope_number),
mode_(mode) {}
CleanupScopeTask::~CleanupScopeTask() = default;
leveldb::Status CleanupScopeTask::Run() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (UNLIKELY(level_db_->destruction_requested()))
return leveldb::Status::OK();
leveldb::ReadOptions read_options;
// Since the range being iterated will never be used again, don't fill the
// cache.
read_options.fill_cache = false;
read_options.verify_checksums = true;
leveldb::WriteOptions write_options;
// The cleanup range will never be used again, so sync is not necessary. If
// any changes are dropped during a crash, cleanup will resume on the next
// database open.
write_options.sync = false;
ScopesEncoder scopes_encoder;
leveldb::Status s;
#if DCHECK_IS_ON()
// Check that the metadata's mode matches the mode of this task.
std::string metadata_value;
s = level_db_->db()->Get(
read_options,
scopes_encoder.ScopeMetadataKey(metadata_prefix_, scope_number_),
&metadata_value);
if (s.IsNotFound())
return leveldb::Status::Corruption("Scope metadata not found.");
if (UNLIKELY(!s.ok()))
return s;
LevelDBScopesScopeMetadata metadata;
if (UNLIKELY(!metadata.ParseFromString(metadata_value)))
return leveldb::Status::Corruption("Unable to parse scope metadata.");
if (UNLIKELY(metadata.ignore_cleanup_tasks() !=
(mode_ == CleanupMode::kIgnoreCleanupTasks))) {
return leveldb::Status::Corruption("Invalid cleanup mode on disk.");
}
#endif // DCHECK_IS_ON()
switch (mode_) {
case CleanupMode::kIgnoreCleanupTasks:
// Delete all tasks.
s = DeletePrefixedRange(
scopes_encoder.TasksKeyPrefix(metadata_prefix_, scope_number_),
read_options, write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
break;
case CleanupMode::kExecuteCleanupTasks:
s = DeletePrefixedRange(
scopes_encoder.UndoTaskKeyPrefix(metadata_prefix_, scope_number_),
read_options, write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
s = ExecuteAndDeleteCleanupTasks(read_options, write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
break;
}
write_batch_.Delete(
scopes_encoder.ScopeMetadataKey(metadata_prefix_, scope_number_));
s = SubmitWriteBatch(write_options);
return s;
}
leveldb::Status CleanupScopeTask::ExecuteAndDeleteCleanupTasks(
const leveldb::ReadOptions& read_options,
const leveldb::WriteOptions& write_options) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ScopesEncoder scopes_encoder;
leveldb::Status s;
// Iterate the cleanup tasks and execute them.
leveldb::Slice cleanup_tasks_prefix =
scopes_encoder.CleanupTaskKeyPrefix(metadata_prefix_, scope_number_);
std::unique_ptr<leveldb::Iterator> iterator =
base::WrapUnique(level_db_->db()->NewIterator(read_options));
iterator->Seek(cleanup_tasks_prefix);
LevelDBScopesCleanupTask cleanup_task;
for (; iterator->Valid() && iterator->key().starts_with(cleanup_tasks_prefix);
iterator->Next()) {
leveldb::Slice value = iterator->value();
if (!cleanup_task.ParseFromArray(value.data(), value.size()))
return leveldb::Status::Corruption("Invalid cleanup operation value.");
switch (cleanup_task.operation_case()) {
case LevelDBScopesCleanupTask::kDeleteRange: {
auto range = cleanup_task.delete_range();
s = DeleteRange(range.begin(), range.end(), read_options,
write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
break;
}
case LevelDBScopesCleanupTask::kDeleteRangeAndCompact: {
auto range = cleanup_task.delete_range_and_compact();
leveldb::Slice begin(range.begin());
leveldb::Slice end(range.end());
s = DeleteRange(begin, end, read_options, write_options);
level_db_->db()->CompactRange(&begin, &end);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
break;
}
// The protobuf code generator is to blame for this style mismatch.
case LevelDBScopesCleanupTask::OPERATION_NOT_SET:
return leveldb::Status::Corruption("Invalid cleanup operation type.");
}
write_batch_.Delete(iterator->key());
s = MaybeSubmitWriteBatch(write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
}
return iterator->status();
}
leveldb::Status CleanupScopeTask::DeletePrefixedRange(
leveldb::Slice prefix,
const leveldb::ReadOptions& read_options,
const leveldb::WriteOptions& write_options) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::unique_ptr<leveldb::Iterator> iterator =
base::WrapUnique(level_db_->db()->NewIterator(read_options));
iterator->Seek(prefix);
leveldb::Status s;
for (; iterator->Valid() && iterator->key().starts_with(prefix);
iterator->Next()) {
write_batch_.Delete(iterator->key());
s = MaybeSubmitWriteBatch(write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
}
if (UNLIKELY(!iterator->status().ok()))
return iterator->status();
return MaybeSubmitWriteBatch(write_options);
}
RevertScopeTask::RevertScopeTask(scoped_refptr<LevelDBState> level_db,
std::vector<uint8_t> metadata_prefix,
int64_t scope_number,
size_t max_write_batch_size_bytes)
: LevelDBScopesTask(std::move(level_db), max_write_batch_size_bytes),
metadata_prefix_(std::move(metadata_prefix)),
scope_number_(scope_number) {}
RevertScopeTask::~RevertScopeTask() = default;
leveldb::Status RevertScopeTask::Run() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (UNLIKELY(level_db_->destruction_requested()))
return leveldb::Status::OK();
leveldb::ReadOptions read_options;
// After this job the scope's cleanup log entries will be read again for
// deletion by a CleanupTask, so fill the cache here.
read_options.fill_cache = true;
read_options.verify_checksums = true;
leveldb::WriteOptions write_options;
// The revert range will never be used again, so sync is not necessary. If
// any changes are dropped during a crash, reverting will resume on the next
// database open, and it is OK to re-apply undo changes.
write_options.sync = false;
ScopesEncoder scopes_encoder;
leveldb::Status s;
leveldb::Slice undo_log_prefix =
scopes_encoder.UndoTaskKeyPrefix(metadata_prefix_, scope_number_);
std::unique_ptr<leveldb::Iterator> iterator =
base::WrapUnique(level_db_->db()->NewIterator(read_options));
iterator->Seek(undo_log_prefix);
LevelDBScopesUndoTask undo_operation;
// Iterate through the undo log, applying the changes & deleting the undo
// entries.
for (; iterator->Valid() && iterator->key().starts_with(undo_log_prefix);
iterator->Next()) {
leveldb::Slice value = iterator->value();
if (!undo_operation.ParseFromArray(value.data(), value.size()))
return leveldb::Status::Corruption("Invalid undo operation value.");
switch (undo_operation.operation_case()) {
case LevelDBScopesUndoTask::kPut:
write_batch_.Put(undo_operation.put().key(),
undo_operation.put().value());
break;
case LevelDBScopesUndoTask::kDelete:
write_batch_.Delete(undo_operation.delete_().key());
break;
case LevelDBScopesUndoTask::kDeleteRange: {
auto range = undo_operation.delete_range();
s = DeleteRange(range.begin(), range.end(), read_options,
write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
break;
}
case LevelDBScopesUndoTask::OPERATION_NOT_SET:
return leveldb::Status::Corruption("Invalid undo operation type.");
}
// The undo entry must be deleted in the same write batch that applies the
// undo change to keep a consistent state on disk.
write_batch_.Delete(iterator->key());
s = MaybeSubmitWriteBatch(write_options);
if (UNLIKELY(!s.ok() || level_db_->destruction_requested()))
return s;
}
if (UNLIKELY(!iterator->status().ok()))
return iterator->status();
// Finally, overwrite the metadata to signal the revert is over.
LevelDBScopesScopeMetadata metadata_;
metadata_.set_ignore_cleanup_tasks(true);
write_batch_.Put(
scopes_encoder.ScopeMetadataKey(metadata_prefix_, scope_number_),
metadata_.SerializeAsString());
s = SubmitWriteBatch(write_options);
return s;
}
} // namespace content