blob: 10538eafcfd45065474e514a576e85d866a3277f [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.
#ifndef COMPONENTS_SERVICES_STORAGE_INDEXED_DB_SCOPES_LEVELDB_SCOPE_H_
#define COMPONENTS_SERVICES_STORAGE_INDEXED_DB_SCOPES_LEVELDB_SCOPE_H_
#include <stdint.h>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/numerics/checked_math.h"
#include "base/sequence_checker.h"
#include "components/services/storage/indexed_db/leveldb/leveldb_state.h"
#include "components/services/storage/indexed_db/scopes/leveldb_scope_deletion_mode.h"
#include "components/services/storage/indexed_db/scopes/leveldb_scopes_coding.h"
#include "components/services/storage/indexed_db/scopes/scope_lock.h"
#include "components/services/storage/indexed_db/scopes/scopes_metadata.pb.h"
#include "third_party/leveldatabase/src/include/leveldb/slice.h"
#include "third_party/leveldatabase/src/include/leveldb/status.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace content {
// LevelDBScope is a specialized type of transaction used only for writing data,
// and is created using the |LevelDBScopes::CreateScope| method.
// It has optimizations like:
// * Supporting deferred deletion for ranges that will never be accessed again.
// This guarantees the data will be deleted, but it will delete it on a
// different thread sometime in the future.
// * Storing changes in leveldb instead of in memory, so the user doesn't have
// to worry about having a set of changes that gets too big.
// * Not bothering looking up 'undo' optimizations for empty ranges.
// To support this, the following invariants have to be followed:
// * The locks the scope was created with should protect it from colliding with
// modifications by other parties.
// * The |empty_ranges| the scope was created with are truly empty.
// * The |empty_ranges| are also disjoints - none of them intersect.
// * All 'Get' operations in the range/s locked by this scope will first call
// |WriteChangesAndUndoLog()| so all writes are submitted to LevelDB.
// * Any ranges deleted with |kDeferred| or |kDeferredWithCompaction| could
// continue to have the data for a while, so they should never be accessed
// again.
//
// Error handling on |Put|, |Delete*|, and |WriteChangesAndUndoLog|:
// * After a corruption error (Status::IsCorruption() returns true), all
// operations on the scope must cease, commit cannot be called, and the whole
// database state must be wiped.
// * After an IO error (Status::IsIOError() returns true), all operations on the
// scope must cease, commit cannot be called, but the scope can attempt to be
// reverted. If the revert fails for any reason, this is treated as a
// corruption. A better route is to immediately abort all usage of the leveldb
// state and close the database. A fresh database open could resolve the IO
// error.
//
// This class is not thread safe, and should be used on the same sequence as
// |LevelDBScopes::CreateScope|.
class LevelDBScope {
public:
using RollbackCallback =
base::OnceCallback<leveldb::Status(int64_t scope_id,
std::vector<ScopeLock> locks)>;
using TearDownCallback = base::RepeatingCallback<void(leveldb::Status)>;
using CleanupCallback = base::OnceCallback<void(int64_t scope_id)>;
~LevelDBScope();
int64_t scope_id() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return scope_id_;
}
leveldb::Status Put(const leveldb::Slice& key,
const leveldb::Slice& value) WARN_UNUSED_RESULT;
leveldb::Status Delete(const leveldb::Slice& key) WARN_UNUSED_RESULT;
// Deletes the range. |begin| is always inclusive. See
// |LevelDBScopeDeletionMode| for the different types of range deletion.
leveldb::Status DeleteRange(const leveldb::Slice& begin,
const leveldb::Slice& end,
LevelDBScopeDeletionMode mode) WARN_UNUSED_RESULT;
// Submits pending changes & the undo log to LevelDB. Required to be able to
// read any keys that have been submitted to |Put|, |Delete|, or
// |DeleteRange|.
leveldb::Status WriteChangesAndUndoLog() WARN_UNUSED_RESULT;
// In the case of LevelDBScopes being in the mode
// TaskRunnerMode::kUseCurrentSequence, rollbacks happen synchronously. The
// status of this possibly synchronous rollback is returned.
leveldb::Status Rollback();
uint64_t GetMemoryUsage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return buffer_batch_.ApproximateSize();
}
uint64_t GetApproximateBytesWritten() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return approximate_bytes_written_.ValueOrDie();
}
private:
using EmptyRange = std::pair<std::string, std::string>;
friend class LevelDBScopes;
class UndoLogWriter;
struct EmptyRangeLessThan {
// This constructor is needed to satisfy the constraints of having default
// construction of the |empty_ranges_| flat_map below.
EmptyRangeLessThan();
EmptyRangeLessThan(const leveldb::Comparator* comparator);
EmptyRangeLessThan& operator=(const EmptyRangeLessThan& other);
// The ranges are expected to be disjoint.
bool operator()(const EmptyRange& lhs, const EmptyRange& rhs) const;
const leveldb::Comparator* comparator_ = nullptr;
};
enum class Mode { kInMemory, kUndoLogOnDisk };
bool IsUndoLogMode() const { return mode_ == Mode::kUndoLogOnDisk; }
// In |empty_ranges|, |pair.first| is the inclusive range begin, and
// |pair.second| is the exclusive range end.
LevelDBScope(int64_t scope_id,
std::vector<uint8_t> prefix,
size_t write_batch_size,
scoped_refptr<LevelDBState> level_db,
std::vector<ScopeLock> locks,
std::vector<EmptyRange> empty_ranges,
RollbackCallback rollback_callback,
TearDownCallback tear_down_callback);
// Called by LevelDBScopes. Saves all data, release all locks, and returns the
// status & the mode of this scope. The caller (LevelDBScopes) is expected to
// queue up a cleanup task if the mode is kUndoLogOnDisk. This instance should
// not be used after this call.
std::pair<leveldb::Status, Mode> Commit(bool sync_on_commit)
WARN_UNUSED_RESULT;
// Submits pending changes & the undo log to LevelDB. Required to be able to
// read any keys that have been submitted to Put, Delete, or
// DeleteRange. |sync| makes the write a synchronous write.
leveldb::Status WriteChangesAndUndoLogInternal(bool sync);
void AddUndoPutTask(std::string key, std::string value);
void AddUndoDeleteTask(std::string key);
void AddUndoDeleteRangeTask(std::string begin, std::string end);
// Writes the current |undo_task_buffer_| to the |write_batch_|, and
// decrements the |undo_sequence_number_|.
void AddBufferedUndoTask();
void AddCleanupDeleteRangeTask(std::string begin, std::string end);
void AddCleanupDeleteAndCompactRangeTask(std::string begin, std::string end);
// Writes the current |cleanup_task_buffer_| to the |write_batch_|, and
// decrements the |cleanup_sequence_number_|.
void AddBufferedCleanupTask();
void SetModeToUndoLog();
// Returns true if the the given key is either part of the scope metadata
// (starts with |prefix_|) or is in a known empty range. If true, then this
// key should NOT have an undo entry generated.
// On the first match of an empty range, the DELETE_RANGE undo log entry is
// written to the buffer_batch_. Since that range is known to be empty, all
// keys within it can be reversed by a delete range operation, and only one
// delete range per empty range is needed.
bool CanSkipWritingUndoEntry(const leveldb::Slice& key);
void AddCommitPoint();
leveldb::Status WriteBufferBatch(bool sync) WARN_UNUSED_RESULT;
#if DCHECK_IS_ON()
std::vector<std::pair<std::string, std::string>> deferred_delete_ranges_;
bool IsRangeEmpty(const EmptyRange& range);
bool IsInDeferredDeletionRange(const leveldb::Slice& key);
void ValidateEmptyRanges();
#endif
SEQUENCE_CHECKER(sequence_checker_);
const int64_t scope_id_;
// The undo tasks are written in reverse, as they are executed in ascending
// order (and they need to be executed in reverse).
int64_t undo_sequence_number_ = leveldb_scopes::kFirstSequenceNumberToWrite;
// The cleanup tasks are written in order, and will be executed in the same
// order.
int64_t cleanup_sequence_number_ = 0ll;
Mode mode_ = Mode::kInMemory;
const std::vector<uint8_t> prefix_;
const size_t write_batch_size_;
const scoped_refptr<LevelDBState> level_db_;
std::vector<ScopeLock> locks_;
base::flat_map<EmptyRange, bool, EmptyRangeLessThan> empty_ranges_;
RollbackCallback rollback_callback_;
// Warning: Calling this callback can destroy this scope.
TearDownCallback tear_down_callback_;
leveldb::WriteBatch buffer_batch_;
bool buffer_batch_empty_ = true;
base::CheckedNumeric<uint64_t> approximate_bytes_written_ = 0;
bool has_written_to_disk_ = false;
bool committed_ = false;
LevelDBScopesUndoTask undo_task_buffer_;
LevelDBScopesCleanupTask cleanup_task_buffer_;
ScopesEncoder key_encoder_;
std::string value_buffer_;
DISALLOW_COPY_AND_ASSIGN(LevelDBScope);
};
} // namespace content
#endif // COMPONENTS_SERVICES_STORAGE_INDEXED_DB_SCOPES_LEVELDB_SCOPE_H_