Coverage Report

Created: 2026-11-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/utilities/transactions/write_unprepared_txn.cc
Line
Count
Source
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2
//  This source code is licensed under both the GPLv2 (found in the
3
//  COPYING file in the root directory) and Apache 2.0 License
4
//  (found in the LICENSE.Apache file in the root directory).
5
6
#include "utilities/transactions/write_unprepared_txn.h"
7
8
#include "db/db_impl/db_impl.h"
9
#include "util/cast_util.h"
10
#include "utilities/transactions/write_unprepared_txn_db.h"
11
#include "utilities/write_batch_with_index/write_batch_with_index_internal.h"
12
13
namespace ROCKSDB_NAMESPACE {
14
15
0
bool WriteUnpreparedTxnReadCallback::IsVisibleFullCheck(SequenceNumber seq) {
16
  // Since unprep_seqs maps prep_seq => prepare_batch_cnt, to check if seq is
17
  // in unprep_seqs, we have to check if seq is equal to prep_seq or any of
18
  // the prepare_batch_cnt seq nums after it.
19
  //
20
  // TODO(lth): Can be optimized with std::lower_bound if unprep_seqs is
21
  // large.
22
0
  for (const auto& it : unprep_seqs_) {
23
0
    if (it.first <= seq && seq < it.first + it.second) {
24
0
      return true;
25
0
    }
26
0
  }
27
28
0
  bool snap_released = false;
29
0
  auto ret =
30
0
      db_->IsInSnapshot(seq, wup_snapshot_, min_uncommitted_, &snap_released);
31
0
  assert(!snap_released || backed_by_snapshot_ == kUnbackedByDBSnapshot);
32
0
  snap_released_ |= snap_released;
33
0
  return ret;
34
0
}
35
36
WriteUnpreparedTxn::WriteUnpreparedTxn(WriteUnpreparedTxnDB* txn_db,
37
                                       const WriteOptions& write_options,
38
                                       const TransactionOptions& txn_options)
39
0
    : WritePreparedTxn(txn_db, write_options, txn_options),
40
0
      wupt_db_(txn_db),
41
0
      last_log_number_(0),
42
0
      recovered_txn_(false),
43
0
      largest_validated_seq_(0) {
44
0
  if (txn_options.write_batch_flush_threshold < 0) {
45
0
    write_batch_flush_threshold_ =
46
0
        txn_db_impl_->GetTxnDBOptions().default_write_batch_flush_threshold;
47
0
  } else {
48
0
    write_batch_flush_threshold_ = txn_options.write_batch_flush_threshold;
49
0
  }
50
0
}
51
52
0
WriteUnpreparedTxn::~WriteUnpreparedTxn() {
53
0
  if (!unprep_seqs_.empty()) {
54
0
    assert(log_number_ > 0);
55
0
    assert(GetId() > 0);
56
0
    assert(!name_.empty());
57
58
    // We should rollback regardless of GetState, but some unit tests that
59
    // test crash recovery run the destructor assuming that rollback does not
60
    // happen, so that rollback during recovery can be exercised.
61
0
    if (GetState() == STARTED || GetState() == LOCKS_STOLEN) {
62
0
      auto s = RollbackInternal();
63
0
      assert(s.ok());
64
0
      if (!s.ok()) {
65
0
        ROCKS_LOG_FATAL(
66
0
            wupt_db_->info_log_,
67
0
            "Rollback of WriteUnprepared transaction failed in destructor: %s",
68
0
            s.ToString().c_str());
69
0
      }
70
0
      dbimpl_->logs_with_prep_tracker()->MarkLogAsHavingPrepSectionFlushed(
71
0
          log_number_);
72
0
    }
73
0
  }
74
75
  // Clear the tracked locks so that ~PessimisticTransaction does not
76
  // try to unlock keys for recovered transactions.
77
0
  if (recovered_txn_) {
78
0
    tracked_locks_->Clear();
79
0
  }
80
0
}
81
82
0
void WriteUnpreparedTxn::Initialize(const TransactionOptions& txn_options) {
83
0
  PessimisticTransaction::Initialize(txn_options);
84
0
  if (txn_options.write_batch_flush_threshold < 0) {
85
0
    write_batch_flush_threshold_ =
86
0
        txn_db_impl_->GetTxnDBOptions().default_write_batch_flush_threshold;
87
0
  } else {
88
0
    write_batch_flush_threshold_ = txn_options.write_batch_flush_threshold;
89
0
  }
90
91
0
  unprep_seqs_.clear();
92
0
  flushed_save_points_.reset(nullptr);
93
0
  unflushed_save_points_.reset(nullptr);
94
0
  recovered_txn_ = false;
95
0
  largest_validated_seq_ = 0;
96
0
  assert(active_iterators_.empty());
97
0
  active_iterators_.clear();
98
0
  untracked_keys_.clear();
99
0
}
100
101
Status WriteUnpreparedTxn::HandleWrite(
102
0
    const std::function<Status()>& do_write) {
103
0
  Status s;
104
0
  if (active_iterators_.empty()) {
105
0
    s = MaybeFlushWriteBatchToDB();
106
0
    if (!s.ok()) {
107
0
      return s;
108
0
    }
109
0
  }
110
0
  s = do_write();
111
0
  if (s.ok()) {
112
0
    if (snapshot_) {
113
0
      largest_validated_seq_ =
114
0
          std::max(largest_validated_seq_, snapshot_->GetSequenceNumber());
115
0
    } else {
116
      // TODO(lth): We should use the same number as tracked_at_seq in TryLock,
117
      // because what is actually being tracked is the sequence number at which
118
      // this key was locked at.
119
0
      largest_validated_seq_ = db_impl_->GetLastPublishedSequence();
120
0
    }
121
0
  }
122
0
  return s;
123
0
}
124
125
Status WriteUnpreparedTxn::Put(ColumnFamilyHandle* column_family,
126
                               const Slice& key, const Slice& value,
127
0
                               const bool assume_tracked) {
128
0
  return HandleWrite([&]() {
129
0
    return TransactionBaseImpl::Put(column_family, key, value, assume_tracked);
130
0
  });
131
0
}
132
133
Status WriteUnpreparedTxn::Put(ColumnFamilyHandle* column_family,
134
                               const SliceParts& key, const SliceParts& value,
135
0
                               const bool assume_tracked) {
136
0
  return HandleWrite([&]() {
137
0
    return TransactionBaseImpl::Put(column_family, key, value, assume_tracked);
138
0
  });
139
0
}
140
141
Status WriteUnpreparedTxn::Merge(ColumnFamilyHandle* column_family,
142
                                 const Slice& key, const Slice& value,
143
0
                                 const bool assume_tracked) {
144
0
  return HandleWrite([&]() {
145
0
    return TransactionBaseImpl::Merge(column_family, key, value,
146
0
                                      assume_tracked);
147
0
  });
148
0
}
149
150
Status WriteUnpreparedTxn::Delete(ColumnFamilyHandle* column_family,
151
0
                                  const Slice& key, const bool assume_tracked) {
152
0
  return HandleWrite([&]() {
153
0
    return TransactionBaseImpl::Delete(column_family, key, assume_tracked);
154
0
  });
155
0
}
156
157
Status WriteUnpreparedTxn::Delete(ColumnFamilyHandle* column_family,
158
                                  const SliceParts& key,
159
0
                                  const bool assume_tracked) {
160
0
  return HandleWrite([&]() {
161
0
    return TransactionBaseImpl::Delete(column_family, key, assume_tracked);
162
0
  });
163
0
}
164
165
Status WriteUnpreparedTxn::SingleDelete(ColumnFamilyHandle* column_family,
166
                                        const Slice& key,
167
0
                                        const bool assume_tracked) {
168
0
  return HandleWrite([&]() {
169
0
    return TransactionBaseImpl::SingleDelete(column_family, key,
170
0
                                             assume_tracked);
171
0
  });
172
0
}
173
174
Status WriteUnpreparedTxn::SingleDelete(ColumnFamilyHandle* column_family,
175
                                        const SliceParts& key,
176
0
                                        const bool assume_tracked) {
177
0
  return HandleWrite([&]() {
178
0
    return TransactionBaseImpl::SingleDelete(column_family, key,
179
0
                                             assume_tracked);
180
0
  });
181
0
}
182
183
// WriteUnpreparedTxn::RebuildFromWriteBatch is only called on recovery. For
184
// WriteUnprepared, the write batches have already been written into the
185
// database during WAL replay, so all we have to do is just to "retrack" the key
186
// so that rollbacks are possible.
187
//
188
// Calling TryLock instead of TrackKey is also possible, but as an optimization,
189
// recovered transactions do not hold locks on their keys. This follows the
190
// implementation in PessimisticTransactionDB::Initialize where we set
191
// skip_concurrency_control to true.
192
0
Status WriteUnpreparedTxn::RebuildFromWriteBatch(WriteBatch* wb) {
193
0
  struct TrackKeyHandler : public WriteBatch::Handler {
194
0
    WriteUnpreparedTxn* txn_;
195
0
    bool rollback_merge_operands_;
196
197
0
    TrackKeyHandler(WriteUnpreparedTxn* txn, bool rollback_merge_operands)
198
0
        : txn_(txn), rollback_merge_operands_(rollback_merge_operands) {}
199
200
0
    Status PutCF(uint32_t cf, const Slice& key, const Slice&) override {
201
0
      txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
202
0
                     false /* read_only */, true /* exclusive */);
203
0
      return Status::OK();
204
0
    }
205
206
0
    Status DeleteCF(uint32_t cf, const Slice& key) override {
207
0
      txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
208
0
                     false /* read_only */, true /* exclusive */);
209
0
      return Status::OK();
210
0
    }
211
212
0
    Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
213
0
      txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
214
0
                     false /* read_only */, true /* exclusive */);
215
0
      return Status::OK();
216
0
    }
217
218
0
    Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override {
219
0
      if (rollback_merge_operands_) {
220
0
        txn_->TrackKey(cf, key.ToString(), kMaxSequenceNumber,
221
0
                       false /* read_only */, true /* exclusive */);
222
0
      }
223
0
      return Status::OK();
224
0
    }
225
226
    // Recovered batches do not contain 2PC markers.
227
0
    Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); }
228
229
0
    Status MarkEndPrepare(const Slice&) override {
230
0
      return Status::InvalidArgument();
231
0
    }
232
233
0
    Status MarkNoop(bool) override { return Status::InvalidArgument(); }
234
235
0
    Status MarkCommit(const Slice&) override {
236
0
      return Status::InvalidArgument();
237
0
    }
238
239
0
    Status MarkRollback(const Slice&) override {
240
0
      return Status::InvalidArgument();
241
0
    }
242
0
  };
243
244
0
  TrackKeyHandler handler(this,
245
0
                          wupt_db_->txn_db_options_.rollback_merge_operands);
246
0
  return wb->Iterate(&handler);
247
0
}
248
249
0
Status WriteUnpreparedTxn::MaybeFlushWriteBatchToDB() {
250
0
  const bool kPrepared = true;
251
0
  Status s;
252
0
  if (write_batch_flush_threshold_ > 0 &&
253
0
      write_batch_.GetWriteBatch()->Count() > 0 &&
254
0
      write_batch_.GetDataSize() >
255
0
          static_cast<size_t>(write_batch_flush_threshold_)) {
256
0
    assert(GetState() != PREPARED);
257
0
    s = FlushWriteBatchToDB(!kPrepared);
258
0
  }
259
0
  return s;
260
0
}
261
262
0
Status WriteUnpreparedTxn::FlushWriteBatchToDB(bool prepared) {
263
  // If the current write batch contains savepoints, then some special handling
264
  // is required so that RollbackToSavepoint can work.
265
  //
266
  // RollbackToSavepoint is not supported after Prepare() is called, so only do
267
  // this for unprepared batches.
268
0
  if (!prepared && unflushed_save_points_ != nullptr &&
269
0
      !unflushed_save_points_->empty()) {
270
0
    return FlushWriteBatchWithSavePointToDB();
271
0
  }
272
273
0
  return FlushWriteBatchToDBInternal(prepared);
274
0
}
275
276
0
Status WriteUnpreparedTxn::FlushWriteBatchToDBInternal(bool prepared) {
277
0
  if (name_.empty()) {
278
0
    assert(!prepared);
279
#ifndef NDEBUG
280
    static std::atomic_ullong autogen_id{0};
281
    // To avoid changing all tests to call SetName, just autogenerate one.
282
    if (wupt_db_->txn_db_options_.autogenerate_name) {
283
      auto s = SetName(std::string("autoxid") +
284
                       std::to_string(autogen_id.fetch_add(1)));
285
      assert(s.ok());
286
    } else
287
#endif
288
0
    {
289
0
      return Status::InvalidArgument("Cannot write to DB without SetName.");
290
0
    }
291
0
  }
292
293
0
  struct UntrackedKeyHandler : public WriteBatch::Handler {
294
0
    WriteUnpreparedTxn* txn_;
295
0
    bool rollback_merge_operands_;
296
297
0
    UntrackedKeyHandler(WriteUnpreparedTxn* txn, bool rollback_merge_operands)
298
0
        : txn_(txn), rollback_merge_operands_(rollback_merge_operands) {}
299
300
0
    Status AddUntrackedKey(uint32_t cf, const Slice& key) {
301
0
      auto str = key.ToString();
302
0
      PointLockStatus lock_status =
303
0
          txn_->tracked_locks_->GetPointLockStatus(cf, str);
304
0
      if (!lock_status.locked) {
305
0
        txn_->untracked_keys_[cf].push_back(str);
306
0
      }
307
0
      return Status::OK();
308
0
    }
309
310
0
    Status PutCF(uint32_t cf, const Slice& key, const Slice&) override {
311
0
      return AddUntrackedKey(cf, key);
312
0
    }
313
314
0
    Status DeleteCF(uint32_t cf, const Slice& key) override {
315
0
      return AddUntrackedKey(cf, key);
316
0
    }
317
318
0
    Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
319
0
      return AddUntrackedKey(cf, key);
320
0
    }
321
322
0
    Status MergeCF(uint32_t cf, const Slice& key, const Slice&) override {
323
0
      if (rollback_merge_operands_) {
324
0
        return AddUntrackedKey(cf, key);
325
0
      }
326
0
      return Status::OK();
327
0
    }
328
329
    // The only expected 2PC marker is the initial Noop marker.
330
0
    Status MarkNoop(bool empty_batch) override {
331
0
      return empty_batch ? Status::OK() : Status::InvalidArgument();
332
0
    }
333
334
0
    Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); }
335
336
0
    Status MarkEndPrepare(const Slice&) override {
337
0
      return Status::InvalidArgument();
338
0
    }
339
340
0
    Status MarkCommit(const Slice&) override {
341
0
      return Status::InvalidArgument();
342
0
    }
343
344
0
    Status MarkRollback(const Slice&) override {
345
0
      return Status::InvalidArgument();
346
0
    }
347
0
  };
348
349
0
  UntrackedKeyHandler handler(
350
0
      this, wupt_db_->txn_db_options_.rollback_merge_operands);
351
0
  auto s = GetWriteBatch()->GetWriteBatch()->Iterate(&handler);
352
0
  assert(s.ok());
353
354
  // TODO(lth): Reduce duplicate code with WritePrepared prepare logic.
355
0
  WriteOptions write_options = write_options_;
356
0
  write_options.disableWAL = false;
357
0
  const bool WRITE_AFTER_COMMIT = true;
358
0
  const bool first_prepare_batch = log_number_ == 0;
359
  // MarkEndPrepare will change Noop marker to the appropriate marker.
360
0
  s = WriteBatchInternal::MarkEndPrepare(GetWriteBatch()->GetWriteBatch(),
361
0
                                         name_, !WRITE_AFTER_COMMIT, !prepared);
362
0
  assert(s.ok());
363
  // For each duplicate key we account for a new sub-batch
364
0
  prepare_batch_cnt_ = GetWriteBatch()->SubBatchCnt();
365
  // AddPrepared better to be called in the pre-release callback otherwise there
366
  // is a non-zero chance of max advancing prepare_seq and readers assume the
367
  // data as committed.
368
  // Also having it in the PreReleaseCallback allows in-order addition of
369
  // prepared entries to PreparedHeap and hence enables an optimization. Refer
370
  // to SmallestUnCommittedSeq for more details.
371
0
  AddPreparedCallback add_prepared_callback(
372
0
      wpt_db_, db_impl_, prepare_batch_cnt_,
373
0
      db_impl_->immutable_db_options().two_write_queues, first_prepare_batch);
374
0
  const bool DISABLE_MEMTABLE = true;
375
0
  uint64_t seq_used = kMaxSequenceNumber;
376
  // log_number_ should refer to the oldest log containing uncommitted data
377
  // from the current transaction. This means that if log_number_ is set,
378
  // WriteImpl should not overwrite that value, so set wal_used to nullptr if
379
  // log_number_ is already set.
380
0
  s = db_impl_->WriteImpl(write_options, GetWriteBatch()->GetWriteBatch(),
381
0
                          /*callback*/ nullptr, /*user_write_cb=*/nullptr,
382
0
                          &last_log_number_,
383
0
                          /*log ref*/ 0, !DISABLE_MEMTABLE, &seq_used,
384
0
                          prepare_batch_cnt_, &add_prepared_callback);
385
0
  if (log_number_ == 0) {
386
0
    log_number_ = last_log_number_;
387
0
  }
388
0
  assert(!s.ok() || seq_used != kMaxSequenceNumber);
389
0
  auto prepare_seq = seq_used;
390
391
  // Only call SetId if it hasn't been set yet.
392
0
  if (GetId() == 0) {
393
0
    SetId(prepare_seq);
394
0
  }
395
  // unprep_seqs_ will also contain prepared seqnos since they are treated in
396
  // the same way in the prepare/commit callbacks. See the comment on the
397
  // definition of unprep_seqs_.
398
0
  if (s.ok()) {
399
0
    unprep_seqs_[prepare_seq] = prepare_batch_cnt_;
400
0
  }
401
402
  // Reset transaction state.
403
0
  if (!prepared) {
404
0
    prepare_batch_cnt_ = 0;
405
0
    const bool kClear = true;
406
0
    TransactionBaseImpl::InitWriteBatch(kClear);
407
0
  }
408
409
0
  return s;
410
0
}
411
412
0
Status WriteUnpreparedTxn::FlushWriteBatchWithSavePointToDB() {
413
0
  assert(unflushed_save_points_ != nullptr &&
414
0
         unflushed_save_points_->size() > 0);
415
0
  assert(save_points_ != nullptr && save_points_->size() > 0);
416
0
  assert(save_points_->size() >= unflushed_save_points_->size());
417
418
  // Handler class for creating an unprepared batch from a savepoint.
419
0
  struct SavePointBatchHandler : public WriteBatch::Handler {
420
0
    WriteBatchWithIndex* wb_;
421
0
    const std::map<uint32_t, ColumnFamilyHandle*>& handles_;
422
423
0
    SavePointBatchHandler(
424
0
        WriteBatchWithIndex* wb,
425
0
        const std::map<uint32_t, ColumnFamilyHandle*>& handles)
426
0
        : wb_(wb), handles_(handles) {}
427
428
0
    Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override {
429
0
      return wb_->Put(handles_.at(cf), key, value);
430
0
    }
431
432
0
    Status DeleteCF(uint32_t cf, const Slice& key) override {
433
0
      return wb_->Delete(handles_.at(cf), key);
434
0
    }
435
436
0
    Status SingleDeleteCF(uint32_t cf, const Slice& key) override {
437
0
      return wb_->SingleDelete(handles_.at(cf), key);
438
0
    }
439
440
0
    Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override {
441
0
      return wb_->Merge(handles_.at(cf), key, value);
442
0
    }
443
444
    // The only expected 2PC marker is the initial Noop marker.
445
0
    Status MarkNoop(bool empty_batch) override {
446
0
      return empty_batch ? Status::OK() : Status::InvalidArgument();
447
0
    }
448
449
0
    Status MarkBeginPrepare(bool) override { return Status::InvalidArgument(); }
450
451
0
    Status MarkEndPrepare(const Slice&) override {
452
0
      return Status::InvalidArgument();
453
0
    }
454
455
0
    Status MarkCommit(const Slice&) override {
456
0
      return Status::InvalidArgument();
457
0
    }
458
459
0
    Status MarkRollback(const Slice&) override {
460
0
      return Status::InvalidArgument();
461
0
    }
462
0
  };
463
464
  // The comparator of the default cf is passed in, similar to the
465
  // initialization of TransactionBaseImpl::write_batch_. This comparator is
466
  // only used if the write batch encounters an invalid cf id, and falls back to
467
  // this comparator.
468
0
  WriteBatchWithIndex wb(wpt_db_->DefaultColumnFamily()->GetComparator(), 0,
469
0
                         true, 0, write_options_.protection_bytes_per_key);
470
  // Swap with write_batch_ so that wb contains the complete write batch. The
471
  // actual write batch that will be flushed to DB will be built in
472
  // write_batch_, and will be read by FlushWriteBatchToDBInternal.
473
0
  std::swap(wb, write_batch_);
474
0
  TransactionBaseImpl::InitWriteBatch();
475
476
0
  size_t prev_boundary = WriteBatchInternal::kHeader;
477
0
  const bool kPrepared = true;
478
0
  for (size_t i = 0; i < unflushed_save_points_->size() + 1; i++) {
479
0
    bool trailing_batch = i == unflushed_save_points_->size();
480
0
    SavePointBatchHandler sp_handler(&write_batch_,
481
0
                                     *wupt_db_->GetCFHandleMap().get());
482
0
    size_t curr_boundary = trailing_batch ? wb.GetWriteBatch()->GetDataSize()
483
0
                                          : (*unflushed_save_points_)[i];
484
485
    // Construct the partial write batch up to the savepoint.
486
    //
487
    // Theoretically, a memcpy between the write batches should be sufficient
488
    // since the rewriting into the batch should produce the exact same byte
489
    // representation. Rebuilding the WriteBatchWithIndex index is still
490
    // necessary though, and would imply doing two passes over the batch though.
491
0
    Status s = WriteBatchInternal::Iterate(wb.GetWriteBatch(), &sp_handler,
492
0
                                           prev_boundary, curr_boundary);
493
0
    if (!s.ok()) {
494
0
      return s;
495
0
    }
496
497
0
    if (write_batch_.GetWriteBatch()->Count() > 0) {
498
      // Flush the write batch.
499
0
      s = FlushWriteBatchToDBInternal(!kPrepared);
500
0
      if (!s.ok()) {
501
0
        return s;
502
0
      }
503
0
    }
504
505
0
    if (!trailing_batch) {
506
0
      if (flushed_save_points_ == nullptr) {
507
0
        flushed_save_points_.reset(
508
0
            new autovector<WriteUnpreparedTxn::SavePoint>());
509
0
      }
510
0
      flushed_save_points_->emplace_back(
511
0
          unprep_seqs_, new ManagedSnapshot(db_impl_, wupt_db_->GetSnapshot()));
512
0
    }
513
514
0
    prev_boundary = curr_boundary;
515
0
    const bool kClear = true;
516
0
    TransactionBaseImpl::InitWriteBatch(kClear);
517
0
  }
518
519
0
  unflushed_save_points_->clear();
520
0
  return Status::OK();
521
0
}
522
523
0
Status WriteUnpreparedTxn::PrepareInternal() {
524
0
  const bool kPrepared = true;
525
0
  return FlushWriteBatchToDB(kPrepared);
526
0
}
527
528
0
Status WriteUnpreparedTxn::CommitWithoutPrepareInternal() {
529
0
  if (unprep_seqs_.empty()) {
530
0
    assert(log_number_ == 0);
531
0
    assert(GetId() == 0);
532
0
    return WritePreparedTxn::CommitWithoutPrepareInternal();
533
0
  }
534
535
  // TODO(lth): We should optimize commit without prepare to not perform
536
  // a prepare under the hood.
537
0
  auto s = PrepareInternal();
538
0
  if (!s.ok()) {
539
0
    return s;
540
0
  }
541
0
  return CommitInternal();
542
0
}
543
544
0
Status WriteUnpreparedTxn::CommitInternal() {
545
  // TODO(lth): Reduce duplicate code with WritePrepared commit logic.
546
547
  // We take the commit-time batch and append the Commit marker.  The Memtable
548
  // will ignore the Commit marker in non-recovery mode
549
0
  WriteBatch* working_batch = GetCommitTimeWriteBatch();
550
0
  const bool empty = working_batch->Count() == 0;
551
0
  auto s = WriteBatchInternal::MarkCommit(working_batch, name_);
552
0
  assert(s.ok());
553
554
0
  const bool for_recovery = use_only_the_last_commit_time_batch_for_recovery_;
555
0
  if (!empty) {
556
    // When not writing to memtable, we can still cache the latest write batch.
557
    // The cached batch will be written to memtable in WriteRecoverableState
558
    // during FlushMemTable
559
0
    if (for_recovery) {
560
0
      WriteBatchInternal::SetAsLatestPersistentState(working_batch);
561
0
    } else {
562
0
      return Status::InvalidArgument(
563
0
          "Commit-time-batch can only be used if "
564
0
          "use_only_the_last_commit_time_batch_for_recovery is true");
565
0
    }
566
0
  }
567
568
0
  const bool includes_data = !empty && !for_recovery;
569
0
  size_t commit_batch_cnt = 0;
570
0
  if (UNLIKELY(includes_data)) {
571
0
    ROCKS_LOG_WARN(db_impl_->immutable_db_options().info_log,
572
0
                   "Duplicate key overhead");
573
0
    SubBatchCounter counter(*wpt_db_->GetCFComparatorMap());
574
0
    s = working_batch->Iterate(&counter);
575
0
    assert(s.ok());
576
0
    commit_batch_cnt = counter.BatchCount();
577
0
  }
578
0
  const bool disable_memtable = !includes_data;
579
0
  const bool do_one_write =
580
0
      !db_impl_->immutable_db_options().two_write_queues || disable_memtable;
581
582
0
  WriteUnpreparedCommitEntryPreReleaseCallback update_commit_map(
583
0
      wpt_db_, db_impl_, unprep_seqs_, commit_batch_cnt);
584
0
  const bool kFirstPrepareBatch = true;
585
0
  AddPreparedCallback add_prepared_callback(
586
0
      wpt_db_, db_impl_, commit_batch_cnt,
587
0
      db_impl_->immutable_db_options().two_write_queues, !kFirstPrepareBatch);
588
0
  PreReleaseCallback* pre_release_callback;
589
0
  if (do_one_write) {
590
0
    pre_release_callback = &update_commit_map;
591
0
  } else {
592
0
    pre_release_callback = &add_prepared_callback;
593
0
  }
594
0
  uint64_t seq_used = kMaxSequenceNumber;
595
  // Since the prepared batch is directly written to memtable, there is
596
  // already a connection between the memtable and its WAL, so there is no
597
  // need to redundantly reference the log that contains the prepared data.
598
0
  const uint64_t zero_log_number = 0ull;
599
0
  size_t batch_cnt = UNLIKELY(commit_batch_cnt) ? commit_batch_cnt : 1;
600
0
  s = db_impl_->WriteImpl(write_options_, working_batch, nullptr, nullptr,
601
0
                          nullptr, zero_log_number, disable_memtable, &seq_used,
602
0
                          batch_cnt, pre_release_callback);
603
0
  assert(!s.ok() || seq_used != kMaxSequenceNumber);
604
0
  const SequenceNumber commit_batch_seq = seq_used;
605
0
  if (LIKELY(do_one_write || !s.ok())) {
606
0
    if (LIKELY(s.ok())) {
607
      // Note RemovePrepared should be called after WriteImpl that publishsed
608
      // the seq. Otherwise SmallestUnCommittedSeq optimization breaks.
609
0
      for (const auto& seq : unprep_seqs_) {
610
0
        wpt_db_->RemovePrepared(seq.first, seq.second);
611
0
      }
612
0
      unprep_seqs_.clear();
613
0
      flushed_save_points_.reset(nullptr);
614
0
      unflushed_save_points_.reset(nullptr);
615
0
    } else if (UNLIKELY(!do_one_write)) {
616
      // The commit write failed after add_prepared_callback may have added a
617
      // prepared entry for commit_batch_seq. Since commit_batch_seq is never
618
      // stored in unprep_seqs_ on this path, a follow-up Rollback() cannot
619
      // remove it, so clean it up here. unprep_seqs_ is otherwise preserved so
620
      // the transaction can still be rolled back after the caller recovers
621
      // from the write error.
622
0
      wpt_db_->RemovePrepared(commit_batch_seq, commit_batch_cnt);
623
0
    }
624
0
    return s;
625
0
  }  // else do the 2nd write to publish seq
626
627
  // Populate unprep_seqs_ with commit_batch_seq, since we treat data in the
628
  // commit write batch as just another "unprepared" batch. This will also
629
  // update the unprep_seqs_ in the update_commit_map callback.
630
0
  unprep_seqs_[commit_batch_seq] = commit_batch_cnt;
631
0
  WriteUnpreparedCommitEntryPreReleaseCallback
632
0
      update_commit_map_with_commit_batch(wpt_db_, db_impl_, unprep_seqs_, 0);
633
634
  // Note: the 2nd write comes with a performance penality. So if we have too
635
  // many of commits accompanied with ComitTimeWriteBatch and yet we cannot
636
  // enable use_only_the_last_commit_time_batch_for_recovery_ optimization,
637
  // two_write_queues should be disabled to avoid many additional writes here.
638
639
  // Update commit map only from the 2nd queue
640
0
  WriteBatch empty_batch;
641
0
  s = empty_batch.PutLogData(Slice());
642
0
  assert(s.ok());
643
  // In the absence of Prepare markers, use Noop as a batch separator
644
0
  s = WriteBatchInternal::InsertNoop(&empty_batch);
645
0
  assert(s.ok());
646
0
  const bool DISABLE_MEMTABLE = true;
647
0
  const size_t ONE_BATCH = 1;
648
0
  const uint64_t NO_REF_LOG = 0;
649
0
  s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr,
650
0
                          nullptr, NO_REF_LOG, DISABLE_MEMTABLE, &seq_used,
651
0
                          ONE_BATCH, &update_commit_map_with_commit_batch);
652
0
  assert(!s.ok() || seq_used != kMaxSequenceNumber);
653
  // Note RemovePrepared should be called after WriteImpl that publishsed the
654
  // seq. Otherwise SmallestUnCommittedSeq optimization breaks.
655
0
  for (const auto& seq : unprep_seqs_) {
656
0
    wpt_db_->RemovePrepared(seq.first, seq.second);
657
0
  }
658
0
  unprep_seqs_.clear();
659
0
  flushed_save_points_.reset(nullptr);
660
0
  unflushed_save_points_.reset(nullptr);
661
0
  return s;
662
0
}
663
664
Status WriteUnpreparedTxn::WriteRollbackKeys(
665
    const LockTracker& lock_tracker, WriteBatchWithIndex* rollback_batch,
666
0
    ReadCallback* callback, const ReadOptions& roptions) {
667
  // This assertion can be removed when range lock is supported.
668
0
  assert(lock_tracker.IsPointLockSupported());
669
0
  const auto& cf_map = *wupt_db_->GetCFHandleMap();
670
0
  auto WriteRollbackKey = [&](const std::string& key, uint32_t cfid) {
671
0
    const auto& cf_handle = cf_map.at(cfid);
672
0
    PinnableSlice pinnable_val;
673
0
    bool not_used;
674
0
    DBImpl::GetImplOptions get_impl_options;
675
0
    get_impl_options.column_family = cf_handle;
676
0
    get_impl_options.value = &pinnable_val;
677
0
    get_impl_options.value_found = &not_used;
678
0
    get_impl_options.callback = callback;
679
0
    auto s = db_impl_->GetImpl(roptions, key, get_impl_options);
680
681
0
    if (s.ok()) {
682
0
      s = rollback_batch->Put(cf_handle, key, pinnable_val);
683
0
      assert(s.ok());
684
0
    } else if (s.IsNotFound()) {
685
0
      if (wupt_db_->ShouldRollbackWithSingleDelete(cf_handle, key)) {
686
0
        s = rollback_batch->SingleDelete(cf_handle, key);
687
0
      } else {
688
0
        s = rollback_batch->Delete(cf_handle, key);
689
0
      }
690
0
      assert(s.ok());
691
0
    } else {
692
0
      return s;
693
0
    }
694
695
0
    return Status::OK();
696
0
  };
697
698
0
  std::unique_ptr<LockTracker::ColumnFamilyIterator> cf_it(
699
0
      lock_tracker.GetColumnFamilyIterator());
700
0
  assert(cf_it != nullptr);
701
0
  while (cf_it->HasNext()) {
702
0
    ColumnFamilyId cf = cf_it->Next();
703
0
    std::unique_ptr<LockTracker::KeyIterator> key_it(
704
0
        lock_tracker.GetKeyIterator(cf));
705
0
    assert(key_it != nullptr);
706
0
    while (key_it->HasNext()) {
707
0
      const std::string& key = key_it->Next();
708
0
      auto s = WriteRollbackKey(key, cf);
709
0
      if (!s.ok()) {
710
0
        return s;
711
0
      }
712
0
    }
713
0
  }
714
715
0
  for (const auto& cfkey : untracked_keys_) {
716
0
    const auto cfid = cfkey.first;
717
0
    const auto& keys = cfkey.second;
718
0
    for (const auto& key : keys) {
719
0
      auto s = WriteRollbackKey(key, cfid);
720
0
      if (!s.ok()) {
721
0
        return s;
722
0
      }
723
0
    }
724
0
  }
725
726
0
  return Status::OK();
727
0
}
728
729
0
Status WriteUnpreparedTxn::RollbackInternal() {
730
  // TODO(lth): Reduce duplicate code with WritePrepared rollback logic.
731
0
  WriteBatchWithIndex rollback_batch(
732
0
      wpt_db_->DefaultColumnFamily()->GetComparator(), 0, true, 0,
733
0
      write_options_.protection_bytes_per_key);
734
0
  assert(GetId() != kMaxSequenceNumber);
735
0
  assert(GetId() > 0);
736
0
  Status s;
737
0
  auto read_at_seq = kMaxSequenceNumber;
738
  // TODO: plumb Env::IOActivity, Env::IOPriority
739
0
  ReadOptions roptions;
740
  // to prevent callback's seq to be overrriden inside DBImpk::Get
741
0
  roptions.snapshot = wpt_db_->GetMaxSnapshot();
742
  // Note that we do not use WriteUnpreparedTxnReadCallback because we do not
743
  // need to read our own writes when reading prior versions of the key for
744
  // rollback.
745
0
  WritePreparedTxnReadCallback callback(wpt_db_, read_at_seq);
746
  // TODO(lth): We write rollback batch all in a single batch here, but this
747
  // should be subdivded into multiple batches as well. In phase 2, when key
748
  // sets are read from WAL, this will happen naturally.
749
0
  s = WriteRollbackKeys(*tracked_locks_, &rollback_batch, &callback, roptions);
750
0
  if (!s.ok()) {
751
0
    return s;
752
0
  }
753
754
  // The Rollback marker will be used as a batch separator
755
0
  s = WriteBatchInternal::MarkRollback(rollback_batch.GetWriteBatch(), name_);
756
0
  assert(s.ok());
757
0
  bool do_one_write = !db_impl_->immutable_db_options().two_write_queues;
758
0
  const bool DISABLE_MEMTABLE = true;
759
0
  const uint64_t NO_REF_LOG = 0;
760
0
  uint64_t seq_used = kMaxSequenceNumber;
761
  // Rollback batch may contain duplicate keys, because tracked_keys_ is not
762
  // comparator aware.
763
0
  auto rollback_batch_cnt = rollback_batch.SubBatchCnt();
764
  // We commit the rolled back prepared batches. Although this is
765
  // counter-intuitive, i) it is safe to do so, since the prepared batches are
766
  // already canceled out by the rollback batch, ii) adding the commit entry to
767
  // CommitCache will allow us to benefit from the existing mechanism in
768
  // CommitCache that keeps an entry evicted due to max advance and yet overlaps
769
  // with a live snapshot around so that the live snapshot properly skips the
770
  // entry even if its prepare seq is lower than max_evicted_seq_.
771
  //
772
  // TODO(lth): RollbackInternal is conceptually very similar to
773
  // CommitInternal, with the rollback batch simply taking on the role of
774
  // CommitTimeWriteBatch. We should be able to merge the two code paths.
775
0
  WriteUnpreparedCommitEntryPreReleaseCallback update_commit_map(
776
0
      wpt_db_, db_impl_, unprep_seqs_, rollback_batch_cnt);
777
  // Note: the rollback batch does not need AddPrepared since it is written to
778
  // DB in one shot. min_uncommitted still works since it requires capturing
779
  // data that is written to DB but not yet committed, while the rollback
780
  // batch commits with PreReleaseCallback.
781
0
  s = db_impl_->WriteImpl(write_options_, rollback_batch.GetWriteBatch(),
782
0
                          nullptr, nullptr, nullptr, NO_REF_LOG,
783
0
                          !DISABLE_MEMTABLE, &seq_used, rollback_batch_cnt,
784
0
                          do_one_write ? &update_commit_map : nullptr);
785
0
  assert(!s.ok() || seq_used != kMaxSequenceNumber);
786
0
  if (!s.ok()) {
787
0
    return s;
788
0
  }
789
0
  if (do_one_write) {
790
0
    for (const auto& seq : unprep_seqs_) {
791
0
      wpt_db_->RemovePrepared(seq.first, seq.second);
792
0
    }
793
0
    unprep_seqs_.clear();
794
0
    flushed_save_points_.reset(nullptr);
795
0
    unflushed_save_points_.reset(nullptr);
796
0
    return s;
797
0
  }  // else do the 2nd write for commit
798
799
0
  uint64_t& prepare_seq = seq_used;
800
  // Populate unprep_seqs_ with rollback_batch_cnt, since we treat data in the
801
  // rollback write batch as just another "unprepared" batch. This will also
802
  // update the unprep_seqs_ in the update_commit_map callback.
803
0
  unprep_seqs_[prepare_seq] = rollback_batch_cnt;
804
0
  WriteUnpreparedCommitEntryPreReleaseCallback
805
0
      update_commit_map_with_rollback_batch(wpt_db_, db_impl_, unprep_seqs_, 0);
806
807
0
  ROCKS_LOG_DETAILS(db_impl_->immutable_db_options().info_log,
808
0
                    "RollbackInternal 2nd write prepare_seq: %" PRIu64,
809
0
                    prepare_seq);
810
0
  WriteBatch empty_batch;
811
0
  const size_t ONE_BATCH = 1;
812
0
  s = empty_batch.PutLogData(Slice());
813
0
  assert(s.ok());
814
  // In the absence of Prepare markers, use Noop as a batch separator
815
0
  s = WriteBatchInternal::InsertNoop(&empty_batch);
816
0
  assert(s.ok());
817
0
  s = db_impl_->WriteImpl(write_options_, &empty_batch, nullptr, nullptr,
818
0
                          nullptr, NO_REF_LOG, DISABLE_MEMTABLE, &seq_used,
819
0
                          ONE_BATCH, &update_commit_map_with_rollback_batch);
820
0
  assert(!s.ok() || seq_used != kMaxSequenceNumber);
821
  // Mark the txn as rolled back
822
0
  if (s.ok()) {
823
0
    for (const auto& seq : unprep_seqs_) {
824
0
      wpt_db_->RemovePrepared(seq.first, seq.second);
825
0
    }
826
0
  }
827
828
0
  unprep_seqs_.clear();
829
0
  flushed_save_points_.reset(nullptr);
830
0
  unflushed_save_points_.reset(nullptr);
831
0
  return s;
832
0
}
833
834
0
void WriteUnpreparedTxn::Clear() {
835
0
  if (!recovered_txn_) {
836
0
    txn_db_impl_->UnLock(this, *tracked_locks_);
837
0
  }
838
0
  unprep_seqs_.clear();
839
0
  flushed_save_points_.reset(nullptr);
840
0
  unflushed_save_points_.reset(nullptr);
841
0
  recovered_txn_ = false;
842
0
  largest_validated_seq_ = 0;
843
0
  for (auto& it : active_iterators_) {
844
0
    auto bdit = static_cast<BaseDeltaIterator*>(it);
845
0
    bdit->Invalidate(Status::InvalidArgument(
846
0
        "Cannot use iterator after transaction has finished"));
847
0
  }
848
0
  active_iterators_.clear();
849
0
  untracked_keys_.clear();
850
0
  TransactionBaseImpl::Clear();
851
0
}
852
853
0
void WriteUnpreparedTxn::SetSavePoint() {
854
0
  assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) +
855
0
             (flushed_save_points_ ? flushed_save_points_->size() : 0) ==
856
0
         (save_points_ ? save_points_->size() : 0));
857
0
  PessimisticTransaction::SetSavePoint();
858
0
  if (unflushed_save_points_ == nullptr) {
859
0
    unflushed_save_points_.reset(new autovector<size_t>());
860
0
  }
861
0
  unflushed_save_points_->push_back(write_batch_.GetDataSize());
862
0
}
863
864
0
Status WriteUnpreparedTxn::RollbackToSavePoint() {
865
0
  assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) +
866
0
             (flushed_save_points_ ? flushed_save_points_->size() : 0) ==
867
0
         (save_points_ ? save_points_->size() : 0));
868
0
  if (unflushed_save_points_ != nullptr && unflushed_save_points_->size() > 0) {
869
0
    Status s = PessimisticTransaction::RollbackToSavePoint();
870
0
    assert(!s.IsNotFound());
871
0
    unflushed_save_points_->pop_back();
872
0
    return s;
873
0
  }
874
875
0
  if (flushed_save_points_ != nullptr && !flushed_save_points_->empty()) {
876
0
    return RollbackToSavePointInternal();
877
0
  }
878
879
0
  return Status::NotFound();
880
0
}
881
882
0
Status WriteUnpreparedTxn::RollbackToSavePointInternal() {
883
0
  Status s;
884
885
0
  const bool kClear = true;
886
0
  TransactionBaseImpl::InitWriteBatch(kClear);
887
888
0
  assert(flushed_save_points_->size() > 0);
889
0
  WriteUnpreparedTxn::SavePoint& top = flushed_save_points_->back();
890
891
0
  assert(save_points_ != nullptr && save_points_->size() > 0);
892
0
  const LockTracker& tracked_keys = *save_points_->top().new_locks_;
893
894
  // TODO: plumb Env::IOActivity, Env::IOPriority
895
0
  ReadOptions roptions;
896
0
  roptions.snapshot = top.snapshot_->snapshot();
897
0
  SequenceNumber min_uncommitted =
898
0
      static_cast_with_check<const SnapshotImpl>(roptions.snapshot)
899
0
          ->min_uncommitted_;
900
0
  SequenceNumber snap_seq = roptions.snapshot->GetSequenceNumber();
901
0
  WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
902
0
                                          top.unprep_seqs_,
903
0
                                          kBackedByDBSnapshot);
904
0
  s = WriteRollbackKeys(tracked_keys, &write_batch_, &callback, roptions);
905
0
  if (!s.ok()) {
906
0
    return s;
907
0
  }
908
909
0
  const bool kPrepared = true;
910
0
  s = FlushWriteBatchToDBInternal(!kPrepared);
911
0
  if (!s.ok()) {
912
0
    return s;
913
0
  }
914
915
  // PessimisticTransaction::RollbackToSavePoint will call also call
916
  // RollbackToSavepoint on write_batch_. However, write_batch_ is empty and has
917
  // no savepoints because this savepoint has already been flushed. Work around
918
  // this by setting a fake savepoint.
919
0
  write_batch_.SetSavePoint();
920
0
  s = PessimisticTransaction::RollbackToSavePoint();
921
0
  assert(s.ok());
922
0
  if (!s.ok()) {
923
0
    return s;
924
0
  }
925
926
0
  flushed_save_points_->pop_back();
927
0
  return s;
928
0
}
929
930
0
Status WriteUnpreparedTxn::PopSavePoint() {
931
0
  assert((unflushed_save_points_ ? unflushed_save_points_->size() : 0) +
932
0
             (flushed_save_points_ ? flushed_save_points_->size() : 0) ==
933
0
         (save_points_ ? save_points_->size() : 0));
934
0
  if (unflushed_save_points_ != nullptr && unflushed_save_points_->size() > 0) {
935
0
    Status s = PessimisticTransaction::PopSavePoint();
936
0
    assert(!s.IsNotFound());
937
0
    unflushed_save_points_->pop_back();
938
0
    return s;
939
0
  }
940
941
0
  if (flushed_save_points_ != nullptr && !flushed_save_points_->empty()) {
942
    // PessimisticTransaction::PopSavePoint will call also call PopSavePoint on
943
    // write_batch_. However, write_batch_ is empty and has no savepoints
944
    // because this savepoint has already been flushed. Work around this by
945
    // setting a fake savepoint.
946
0
    write_batch_.SetSavePoint();
947
0
    Status s = PessimisticTransaction::PopSavePoint();
948
0
    assert(!s.IsNotFound());
949
0
    flushed_save_points_->pop_back();
950
0
    return s;
951
0
  }
952
953
0
  return Status::NotFound();
954
0
}
955
956
void WriteUnpreparedTxn::MultiGet(const ReadOptions& _read_options,
957
                                  ColumnFamilyHandle* column_family,
958
                                  const size_t num_keys, const Slice* keys,
959
                                  PinnableSlice* values, Status* statuses,
960
0
                                  const bool sorted_input) {
961
0
  if (_read_options.io_activity != Env::IOActivity::kUnknown &&
962
0
      _read_options.io_activity != Env::IOActivity::kMultiGet) {
963
0
    Status s = Status::InvalidArgument(
964
0
        "Can only call MultiGet with `ReadOptions::io_activity` is "
965
0
        "`Env::IOActivity::kUnknown` or `Env::IOActivity::kMultiGet`");
966
967
0
    for (size_t i = 0; i < num_keys; ++i) {
968
0
      if (statuses[i].ok()) {
969
0
        statuses[i] = s;
970
0
      }
971
0
    }
972
0
    return;
973
0
  }
974
0
  ReadOptions read_options(_read_options);
975
0
  if (read_options.io_activity == Env::IOActivity::kUnknown) {
976
0
    read_options.io_activity = Env::IOActivity::kMultiGet;
977
0
  }
978
0
  SequenceNumber min_uncommitted;
979
0
  SequenceNumber snap_seq;
980
0
  const SnapshotBackup backed_by_snapshot = wupt_db_->AssignMinMaxSeqs(
981
0
      read_options.snapshot, &min_uncommitted, &snap_seq);
982
0
  WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
983
0
                                          unprep_seqs_, backed_by_snapshot);
984
0
  write_batch_.MultiGetFromBatchAndDB(db_, read_options, column_family,
985
0
                                      num_keys, keys, values, statuses,
986
0
                                      sorted_input, &callback);
987
0
  if (UNLIKELY(!callback.valid() ||
988
0
               !wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) {
989
0
    wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
990
0
    for (size_t i = 0; i < num_keys; i++) {
991
0
      statuses[i] = Status::TryAgain();
992
0
    }
993
0
  }
994
0
}
995
996
Status WriteUnpreparedTxn::Get(const ReadOptions& _read_options,
997
                               ColumnFamilyHandle* column_family,
998
0
                               const Slice& key, PinnableSlice* value) {
999
0
  if (_read_options.io_activity != Env::IOActivity::kUnknown &&
1000
0
      _read_options.io_activity != Env::IOActivity::kGet) {
1001
0
    return Status::InvalidArgument(
1002
0
        "Can only call Get with `ReadOptions::io_activity` is "
1003
0
        "`Env::IOActivity::kUnknown` or `Env::IOActivity::kGet`");
1004
0
  }
1005
0
  ReadOptions read_options(_read_options);
1006
0
  if (read_options.io_activity == Env::IOActivity::kUnknown) {
1007
0
    read_options.io_activity = Env::IOActivity::kGet;
1008
0
  }
1009
1010
0
  return GetImpl(read_options, column_family, key, value);
1011
0
}
1012
1013
Status WriteUnpreparedTxn::GetImpl(const ReadOptions& options,
1014
                                   ColumnFamilyHandle* column_family,
1015
0
                                   const Slice& key, PinnableSlice* value) {
1016
0
  SequenceNumber min_uncommitted;
1017
0
  SequenceNumber snap_seq;
1018
0
  const SnapshotBackup backed_by_snapshot =
1019
0
      wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
1020
0
  WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
1021
0
                                          unprep_seqs_, backed_by_snapshot);
1022
0
  auto res = write_batch_.GetFromBatchAndDB(db_, options, column_family, key,
1023
0
                                            value, &callback);
1024
0
  if (LIKELY(callback.valid() &&
1025
0
             wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) {
1026
0
    return res;
1027
0
  } else {
1028
0
    res.PermitUncheckedError();
1029
0
    wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
1030
0
    return Status::TryAgain();
1031
0
  }
1032
0
}
1033
1034
Status WriteUnpreparedTxn::GetEntityImpl(const ReadOptions& options,
1035
                                         ColumnFamilyHandle* column_family,
1036
                                         const Slice& key,
1037
0
                                         PinnableWideColumns* columns) {
1038
0
  SequenceNumber min_uncommitted;
1039
0
  SequenceNumber snap_seq;
1040
0
  const SnapshotBackup backed_by_snapshot =
1041
0
      wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
1042
0
  WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
1043
0
                                          unprep_seqs_, backed_by_snapshot);
1044
0
  Status res = write_batch_.GetEntityFromBatchAndDB(db_, options, column_family,
1045
0
                                                    key, columns, &callback);
1046
0
  if (LIKELY(callback.valid() &&
1047
0
             wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) {
1048
0
    return res;
1049
0
  } else {
1050
0
    res.PermitUncheckedError();
1051
0
    wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
1052
0
    return Status::TryAgain();
1053
0
  }
1054
0
}
1055
1056
void WriteUnpreparedTxn::MultiGetEntityImpl(const ReadOptions& options,
1057
                                            ColumnFamilyHandle* column_family,
1058
                                            size_t num_keys, const Slice* keys,
1059
                                            PinnableWideColumns* results,
1060
                                            Status* statuses,
1061
0
                                            bool sorted_input) {
1062
0
  SequenceNumber min_uncommitted;
1063
0
  SequenceNumber snap_seq;
1064
0
  const SnapshotBackup backed_by_snapshot =
1065
0
      wupt_db_->AssignMinMaxSeqs(options.snapshot, &min_uncommitted, &snap_seq);
1066
0
  WriteUnpreparedTxnReadCallback callback(wupt_db_, snap_seq, min_uncommitted,
1067
0
                                          unprep_seqs_, backed_by_snapshot);
1068
0
  write_batch_.MultiGetEntityFromBatchAndDB(db_, options, column_family,
1069
0
                                            num_keys, keys, results, statuses,
1070
0
                                            sorted_input, &callback);
1071
0
  if (UNLIKELY(!callback.valid() ||
1072
0
               !wupt_db_->ValidateSnapshot(snap_seq, backed_by_snapshot))) {
1073
0
    wupt_db_->WPRecordTick(TXN_GET_TRY_AGAIN);
1074
0
    for (size_t i = 0; i < num_keys; i++) {
1075
0
      statuses[i] = Status::TryAgain();
1076
0
    }
1077
0
  }
1078
0
}
1079
1080
namespace {
1081
0
static void CleanupWriteUnpreparedWBWIIterator(void* arg1, void* arg2) {
1082
0
  auto txn = static_cast<WriteUnpreparedTxn*>(arg1);
1083
0
  auto iter = static_cast<Iterator*>(arg2);
1084
0
  txn->RemoveActiveIterator(iter);
1085
0
}
1086
}  // anonymous namespace
1087
1088
0
Iterator* WriteUnpreparedTxn::GetIterator(const ReadOptions& options) {
1089
0
  return GetIterator(options, wupt_db_->DefaultColumnFamily());
1090
0
}
1091
1092
Iterator* WriteUnpreparedTxn::GetIterator(const ReadOptions& options,
1093
0
                                          ColumnFamilyHandle* column_family) {
1094
  // Make sure to get iterator from WriteUnprepareTxnDB, not the root db.
1095
0
  Iterator* db_iter = wupt_db_->NewIterator(options, column_family, this);
1096
0
  assert(db_iter);
1097
1098
0
  auto iter =
1099
0
      write_batch_.NewIteratorWithBase(column_family, db_iter, &options);
1100
0
  active_iterators_.push_back(iter);
1101
0
  iter->RegisterCleanup(CleanupWriteUnpreparedWBWIIterator, this, iter);
1102
0
  return iter;
1103
0
}
1104
1105
Status WriteUnpreparedTxn::ValidateSnapshot(ColumnFamilyHandle* column_family,
1106
                                            const Slice& key,
1107
0
                                            SequenceNumber* tracked_at_seq) {
1108
  // TODO(lth): Reduce duplicate code with WritePrepared ValidateSnapshot logic.
1109
0
  assert(snapshot_);
1110
1111
0
  SequenceNumber min_uncommitted =
1112
0
      static_cast_with_check<const SnapshotImpl>(snapshot_.get())
1113
0
          ->min_uncommitted_;
1114
0
  SequenceNumber snap_seq = snapshot_->GetSequenceNumber();
1115
  // tracked_at_seq is either max or the last snapshot with which this key was
1116
  // trackeed so there is no need to apply the IsInSnapshot to this comparison
1117
  // here as tracked_at_seq is not a prepare seq.
1118
0
  if (*tracked_at_seq <= snap_seq) {
1119
    // If the key has been previous validated at a sequence number earlier
1120
    // than the curent snapshot's sequence number, we already know it has not
1121
    // been modified.
1122
0
    return Status::OK();
1123
0
  }
1124
1125
0
  *tracked_at_seq = snap_seq;
1126
1127
0
  ColumnFamilyHandle* cfh =
1128
0
      column_family ? column_family : db_impl_->DefaultColumnFamily();
1129
1130
0
  WriteUnpreparedTxnReadCallback snap_checker(
1131
0
      wupt_db_, snap_seq, min_uncommitted, unprep_seqs_, kBackedByDBSnapshot);
1132
  // TODO(yanqin): Support user-defined timestamp.
1133
0
  return TransactionUtil::CheckKeyForConflicts(
1134
0
      db_impl_, cfh, key.ToString(), snap_seq, /*ts=*/nullptr,
1135
0
      false /* cache_only */, &snap_checker, min_uncommitted,
1136
0
      txn_db_impl_->GetTxnDBOptions().enable_udt_validation);
1137
0
}
1138
1139
const std::map<SequenceNumber, size_t>&
1140
0
WriteUnpreparedTxn::GetUnpreparedSequenceNumbers() {
1141
0
  return unprep_seqs_;
1142
0
}
1143
1144
}  // namespace ROCKSDB_NAMESPACE