1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
|
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_INDEXED_DB_INSTANCE_TRANSACTION_H_
#define CONTENT_BROWSER_INDEXED_DB_INSTANCE_TRANSACTION_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <set>
#include <tuple>
#include "base/containers/queue.h"
#include "base/containers/stack.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_id.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
#include "components/services/storage/privileged/mojom/indexed_db_internals_types.mojom-forward.h"
#include "content/browser/indexed_db/indexed_db_database_error.h"
#include "content/browser/indexed_db/indexed_db_external_object_storage.h"
#include "content/browser/indexed_db/instance/backing_store.h"
#include "content/browser/indexed_db/instance/bucket_context_handle.h"
#include "content/browser/indexed_db/status.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
namespace content::indexed_db {
class Connection;
class Cursor;
class Database;
// Corresponds to the IndexedDB API notion of transaction and has a 1:1
// relationship with IDBTransaction in Blink.
class CONTENT_EXPORT Transaction : public blink::mojom::IDBTransaction {
public:
using Operation = base::OnceCallback<Status(Transaction*)>;
enum State {
CREATED, // Created, but not yet started by coordinator.
STARTED, // Started by the coordinator.
COMMITTING, // In the process of committing, possibly waiting for blobs
// to be written.
FINISHED, // Either aborted or committed.
};
static void DisableInactivityTimeoutForTesting();
Transaction(
int64_t id,
Connection* connection,
const std::set<int64_t>& object_store_ids,
blink::mojom::IDBTransactionMode mode,
blink::mojom::IDBTransactionDurability durability,
BucketContextHandle bucket_context,
std::unique_ptr<BackingStore::Transaction> backing_store_transaction);
~Transaction() override;
void BindReceiver(
mojo::PendingAssociatedReceiver<blink::mojom::IDBTransaction>
mojo_receiver);
// Signals the transaction for commit.
void SetCommitFlag();
// Returns false if the transaction has been signalled to commit, is in the
// process of committing, or finished committing or was aborted. Essentially
// when this returns false no tasks should be scheduled that try to modify
// the transaction.
// TODO(crbug.com/40791538): If the transaction was already committed
// (or is in the process of being committed), and this object receives a new
// Mojo message, we should kill the renderer. This branch however also
// includes cases where the browser process aborted the transaction, as
// currently we don't distinguish that state from the transaction having been
// committed. So for now simply ignore the request.
bool IsAcceptingRequests() {
return !is_commit_pending_ && state_ != COMMITTING && state_ != FINISHED;
}
// This transaction is ultimately backed by a LevelDBScope. Aborting a
// transaction rolls back the LevelDBScopes, which (if LevelDBScopes is in
// single-sequence mode) can fail. This returns the result of that rollback,
// if applicable.
Status Abort(const DatabaseError& error);
// Called by the scopes lock manager when this transaction is unblocked.
void Start();
// If the client is in BFCache and blocking live clients, this will kill it
// and release the locks.
void DontAllowInactiveClientToBlockOthers(
storage::mojom::DisallowInactiveClientReason reason);
// Returns true if the given transaction wants to hold any locks that
// other transactions *from other clients* are waiting for. If
// `consider_priority` is true, then this routine ignores other clients
// that are equal or lower priority than this one, provided that this isn't
// the highest priority (0).
bool IsTransactionBlockingOtherClients(bool consider_priority = false) const;
// Returns the locks required for this transaction to start. NB: this is only
// relevant to readonly and readwrite transactions. Lock requests for version
// change transactions are created by the `ConnectionCoordinator`.
std::vector<PartitionedLockManager::PartitionedLockRequest>
BuildLockRequests() const;
void OnSchedulingPriorityUpdated(int new_priority);
blink::mojom::IDBTransactionMode mode() const { return mode_; }
const std::set<int64_t>& scope() const { return object_store_ids_; }
void ScheduleTask(Operation task) {
ScheduleTask(blink::mojom::IDBTaskType::Normal, std::move(task));
}
void ScheduleTask(blink::mojom::IDBTaskType, Operation task);
void RegisterOpenCursor(Cursor* cursor);
void UnregisterOpenCursor(Cursor* cursor);
void AddPreemptiveEvent() { pending_preemptive_events_++; }
void DidCompletePreemptiveEvent() {
pending_preemptive_events_--;
DCHECK_GE(pending_preemptive_events_, 0);
}
enum class RunTasksResult { kError, kNotFinished, kCommitted, kAborted };
std::tuple<RunTasksResult, Status> RunTasks();
// Returns metadata relevant to idb-internals.
storage::mojom::IdbTransactionMetadataPtr GetIdbInternalsMetadata() const;
// Called when the data used to populate the struct in
// `GetIdbInternalsMetadata` is changed in a significant way.
void NotifyOfIdbInternalsRelevantChange();
BackingStore::Transaction* BackingStoreTransaction() {
return backing_store_transaction_.get();
}
int64_t id() const { return id_; }
Connection* connection() const { return connection_.get(); }
bool is_commit_pending() const { return is_commit_pending_; }
int64_t num_errors_sent() const { return num_errors_sent_; }
int64_t num_errors_handled() const { return num_errors_handled_; }
void IncrementNumErrorsSent() { ++num_errors_sent_; }
State state() const { return state_; }
bool aborted() const { return aborted_; }
bool IsTimeoutTimerRunning() const { return timeout_timer_.IsRunning(); }
struct Diagnostics {
base::Time creation_time;
base::Time start_time;
int tasks_scheduled;
int tasks_completed;
};
const Diagnostics& diagnostics() const { return diagnostics_; }
base::WeakPtr<Transaction> AsWeakPtr() { return ptr_factory_.GetWeakPtr(); }
BucketContext* bucket_context() { return bucket_context_.bucket_context(); }
const base::flat_set<PartitionedLockId> lock_ids() const { return lock_ids_; }
PartitionedLockHolder* mutable_locks_receiver() { return &locks_receiver_; }
size_t in_flight_memory() const { return in_flight_memory_.ValueOrDie(); }
private:
friend class IndexedDBClassFactory;
friend class Connection;
friend class base::RefCounted<Transaction>;
friend class DatabaseOperationTest;
FRIEND_TEST_ALL_PREFIXES(TransactionTestMode, AbortPreemptive);
FRIEND_TEST_ALL_PREFIXES(TransactionTestMode, AbortTasks);
FRIEND_TEST_ALL_PREFIXES(TransactionTest, NoTimeoutReadOnly);
FRIEND_TEST_ALL_PREFIXES(TransactionTest, SchedulePreemptiveTask);
FRIEND_TEST_ALL_PREFIXES(TransactionTestMode, ScheduleNormalTask);
FRIEND_TEST_ALL_PREFIXES(TransactionTestMode, TaskFails);
FRIEND_TEST_ALL_PREFIXES(TransactionTest, Timeout);
FRIEND_TEST_ALL_PREFIXES(TransactionTest, TimeoutPreemptive);
FRIEND_TEST_ALL_PREFIXES(TransactionTest, TimeoutWithPriorities);
FRIEND_TEST_ALL_PREFIXES(DatabaseOperationTest, CreatePutDelete);
// blink::mojom::IDBTransaction:
void CreateObjectStore(int64_t object_store_id,
const std::u16string& name,
const blink::IndexedDBKeyPath& key_path,
bool auto_increment) override;
void DeleteObjectStore(int64_t object_store_id) override;
void Put(int64_t object_store_id,
blink::mojom::IDBValuePtr value,
blink::IndexedDBKey key,
blink::mojom::IDBPutMode mode,
std::vector<blink::IndexedDBIndexKeys> index_keys,
blink::mojom::IDBTransaction::PutCallback callback) override;
void Commit(int64_t num_errors_handled) override;
void OnQuotaCheckDone(bool allowed);
// Turns an IDBValue into a set of IndexedDBExternalObjects in
// |external_objects|.
uint64_t CreateExternalObjects(
blink::mojom::IDBValuePtr& value,
std::vector<IndexedDBExternalObject>* external_objects);
Status DoPendingCommit();
Status DoPut(int64_t object_store_id,
IndexedDBValue value,
blink::IndexedDBKey key,
blink::mojom::IDBPutMode put_mode,
std::vector<blink::IndexedDBIndexKeys> index_keys,
blink::mojom::IDBTransaction::PutCallback callback,
Transaction* transaction);
// Helper for posting a task to call Transaction::CommitPhaseTwo when
// we know the transaction had no requests and therefore the commit must
// succeed.
static Status CommitPhaseTwoProxy(Transaction* transaction);
bool IsTaskQueueEmpty() const;
bool HasPendingTasks() const;
Status BlobWriteComplete(BlobWriteResult result,
storage::mojom::WriteBlobToFileResult error);
void CloseOpenCursors();
Status CommitPhaseTwo();
void TimeoutFired();
void ResetTimeoutTimer();
void SetState(State state);
// Generates a key for an auto_increment object store, or an invalid key if
// the backing store has a problem.
blink::IndexedDBKey GenerateAutoIncrementKey(int64_t object_store_id);
const int64_t id_;
const std::set<int64_t> object_store_ids_;
const blink::mojom::IDBTransactionMode mode_;
const blink::mojom::IDBTransactionDurability durability_;
bool used_ = false;
State state_ = CREATED;
std::optional<int> scheduling_priority_at_last_state_change_;
base::flat_set<PartitionedLockId> lock_ids_;
// Holds the locks from when they're acquired until they're handed off to the
// backing store transaction.
PartitionedLockHolder locks_receiver_;
bool is_commit_pending_ = false;
// We are owned by the connection object, but during force closes sometimes
// there are issues if there is a pending OpenRequest. So use a WeakPtr.
base::WeakPtr<Connection> connection_;
base::WeakPtr<Database> database_;
BucketContextHandle bucket_context_;
base::CheckedNumeric<size_t> in_flight_memory_ = 0;
class TaskQueue {
public:
TaskQueue();
TaskQueue(const TaskQueue&) = delete;
TaskQueue& operator=(const TaskQueue&) = delete;
~TaskQueue();
bool empty() const { return queue_.empty(); }
void push(Operation task) { queue_.push(std::move(task)); }
Operation pop();
void clear();
private:
base::queue<Operation> queue_;
};
TaskQueue task_queue_;
TaskQueue preemptive_task_queue_;
std::unique_ptr<BackingStore::Transaction> backing_store_transaction_;
bool backing_store_transaction_begun_ = false;
int pending_preemptive_events_ = 0;
bool processing_event_queue_ = false;
bool aborted_ = false;
int64_t num_errors_sent_ = 0;
int64_t num_errors_handled_ = 0;
// In bytes, the estimated additional space used on disk after this
// transaction is committed. Note that this is a very approximate view of the
// changes associated with this transaction:
//
// * It ignores the additional overhead needed for meta records such as
// object stores.
// * It ignores compression which may be applied before rows are flushed to
// disk.
// * It ignores space freed up by deletions, which currently flow through
// DatabaseImpl::DeleteRange(), and which can't easily be calculated a
// priori.
//
// As such, it's only useful as a rough upper bound for the amount of
// additional space required by this transaction, used to abandon transactions
// that would likely exceed quota caps, but not used to calculate ultimate
// quota usage.
//
// See crbug.com/1493696 for discussion of how this should be improved.
int64_t preliminary_size_estimate_ = 0;
std::set<raw_ptr<Cursor, SetExperimental>> open_cursors_;
// This timer is started after requests have been processed. If no subsequent
// requests are processed before the timer fires, assume the script is
// unresponsive and abort to unblock the transaction queue.
base::RepeatingTimer timeout_timer_;
int timeout_strikes_ = 0;
// Poll every 20 seconds to see if this transaction is blocking others, and
// kill the transaction after 3 strikes. The polling mitigates the fact that
// timers may or may not pause when a system is suspended
// (crbug.com/40296804). See also crbug.com/40581991.
static constexpr base::TimeDelta kInactivityTimeoutPollPeriod =
base::Seconds(20);
static const int kMaxTimeoutStrikes = 3;
Diagnostics diagnostics_;
mojo::AssociatedReceiver<blink::mojom::IDBTransaction> receiver_;
base::WeakPtrFactory<Transaction> ptr_factory_{this};
};
} // namespace content::indexed_db
#endif // CONTENT_BROWSER_INDEXED_DB_INSTANCE_TRANSACTION_H_
|