couchbase 3.4.0 → 3.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/ext/couchbase/CMakeLists.txt +8 -3
  4. data/ext/couchbase/cmake/CompilerWarnings.cmake +12 -4
  5. data/ext/couchbase/cmake/Documentation.cmake +4 -3
  6. data/ext/couchbase/cmake/OpenSSL.cmake +52 -7
  7. data/ext/couchbase/cmake/VersionInfo.cmake +39 -3
  8. data/ext/couchbase/cmake/test_openssl.cxx +7 -0
  9. data/ext/couchbase/core/crypto/CMakeLists.txt +5 -1
  10. data/ext/couchbase/core/impl/collection_query_index_manager.cxx +3 -3
  11. data/ext/couchbase/core/impl/get_all_query_indexes.cxx +3 -3
  12. data/ext/couchbase/core/impl/transaction_get_result.cxx +54 -0
  13. data/ext/couchbase/core/meta/CMakeLists.txt +7 -5
  14. data/ext/couchbase/core/meta/version.cxx +19 -0
  15. data/ext/couchbase/core/operations/document_search.cxx +5 -2
  16. data/ext/couchbase/core/operations/document_search.hxx +0 -1
  17. data/ext/couchbase/core/transactions/atr_cleanup_entry.cxx +1 -0
  18. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +62 -31
  19. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +43 -22
  20. data/ext/couchbase/core/transactions/forward_compat.hxx +2 -2
  21. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +1 -1
  22. data/ext/couchbase/core/transactions/internal/transaction_fields.hxx +1 -0
  23. data/ext/couchbase/core/transactions/staged_mutation.cxx +1 -1
  24. data/ext/couchbase/core/transactions/staged_mutation.hxx +12 -2
  25. data/ext/couchbase/core/transactions/transaction_context.cxx +8 -11
  26. data/ext/couchbase/core/transactions/transaction_get_result.cxx +41 -31
  27. data/ext/couchbase/core/transactions/transaction_get_result.hxx +7 -3
  28. data/ext/couchbase/core/transactions/transaction_links.hxx +13 -1
  29. data/ext/couchbase/core/transactions/waitable_op_list.hxx +1 -0
  30. data/ext/couchbase/couchbase/cluster.hxx +2 -2
  31. data/ext/couchbase/couchbase/cluster_options.hxx +10 -10
  32. data/ext/couchbase/couchbase/collection.hxx +22 -17
  33. data/ext/couchbase/couchbase/collection_query_index_manager.hxx +1 -1
  34. data/ext/couchbase/couchbase/common_options.hxx +1 -1
  35. data/ext/couchbase/couchbase/configuration_profile.hxx +1 -1
  36. data/ext/couchbase/couchbase/configuration_profiles_registry.hxx +0 -1
  37. data/ext/couchbase/couchbase/create_primary_query_index_options.hxx +1 -1
  38. data/ext/couchbase/couchbase/drop_primary_query_index_options.hxx +1 -1
  39. data/ext/couchbase/couchbase/drop_query_index_options.hxx +1 -1
  40. data/ext/couchbase/couchbase/fmt/cas.hxx +12 -0
  41. data/ext/couchbase/couchbase/fmt/durability_level.hxx +6 -0
  42. data/ext/couchbase/couchbase/fmt/key_value_extended_error_info.hxx +6 -0
  43. data/ext/couchbase/couchbase/fmt/key_value_status_code.hxx +6 -0
  44. data/ext/couchbase/couchbase/fmt/mutation_token.hxx +6 -0
  45. data/ext/couchbase/couchbase/fmt/query_scan_consistency.hxx +6 -0
  46. data/ext/couchbase/couchbase/fmt/query_status.hxx +6 -0
  47. data/ext/couchbase/couchbase/fmt/retry_reason.hxx +6 -0
  48. data/ext/couchbase/couchbase/fmt/tls_verify_mode.hxx +6 -0
  49. data/ext/couchbase/couchbase/get_all_query_indexes_options.hxx +5 -4
  50. data/ext/couchbase/couchbase/query_index_manager.hxx +4 -2
  51. data/ext/couchbase/couchbase/scope.hxx +1 -1
  52. data/ext/couchbase/couchbase/subdoc/array_add_unique.hxx +2 -0
  53. data/ext/couchbase/couchbase/subdoc/array_append.hxx +2 -0
  54. data/ext/couchbase/couchbase/subdoc/array_insert.hxx +2 -0
  55. data/ext/couchbase/couchbase/subdoc/array_prepend.hxx +2 -0
  56. data/ext/couchbase/couchbase/subdoc/count.hxx +2 -0
  57. data/ext/couchbase/couchbase/subdoc/counter.hxx +2 -0
  58. data/ext/couchbase/couchbase/subdoc/exists.hxx +2 -0
  59. data/ext/couchbase/couchbase/subdoc/get.hxx +2 -0
  60. data/ext/couchbase/couchbase/subdoc/insert.hxx +2 -0
  61. data/ext/couchbase/couchbase/subdoc/remove.hxx +2 -0
  62. data/ext/couchbase/couchbase/subdoc/replace.hxx +3 -1
  63. data/ext/couchbase/couchbase/subdoc/upsert.hxx +2 -0
  64. data/ext/couchbase/couchbase/transaction_op_error_context.hxx +4 -4
  65. data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +36 -51
  66. data/ext/couchbase/couchbase/transactions/transactions_config.hxx +1 -1
  67. data/ext/couchbase/test/CMakeLists.txt +2 -0
  68. data/ext/couchbase/test/test_integration_examples.cxx +141 -0
  69. data/ext/couchbase/test/test_unit_transaction_utils.cxx +76 -19
  70. data/ext/couchbase.cxx +479 -20
  71. data/ext/extconf.rb +2 -1
  72. data/ext/revisions.rb +3 -2
  73. data/lib/couchbase/binary_collection.rb +4 -4
  74. data/lib/couchbase/collection.rb +5 -0
  75. data/lib/couchbase/errors.rb +10 -0
  76. data/lib/couchbase/management/collection_query_index_manager.rb +183 -0
  77. data/lib/couchbase/management/query_index_manager.rb +35 -3
  78. data/lib/couchbase/management.rb +1 -0
  79. data/lib/couchbase/options.rb +2 -3
  80. data/lib/couchbase/version.rb +1 -1
  81. metadata +9 -5
@@ -243,6 +243,7 @@ core::operations::mutate_in_request
243
243
  attempt_context_impl::create_staging_request(const core::document_id& id,
244
244
  const transaction_get_result* document,
245
245
  const std::string type,
246
+ const std::string op_id,
246
247
  std::optional<std::vector<std::byte>> content)
247
248
  {
248
249
  core::operations::mutate_in_request req{ id };
@@ -250,6 +251,7 @@ attempt_context_impl::create_staging_request(const core::document_id& id,
250
251
  txn["id"] = tao::json::empty_object;
251
252
  txn["id"]["txn"] = transaction_id();
252
253
  txn["id"]["atmpt"] = this->id();
254
+ txn["id"]["op"] = op_id;
253
255
  txn["atr"] = tao::json::empty_object;
254
256
  txn["atr"]["id"] = atr_id();
255
257
  txn["atr"]["bkt"] = atr_id_->bucket();
@@ -296,6 +298,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
296
298
  return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, ec.message()));
297
299
  }
298
300
  try {
301
+ auto op_id = uid_generator::next();
299
302
  // a get can return a 'empty' doc, so check for that and short-circuit the eventual error that will occur...
300
303
  if (document.key().empty() || document.bucket().empty()) {
301
304
  return op_completed_with_error(std::move(cb),
@@ -321,7 +324,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
321
324
  check_and_handle_blocking_transactions(
322
325
  document,
323
326
  forward_compat_stage::WWC_REPLACING,
324
- [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), content](
327
+ [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), op_id, content](
325
328
  std::optional<transaction_operation_failed> e1) mutable {
326
329
  if (e1) {
327
330
  return op_completed_with_error(std::move(cb), *e1);
@@ -330,7 +333,7 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
330
333
  document_id{ document.id().bucket(), document.id().scope(), document.id().collection(), document.id().key() };
331
334
  select_atr_if_needed_unlocked(
332
335
  tmp_doc,
333
- [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), content](
336
+ [this, existing_sm = std::move(existing_sm), document = std::move(document), cb = std::move(cb), op_id, content](
334
337
  std::optional<transaction_operation_failed> e2) mutable {
335
338
  if (e2) {
336
339
  return op_completed_with_error(std::move(cb), *e2);
@@ -339,10 +342,10 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
339
342
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing INSERT of {} while replacing", document);
340
343
  exp_delay delay(
341
344
  std::chrono::milliseconds(5), std::chrono::milliseconds(300), overall_.config().expiration_time);
342
- create_staged_insert(document.id(), content, existing_sm->doc().cas().value(), delay, std::move(cb));
345
+ create_staged_insert(document.id(), content, existing_sm->doc().cas().value(), delay, op_id, std::move(cb));
343
346
  return;
344
347
  }
345
- create_staged_replace(document, content, std::move(cb));
348
+ create_staged_replace(document, content, op_id, std::move(cb));
346
349
  });
347
350
  });
348
351
  } catch (const client_error& e) {
@@ -361,9 +364,12 @@ attempt_context_impl::replace_raw(const transaction_get_result& document, const
361
364
 
362
365
  template<typename Handler>
363
366
  void
364
- attempt_context_impl::create_staged_replace(const transaction_get_result& document, const std::vector<std::byte>& content, Handler&& cb)
367
+ attempt_context_impl::create_staged_replace(const transaction_get_result& document,
368
+ const std::vector<std::byte>& content,
369
+ const std::string& op_id,
370
+ Handler&& cb)
365
371
  {
366
- auto req = create_staging_request(document.id(), &document, "replace", content);
372
+ auto req = create_staging_request(document.id(), &document, "replace", op_id, content);
367
373
  req.cas = document.cas();
368
374
  req.access_deleted = true;
369
375
  auto error_handler = [this](error_class ec, const std::string& msg, Handler&& cb) {
@@ -449,6 +455,7 @@ attempt_context_impl::insert_raw(const core::document_id& id, const std::vector<
449
455
  }
450
456
  try {
451
457
  check_if_done(cb);
458
+ auto op_id = uid_generator::next();
452
459
  staged_mutation* existing_sm = staged_mutations_->find_any(id);
453
460
  if ((existing_sm != nullptr) &&
454
461
  (existing_sm->type() == staged_mutation_type::INSERT || existing_sm->type() == staged_mutation_type::REPLACE)) {
@@ -462,17 +469,17 @@ attempt_context_impl::insert_raw(const core::document_id& id, const std::vector<
462
469
  transaction_operation_failed(FAIL_EXPIRY, "transaction expired").expired());
463
470
  }
464
471
  select_atr_if_needed_unlocked(
465
- id, [this, existing_sm, cb = std::move(cb), id, content](std::optional<transaction_operation_failed> err) mutable {
472
+ id, [this, existing_sm, cb = std::move(cb), id, op_id, content](std::optional<transaction_operation_failed> err) mutable {
466
473
  if (err) {
467
474
  return op_completed_with_error(std::move(cb), *err);
468
475
  }
469
476
  if (existing_sm != nullptr && existing_sm->type() == staged_mutation_type::REMOVE) {
470
477
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing remove of {} while inserting", id);
471
- return create_staged_replace(existing_sm->doc(), content, std::move(cb));
478
+ return create_staged_replace(existing_sm->doc(), content, op_id, std::move(cb));
472
479
  }
473
480
  uint64_t cas = 0;
474
481
  exp_delay delay(std::chrono::milliseconds(5), std::chrono::milliseconds(300), overall_.config().expiration_time);
475
- create_staged_insert(id, content, cas, delay, std::move(cb));
482
+ create_staged_insert(id, content, cas, delay, op_id, std::move(cb));
476
483
  });
477
484
  } catch (const std::exception& e) {
478
485
  return op_completed_with_error(std::move(cb), transaction_operation_failed(FAIL_OTHER, e.what()));
@@ -599,6 +606,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
599
606
  return error_handler(FAIL_EXPIRY, "transaction expired", std::move(cb));
600
607
  }
601
608
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "removing {}", document);
609
+ auto op_id = uid_generator::next();
602
610
  if (existing_sm != nullptr) {
603
611
  if (existing_sm->type() == staged_mutation_type::REMOVE) {
604
612
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing REMOVE of {} while removing", document);
@@ -616,7 +624,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
616
624
  check_and_handle_blocking_transactions(
617
625
  document,
618
626
  forward_compat_stage::WWC_REMOVING,
619
- [this, document = std::move(document), cb = std::move(cb), error_handler = std::move(error_handler)](
627
+ [this, document = std::move(document), cb = std::move(cb), op_id, error_handler = std::move(error_handler)](
620
628
  std::optional<transaction_operation_failed> err1) mutable {
621
629
  if (err1) {
622
630
  return op_completed_with_error(std::move(cb), *err1);
@@ -625,7 +633,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
625
633
  document_id{ document.id().bucket(), document.id().scope(), document.id().collection(), document.id().key() };
626
634
  select_atr_if_needed_unlocked(
627
635
  tmp_doc,
628
- [document = std::move(document), cb = std::move(cb), this, error_handler = std::move(error_handler)](
636
+ [document = std::move(document), cb = std::move(cb), this, op_id, error_handler = std::move(error_handler)](
629
637
  std::optional<transaction_operation_failed> err2) mutable {
630
638
  if (err2) {
631
639
  return op_completed_with_error(std::move(cb), *err2);
@@ -634,7 +642,7 @@ attempt_context_impl::remove(const transaction_get_result& document, VoidCallbac
634
642
  return error_handler(*ec, "before_staged_remove hook raised error", std::move(cb));
635
643
  }
636
644
  CB_ATTEMPT_CTX_LOG_TRACE(this, "about to remove doc {} with cas {}", document.id(), document.cas().value());
637
- auto req = create_staging_request(document.id(), &document, "remove");
645
+ auto req = create_staging_request(document.id(), &document, "remove", op_id);
638
646
  req.cas = document.cas();
639
647
  req.access_deleted = document.links().is_deleted();
640
648
  overall_.cluster_ref()->execute(
@@ -1905,10 +1913,10 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
1905
1913
  return fn(std::nullopt);
1906
1914
  case FAIL_AMBIGUOUS:
1907
1915
  // Retry just this
1908
- overall_.retry_delay();
1909
- // keep it locked!
1910
- CB_ATTEMPT_CTX_LOG_DEBUG(this, "got {}, retrying set atr pending", ec);
1911
- return set_atr_pending_locked(doc_id, std::move(lock), std::move(fn));
1916
+ CB_ATTEMPT_CTX_LOG_DEBUG(this, "got FAIL_AMBIGUOUS, retrying set atr pending", ec);
1917
+ return overall_.after_delay(std::chrono::milliseconds(1), [this, doc_id, &lock, fn = std::move(fn)]() {
1918
+ set_atr_pending_locked(doc_id, std::move(lock), std::move(fn));
1919
+ });
1912
1920
  case FAIL_TRANSIENT:
1913
1921
  // Retry txn
1914
1922
  return fn(err.retry());
@@ -1923,8 +1931,6 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
1923
1931
  }
1924
1932
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "updating atr {}", atr_id_.value());
1925
1933
 
1926
- // FIXME: do we need to capture "now" here?
1927
- // std::chrono::time_point<std::chrono::steady_clock> now = std::chrono::steady_clock::now();
1928
1934
  std::chrono::nanoseconds remaining = overall_.remaining();
1929
1935
  // This bounds the value to [0-expirationTime]. It should always be in this range, this is just to protect
1930
1936
  // against the application clock changing.
@@ -2153,6 +2159,7 @@ attempt_context_impl::get_doc(
2153
2159
  lookup_in_specs::get(ATR_ID).xattr(),
2154
2160
  lookup_in_specs::get(TRANSACTION_ID).xattr(),
2155
2161
  lookup_in_specs::get(ATTEMPT_ID).xattr(),
2162
+ lookup_in_specs::get(OPERATION_ID).xattr(),
2156
2163
  lookup_in_specs::get(STAGED_DATA).xattr(),
2157
2164
  lookup_in_specs::get(ATR_BUCKET_NAME).xattr(),
2158
2165
  lookup_in_specs::get(ATR_SCOPE_NAME).xattr(),
@@ -2193,6 +2200,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2193
2200
  const std::vector<std::byte>& content,
2194
2201
  uint64_t cas,
2195
2202
  Delay&& delay,
2203
+ const std::string& op_id,
2196
2204
  Handler&& cb,
2197
2205
  error_class ec,
2198
2206
  const std::string& message)
@@ -2211,7 +2219,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2211
2219
  case FAIL_AMBIGUOUS:
2212
2220
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "FAIL_AMBIGUOUS in create_staged_insert, retrying");
2213
2221
  delay();
2214
- return create_staged_insert(id, content, cas, delay, std::forward<Handler>(cb));
2222
+ return create_staged_insert(id, content, cas, delay, op_id, std::forward<Handler>(cb));
2215
2223
  case FAIL_OTHER:
2216
2224
  return op_completed_with_error(std::forward<Handler>(cb), transaction_operation_failed(ec, "error in create_staged_insert"));
2217
2225
  case FAIL_HARD:
@@ -2221,7 +2229,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2221
2229
  case FAIL_CAS_MISMATCH: {
2222
2230
  // special handling for doc already existing
2223
2231
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "found existing doc {}, may still be able to insert", id);
2224
- auto error_handler = [this, id, content](error_class ec2, const std::string& err_message, Handler&& cb) mutable {
2232
+ auto error_handler = [this, id, op_id, content](error_class ec2, const std::string& err_message, Handler&& cb) mutable {
2225
2233
  CB_ATTEMPT_CTX_LOG_TRACE(
2226
2234
  this, "after a CAS_MISMATCH or DOC_ALREADY_EXISTS, then got error {} in create_staged_insert", ec2);
2227
2235
  if (expiry_overtime_mode_.load()) {
@@ -2247,7 +2255,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2247
2255
  }
2248
2256
  return get_doc(
2249
2257
  id,
2250
- [this, id, content, cb = std::forward<Handler>(cb), error_handler, delay](
2258
+ [this, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
2251
2259
  std::optional<error_class> ec3, std::optional<std::string> err_message, std::optional<transaction_get_result> doc) mutable {
2252
2260
  if (!ec3) {
2253
2261
  if (doc) {
@@ -2266,7 +2274,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2266
2274
  CB_ATTEMPT_CTX_LOG_DEBUG(
2267
2275
  this, "create staged insert found existing deleted doc, retrying with cas {}", doc->cas().value());
2268
2276
  delay();
2269
- return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
2277
+ return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
2270
2278
  }
2271
2279
  if (!doc->links().is_document_in_transaction()) {
2272
2280
  // doc was inserted outside txn elsewhere
@@ -2275,6 +2283,18 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2275
2283
  std::forward<Handler>(cb),
2276
2284
  document_exists({ couchbase::errc::transaction_op::document_exists_exception, key_value_error_context() }));
2277
2285
  }
2286
+ if (doc->links().staged_attempt_id() == this->id()) {
2287
+ if (doc->links().staged_operation_id() == op_id) {
2288
+ // this is us dealing with resolving an ambiguity. So, lets just update the staged_mutation with the
2289
+ // correct cas and continue...
2290
+ staged_mutations_->add(staged_mutation(*doc, content, staged_mutation_type::INSERT));
2291
+ return op_completed_with_callback(std::forward<Handler>(cb), doc);
2292
+ }
2293
+ return op_completed_with_error(
2294
+ std::forward<Handler>(cb),
2295
+ transaction_operation_failed(FAIL_OTHER, "concurrent operations on a document are not allowed")
2296
+ .cause(CONCURRENT_OPERATIONS_DETECTED_ON_SAME_DOCUMENT));
2297
+ }
2278
2298
  // CBD-3787 - Only a staged insert is ok to overwrite
2279
2299
  if (doc->links().op() && *doc->links().op() != "insert") {
2280
2300
  return op_completed_with_error(
@@ -2285,7 +2305,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2285
2305
  check_and_handle_blocking_transactions(
2286
2306
  *doc,
2287
2307
  forward_compat_stage::WWC_INSERTING,
2288
- [this, id, content, doc, cb = std::forward<Handler>(cb), delay](
2308
+ [this, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
2289
2309
  std::optional<transaction_operation_failed> err) mutable {
2290
2310
  if (err) {
2291
2311
  return op_completed_with_error(std::move(cb), *err);
@@ -2293,7 +2313,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2293
2313
  CB_ATTEMPT_CTX_LOG_DEBUG(
2294
2314
  this, "doc ok to overwrite, retrying create_staged_insert with cas {}", doc->cas().value());
2295
2315
  delay();
2296
- return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
2316
+ return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
2297
2317
  });
2298
2318
  } else {
2299
2319
  // no doc now, just retry entire txn
@@ -2320,20 +2340,27 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2320
2340
  const std::vector<std::byte>& content,
2321
2341
  uint64_t cas,
2322
2342
  Delay&& delay,
2343
+ const std::string& op_id,
2323
2344
  Handler&& cb)
2324
2345
  {
2325
2346
 
2326
2347
  if (auto ec = error_if_expired_and_not_in_overtime(STAGE_CREATE_STAGED_INSERT, id.key()); ec) {
2327
- return create_staged_insert_error_handler(
2328
- id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec, "create_staged_insert expired and not in overtime");
2348
+ return create_staged_insert_error_handler(id,
2349
+ content,
2350
+ cas,
2351
+ std::forward<Delay>(delay),
2352
+ op_id,
2353
+ std::forward<Handler>(cb),
2354
+ *ec,
2355
+ "create_staged_insert expired and not in overtime");
2329
2356
  }
2330
2357
 
2331
2358
  if (auto ec = hooks_.before_staged_insert(this, id.key()); ec) {
2332
2359
  return create_staged_insert_error_handler(
2333
- id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
2360
+ id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
2334
2361
  }
2335
2362
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "about to insert staged doc {} with cas {}", id, cas);
2336
- auto req = create_staging_request(id, nullptr, "insert", content);
2363
+ auto req = create_staging_request(id, nullptr, "insert", op_id, content);
2337
2364
  req.access_deleted = true;
2338
2365
  req.create_as_deleted = true;
2339
2366
  req.cas = couchbase::cas(cas);
@@ -2341,11 +2368,13 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2341
2368
  wrap_durable_request(req, overall_.config());
2342
2369
  overall_.cluster_ref()->execute(
2343
2370
  req,
2344
- [this, id, content, cas, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
2371
+ [this, id, content, cas, op_id, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
2345
2372
  core::operations::mutate_in_response resp) mutable {
2346
- if (auto ec = hooks_.after_staged_insert_complete(this, id.key()); ec) {
2373
+ auto ec = resp.ctx.ec() ? error_class_from_response(resp) : hooks_.after_staged_insert_complete(this, id.key());
2374
+ if (ec) {
2375
+ auto msg = (resp.ctx.ec() ? resp.ctx.ec().message() : "after_staged_insert hook threw error");
2347
2376
  return create_staged_insert_error_handler(
2348
- id, content, cas, std::forward<Delay>(delay), std::forward<Handler>(cb), *ec, "after_staged_insert hook threw error");
2377
+ id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, msg);
2349
2378
  }
2350
2379
  if (!resp.ctx.ec()) {
2351
2380
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "inserted doc {} CAS={}, {}", id, resp.cas.value(), resp.ctx.ec().message());
@@ -2357,6 +2386,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2357
2386
  id.collection(),
2358
2387
  overall_.transaction_id(),
2359
2388
  this->id(),
2389
+ op_id,
2360
2390
  content,
2361
2391
  std::nullopt,
2362
2392
  std::nullopt,
@@ -2373,6 +2403,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2373
2403
  content,
2374
2404
  cas,
2375
2405
  std::forward<Delay>(delay),
2406
+ op_id,
2376
2407
  std::forward<Handler>(cb),
2377
2408
  error_class_from_response(resp).value(),
2378
2409
  resp.ctx.ec().message());
@@ -301,11 +301,23 @@ class attempt_context_impl
301
301
  existing_error();
302
302
  return func();
303
303
  } catch (const async_operation_conflict& e) {
304
- // can't do anything here but log and eat it.
305
304
  CB_ATTEMPT_CTX_LOG_ERROR(this, "Attempted to perform txn operation after commit/rollback started: {}", e.what());
306
305
  // you cannot call op_completed_with_error, as it tries to decrement
307
306
  // the op count, however it didn't successfully increment it, so...
308
- op_completed_with_error_no_cache(std::move(cb), std::current_exception());
307
+ auto err = transaction_operation_failed(FAIL_OTHER, "async operation conflict");
308
+ switch (state()) {
309
+ case attempt_state::ABORTED:
310
+ case attempt_state::ROLLED_BACK:
311
+ err.cause(TRANSACTION_ALREADY_ABORTED);
312
+ break;
313
+ case attempt_state::COMMITTED:
314
+ case attempt_state::COMPLETED:
315
+ err.cause(TRANSACTION_ALREADY_COMMITTED);
316
+ break;
317
+ default:
318
+ err.cause(UNKNOWN);
319
+ }
320
+ op_completed_with_error_no_cache(std::move(cb), std::make_exception_ptr(err));
309
321
  } catch (const transaction_operation_failed& e) {
310
322
  // thrown only from call_func when previous error exists, so eat it, unless
311
323
  // it has PREVIOUS_OP_FAILED cause
@@ -389,26 +401,25 @@ class attempt_context_impl
389
401
  std::optional<std::string> query_context,
390
402
  couchbase::transactions::async_query_handler&& handler) override
391
403
  {
392
- query(
393
- statement,
394
- opts,
395
- query_context,
396
- [handler = std::move(handler)](std::exception_ptr err, std::optional<core::operations::query_response> resp) {
397
- if (err) {
398
- try {
399
- std::rethrow_exception(err);
400
- } catch (const transaction_operation_failed& e) {
401
- return handler(e.get_error_ctx(), {});
402
- } catch (const op_exception& ex) {
403
- return handler(ex.ctx(), {});
404
- } catch (...) {
405
- // just in case...
406
- return handler(transaction_op_error_context(couchbase::errc::transaction_op::unknown), {});
404
+ query(statement,
405
+ opts,
406
+ query_context,
407
+ [handler = std::move(handler)](std::exception_ptr err, std::optional<core::operations::query_response> resp) {
408
+ if (err) {
409
+ try {
410
+ std::rethrow_exception(err);
411
+ } catch (const transaction_operation_failed& e) {
412
+ return handler(e.get_error_ctx(), {});
413
+ } catch (const op_exception& ex) {
414
+ return handler(ex.ctx(), {});
415
+ } catch (...) {
416
+ // just in case...
417
+ return handler(transaction_op_error_context(couchbase::errc::transaction_op::unknown), {});
418
+ }
407
419
  }
408
- }
409
- auto [ctx, res] = core::impl::build_transaction_query_result(*resp);
410
- handler(ctx, res);
411
- });
420
+ auto [ctx, res] = core::impl::build_transaction_query_result(*resp);
421
+ handler(ctx, res);
422
+ });
412
423
  }
413
424
 
414
425
  void commit() override;
@@ -514,6 +525,7 @@ class attempt_context_impl
514
525
  core::operations::mutate_in_request create_staging_request(const core::document_id& in,
515
526
  const transaction_get_result* document,
516
527
  const std::string type,
528
+ const std::string op_id,
517
529
  std::optional<std::vector<std::byte>> content = std::nullopt);
518
530
 
519
531
  template<typename Handler, typename Delay>
@@ -521,16 +533,21 @@ class attempt_context_impl
521
533
  const std::vector<std::byte>& content,
522
534
  uint64_t cas,
523
535
  Delay&& delay,
536
+ const std::string& op_id,
524
537
  Handler&& cb);
525
538
 
526
539
  template<typename Handler>
527
- void create_staged_replace(const transaction_get_result& document, const std::vector<std::byte>& content, Handler&& cb);
540
+ void create_staged_replace(const transaction_get_result& document,
541
+ const std::vector<std::byte>& content,
542
+ const std::string& op_id,
543
+ Handler&& cb);
528
544
 
529
545
  template<typename Handler, typename Delay>
530
546
  void create_staged_insert_error_handler(const core::document_id& id,
531
547
  const std::vector<std::byte>& content,
532
548
  uint64_t cas,
533
549
  Delay&& delay,
550
+ const std::string& op_id,
534
551
  Handler&& cb,
535
552
  error_class ec,
536
553
  const std::string& message);
@@ -601,6 +618,10 @@ class attempt_context_impl
601
618
 
602
619
  void ensure_open_bucket(std::string bucket_name, std::function<void(std::error_code)>&& handler)
603
620
  {
621
+ if (bucket_name.empty()) {
622
+ CB_LOG_DEBUG("ensure_open_bucket called with empty bucket_name");
623
+ return handler(couchbase::errc::common::bucket_not_found);
624
+ }
604
625
  cluster_ref()->open_bucket(bucket_name, [handler = std::move(handler)](std::error_code ec) { handler(ec); });
605
626
  }
606
627
  };
@@ -122,8 +122,8 @@ struct forward_compat_behavior_full {
122
122
  struct forward_compat_supported {
123
123
  uint32_t protocol_major = 2;
124
124
  uint32_t protocol_minor = 0;
125
- std::list<std::string> extensions{ "TI", "MO", "BM", "QU", "SD", "BF3787", "BF3705", "BF3838",
126
- "RC", "UA", "CO", "BF3791", "CM", "SI", "QC", "IX" };
125
+ std::list<std::string> extensions{ "TI", "MO", "BM", "QU", "SD", "BF3787", "BF3705", "BF3838", "RC",
126
+ "UA", "CO", "BF3791", "CM", "SI", "QC", "IX", "TS" };
127
127
  };
128
128
 
129
129
  struct forward_compat_requirement {
@@ -90,7 +90,7 @@ class transaction_context
90
90
 
91
91
  [[nodiscard]] bool has_expired_client_side();
92
92
 
93
- void retry_delay();
93
+ void after_delay(std::chrono::milliseconds delay, std::function<void()> fn);
94
94
 
95
95
  [[nodiscard]] std::chrono::time_point<std::chrono::steady_clock> start_time_client() const
96
96
  {
@@ -50,6 +50,7 @@ static const std::string TRANSACTION_RESTORE_PREFIX_ONLY = TRANSACTION_INTERFACE
50
50
  static const std::string TRANSACTION_RESTORE_PREFIX = TRANSACTION_RESTORE_PREFIX_ONLY + ".";
51
51
  static const std::string TRANSACTION_ID = TRANSACTION_INTERFACE_PREFIX + "id.txn";
52
52
  static const std::string ATTEMPT_ID = TRANSACTION_INTERFACE_PREFIX + "id.atmpt";
53
+ static const std::string OPERATION_ID = TRANSACTION_INTERFACE_PREFIX + "id.op";
53
54
  static const std::string ATR_ID = TRANSACTION_INTERFACE_PREFIX + "atr.id";
54
55
  static const std::string ATR_BUCKET_NAME = TRANSACTION_INTERFACE_PREFIX + "atr.bkt";
55
56
 
@@ -315,7 +315,7 @@ staged_mutation_queue::commit_doc(attempt_context_impl* ctx, staged_mutation& it
315
315
 
316
316
  result res;
317
317
  if (item.type() == staged_mutation_type::INSERT && !cas_zero_mode) {
318
- core::operations::insert_request req{ item.doc().id(), item.doc().content() };
318
+ core::operations::insert_request req{ item.doc().id(), item.content() };
319
319
  req.flags = couchbase::codec::codec_flags::json_common_flags;
320
320
  wrap_durable_request(req, ctx->overall_.config());
321
321
  auto barrier = std::make_shared<std::promise<result>>();
@@ -17,9 +17,9 @@
17
17
  #pragma once
18
18
 
19
19
  #include "attempt_context_impl.hxx"
20
-
21
20
  #include "internal/utils.hxx"
22
21
  #include "transaction_get_result.hxx"
22
+ #include "uid_generator.hxx"
23
23
 
24
24
  #include <mutex>
25
25
  #include <string>
@@ -35,13 +35,18 @@ class staged_mutation
35
35
  transaction_get_result doc_;
36
36
  staged_mutation_type type_;
37
37
  std::vector<std::byte> content_;
38
+ std::string operation_id_;
38
39
 
39
40
  public:
40
41
  template<typename Content>
41
- staged_mutation(transaction_get_result& doc, Content content, staged_mutation_type type)
42
+ staged_mutation(transaction_get_result& doc,
43
+ Content content,
44
+ staged_mutation_type type,
45
+ std::string operation_id = uid_generator::next())
42
46
  : doc_(doc)
43
47
  , type_(type)
44
48
  , content_(std::move(content))
49
+ , operation_id_(std::move(operation_id))
45
50
  {
46
51
  }
47
52
 
@@ -97,6 +102,11 @@ class staged_mutation
97
102
  }
98
103
  throw std::runtime_error("unknown type of staged mutation");
99
104
  }
105
+
106
+ [[nodiscard]] const std::string& operation_id() const
107
+ {
108
+ return operation_id_;
109
+ }
100
110
  };
101
111
 
102
112
  class staged_mutation_queue
@@ -17,7 +17,7 @@
17
17
  #include "attempt_context_impl.hxx"
18
18
  #include "uid_generator.hxx"
19
19
  #include <asio/post.hpp>
20
- #include <core/cluster.hxx>
20
+ #include <asio/steady_timer.hpp>
21
21
 
22
22
  #include "internal/logging.hxx"
23
23
  #include "internal/transaction_context.hxx"
@@ -78,17 +78,14 @@ transaction_context::has_expired_client_side()
78
78
  }
79
79
 
80
80
  void
81
- transaction_context::retry_delay()
81
+ transaction_context::after_delay(std::chrono::milliseconds delay, std::function<void()> fn)
82
82
  {
83
- // when we retry an operation, we typically call that function recursively. So, we need to
84
- // limit total number of times we do it. CXXCBC-263 will address this, and no longer make the
85
- // recursive calls that lead to hacks like this. No way to know how many calls will blow up the
86
- // stack, so using 50 in hopes that makes jenkins happy till we do this better.
87
- constexpr auto arbitrary_factor = 50;
88
- auto delay = config_.expiration_time / arbitrary_factor;
89
- CB_ATTEMPT_CTX_LOG_TRACE(
90
- current_attempt_context_, "about to sleep for {} ms", std::chrono::duration_cast<std::chrono::milliseconds>(delay).count());
91
- std::this_thread::sleep_for(delay);
83
+ auto timer = std::make_shared<asio::steady_timer>(this->transactions_.cluster_ref()->io_context());
84
+ timer->expires_after(delay);
85
+ timer->async_wait([timer, fn](std::error_code) {
86
+ // have to always call the function, even if timer was canceled.
87
+ fn();
88
+ });
92
89
  }
93
90
 
94
91
  void