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

content / browser / indexed_db / instance / transaction.h [blame]

// 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 "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/connection.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-forward.h"

namespace content::indexed_db {

class Cursor;
class DatabaseCallbacks;

// 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*)>;
  using AbortOperation = base::OnceClosure;

  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,
              BucketContextHandle bucket_context,
              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 ScheduleAbortTask(AbortOperation abort_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_; }

  DatabaseCallbacks* callbacks() const { return connection()->callbacks(); }
  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_; }

  // in_flight_memory() is used to keep track of all memory scheduled to be
  // written using ScheduleTask. This is reported to memory dumps.
  base::CheckedNumeric<size_t>& in_flight_memory() { return in_flight_memory_; }

 private:
  friend class IndexedDBClassFactory;
  friend class Connection;
  friend class base::RefCounted<Transaction>;

  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);

  // 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,
           const blink::IndexedDBKey& key,
           blink::mojom::IDBPutMode mode,
           const 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();

  // 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);

  const int64_t id_;
  const std::set<int64_t> object_store_ids_;
  const blink::mojom::IDBTransactionMode mode_;

  bool used_ = false;
  State state_ = CREATED;
  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_;
  };

  class TaskStack {
   public:
    TaskStack();

    TaskStack(const TaskStack&) = delete;
    TaskStack& operator=(const TaskStack&) = delete;

    ~TaskStack();
    bool empty() const { return stack_.empty(); }
    void push(AbortOperation task) { stack_.push(std::move(task)); }
    AbortOperation pop();
    void clear();

   private:
    base::stack<AbortOperation> stack_;
  };

  TaskQueue task_queue_;
  TaskQueue preemptive_task_queue_;
  TaskStack abort_task_stack_;

  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_