couchbase 3.4.0 → 3.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/couchbase/CMakeLists.txt +10 -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/ThirdPartyDependencies.cmake +4 -0
  8. data/ext/couchbase/cmake/VersionInfo.cmake +39 -3
  9. data/ext/couchbase/cmake/test_openssl.cxx +7 -0
  10. data/ext/couchbase/core/cluster_options.hxx +0 -1
  11. data/ext/couchbase/core/config_profile.cxx +23 -1
  12. data/ext/couchbase/core/config_profile.hxx +2 -12
  13. data/ext/couchbase/core/crypto/CMakeLists.txt +5 -1
  14. data/ext/couchbase/core/impl/analytics.cxx +236 -0
  15. data/ext/couchbase/core/impl/cluster.cxx +0 -1
  16. data/ext/couchbase/core/impl/collection_query_index_manager.cxx +3 -3
  17. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +5 -3
  18. data/ext/couchbase/core/impl/get_all_query_indexes.cxx +3 -3
  19. data/ext/couchbase/core/impl/query.cxx +5 -5
  20. data/ext/couchbase/core/impl/transaction_get_result.cxx +54 -0
  21. data/ext/couchbase/core/io/dns_client.cxx +225 -0
  22. data/ext/couchbase/core/io/dns_client.hxx +19 -188
  23. data/ext/couchbase/core/meta/CMakeLists.txt +7 -5
  24. data/ext/couchbase/core/meta/version.cxx +19 -0
  25. data/ext/couchbase/core/operations/document_search.cxx +5 -2
  26. data/ext/couchbase/core/operations/document_search.hxx +0 -1
  27. data/ext/couchbase/core/transactions/active_transaction_record.hxx +2 -2
  28. data/ext/couchbase/core/transactions/atr_cleanup_entry.cxx +1 -0
  29. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +65 -31
  30. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +44 -23
  31. data/ext/couchbase/core/transactions/forward_compat.hxx +2 -2
  32. data/ext/couchbase/core/transactions/internal/transaction_context.hxx +13 -13
  33. data/ext/couchbase/core/transactions/internal/transaction_fields.hxx +1 -0
  34. data/ext/couchbase/core/transactions/internal/transactions_cleanup.hxx +7 -1
  35. data/ext/couchbase/core/transactions/staged_mutation.cxx +1 -1
  36. data/ext/couchbase/core/transactions/staged_mutation.hxx +12 -2
  37. data/ext/couchbase/core/transactions/transaction_context.cxx +9 -11
  38. data/ext/couchbase/core/transactions/transaction_get_result.cxx +41 -31
  39. data/ext/couchbase/core/transactions/transaction_get_result.hxx +7 -3
  40. data/ext/couchbase/core/transactions/transaction_links.hxx +13 -1
  41. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +144 -155
  42. data/ext/couchbase/core/transactions/waitable_op_list.hxx +1 -0
  43. data/ext/couchbase/core/utils/connection_string.cxx +10 -3
  44. data/ext/couchbase/core/utils/connection_string.hxx +3 -3
  45. data/ext/couchbase/couchbase/analytics_error_context.hxx +143 -0
  46. data/ext/couchbase/couchbase/analytics_meta_data.hxx +155 -0
  47. data/ext/couchbase/couchbase/analytics_metrics.hxx +163 -0
  48. data/ext/couchbase/couchbase/analytics_options.hxx +359 -0
  49. data/ext/couchbase/couchbase/analytics_result.hxx +102 -0
  50. data/ext/couchbase/couchbase/analytics_scan_consistency.hxx +46 -0
  51. data/ext/couchbase/couchbase/analytics_status.hxx +41 -0
  52. data/ext/couchbase/couchbase/analytics_warning.hxx +85 -0
  53. data/ext/couchbase/couchbase/cluster.hxx +35 -2
  54. data/ext/couchbase/couchbase/cluster_options.hxx +10 -10
  55. data/ext/couchbase/couchbase/collection.hxx +22 -17
  56. data/ext/couchbase/couchbase/collection_query_index_manager.hxx +1 -1
  57. data/ext/couchbase/couchbase/common_options.hxx +1 -1
  58. data/ext/couchbase/couchbase/configuration_profile.hxx +1 -1
  59. data/ext/couchbase/couchbase/configuration_profiles_registry.hxx +0 -1
  60. data/ext/couchbase/couchbase/create_primary_query_index_options.hxx +1 -1
  61. data/ext/couchbase/couchbase/drop_primary_query_index_options.hxx +1 -1
  62. data/ext/couchbase/couchbase/drop_query_index_options.hxx +1 -1
  63. data/ext/couchbase/couchbase/fmt/analytics_status.hxx +76 -0
  64. data/ext/couchbase/couchbase/fmt/cas.hxx +12 -0
  65. data/ext/couchbase/couchbase/fmt/durability_level.hxx +6 -0
  66. data/ext/couchbase/couchbase/fmt/key_value_extended_error_info.hxx +6 -0
  67. data/ext/couchbase/couchbase/fmt/key_value_status_code.hxx +6 -0
  68. data/ext/couchbase/couchbase/fmt/mutation_token.hxx +6 -0
  69. data/ext/couchbase/couchbase/fmt/query_scan_consistency.hxx +6 -0
  70. data/ext/couchbase/couchbase/fmt/query_status.hxx +6 -0
  71. data/ext/couchbase/couchbase/fmt/retry_reason.hxx +6 -0
  72. data/ext/couchbase/couchbase/fmt/tls_verify_mode.hxx +6 -0
  73. data/ext/couchbase/couchbase/get_all_query_indexes_options.hxx +5 -4
  74. data/ext/couchbase/couchbase/query_index_manager.hxx +4 -2
  75. data/ext/couchbase/couchbase/query_options.hxx +0 -1
  76. data/ext/couchbase/couchbase/scope.hxx +34 -1
  77. data/ext/couchbase/couchbase/subdoc/array_add_unique.hxx +2 -0
  78. data/ext/couchbase/couchbase/subdoc/array_append.hxx +2 -0
  79. data/ext/couchbase/couchbase/subdoc/array_insert.hxx +2 -0
  80. data/ext/couchbase/couchbase/subdoc/array_prepend.hxx +2 -0
  81. data/ext/couchbase/couchbase/subdoc/count.hxx +2 -0
  82. data/ext/couchbase/couchbase/subdoc/counter.hxx +2 -0
  83. data/ext/couchbase/couchbase/subdoc/exists.hxx +2 -0
  84. data/ext/couchbase/couchbase/subdoc/get.hxx +2 -0
  85. data/ext/couchbase/couchbase/subdoc/insert.hxx +2 -0
  86. data/ext/couchbase/couchbase/subdoc/remove.hxx +2 -0
  87. data/ext/couchbase/couchbase/subdoc/replace.hxx +3 -1
  88. data/ext/couchbase/couchbase/subdoc/upsert.hxx +2 -0
  89. data/ext/couchbase/couchbase/transaction_op_error_context.hxx +4 -4
  90. data/ext/couchbase/couchbase/transactions/attempt_context.hxx +1 -1
  91. data/ext/couchbase/couchbase/transactions/transaction_get_result.hxx +36 -51
  92. data/ext/couchbase/couchbase/transactions/transactions_config.hxx +1 -1
  93. data/ext/couchbase/test/CMakeLists.txt +3 -2
  94. data/ext/couchbase/test/test_helper.hxx +1 -1
  95. data/ext/couchbase/test/test_integration_analytics.cxx +289 -13
  96. data/ext/couchbase/test/test_integration_crud.cxx +8 -1
  97. data/ext/couchbase/test/test_integration_examples.cxx +182 -0
  98. data/ext/couchbase/test/test_integration_management.cxx +15 -3
  99. data/ext/couchbase/test/test_integration_search.cxx +601 -0
  100. data/ext/couchbase/test/test_transaction_transaction_simple.cxx +73 -0
  101. data/ext/couchbase/test/test_unit_config_profiles.cxx +12 -12
  102. data/ext/couchbase/test/test_unit_connection_string.cxx +35 -0
  103. data/ext/couchbase/test/test_unit_transaction_utils.cxx +76 -19
  104. data/ext/couchbase/third_party/snappy/CMakeLists.txt +150 -27
  105. data/ext/couchbase/third_party/snappy/cmake/config.h.in +28 -24
  106. data/ext/couchbase/third_party/snappy/snappy-internal.h +189 -25
  107. data/ext/couchbase/third_party/snappy/snappy-sinksource.cc +26 -9
  108. data/ext/couchbase/third_party/snappy/snappy-sinksource.h +11 -11
  109. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.cc +1 -1
  110. data/ext/couchbase/third_party/snappy/snappy-stubs-internal.h +227 -308
  111. data/ext/couchbase/third_party/snappy/snappy-stubs-public.h.in +0 -11
  112. data/ext/couchbase/third_party/snappy/snappy.cc +1176 -410
  113. data/ext/couchbase/third_party/snappy/snappy.h +19 -4
  114. data/ext/couchbase.cxx +506 -26
  115. data/ext/extconf.rb +2 -1
  116. data/ext/revisions.rb +3 -2
  117. data/lib/couchbase/binary_collection.rb +4 -4
  118. data/lib/couchbase/cluster.rb +13 -9
  119. data/lib/couchbase/cluster_registry.rb +7 -2
  120. data/lib/couchbase/collection.rb +5 -0
  121. data/lib/couchbase/configuration.rb +3 -4
  122. data/lib/couchbase/errors.rb +10 -0
  123. data/lib/couchbase/management/collection_query_index_manager.rb +183 -0
  124. data/lib/couchbase/management/query_index_manager.rb +35 -3
  125. data/lib/couchbase/management.rb +1 -0
  126. data/lib/couchbase/options.rb +87 -5
  127. data/lib/couchbase/search_options.rb +158 -240
  128. data/lib/couchbase/version.rb +1 -1
  129. metadata +21 -6
  130. data/ext/couchbase/core/CMakeLists.txt +0 -0
@@ -174,6 +174,7 @@ atr_cleanup_entry::do_per_doc(std::vector<doc_record> docs,
174
174
  lookup_in_specs::get(ATR_ID).xattr(),
175
175
  lookup_in_specs::get(TRANSACTION_ID).xattr(),
176
176
  lookup_in_specs::get(ATTEMPT_ID).xattr(),
177
+ lookup_in_specs::get(OPERATION_ID).xattr(),
177
178
  lookup_in_specs::get(STAGED_DATA).xattr(),
178
179
  lookup_in_specs::get(ATR_BUCKET_NAME).xattr(),
179
180
  lookup_in_specs::get(ATR_SCOPE_NAME).xattr(),
@@ -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(
@@ -1139,6 +1147,9 @@ attempt_context_impl::get_with_query(const core::document_id& id, bool optional,
1139
1147
  // make a transaction_get_result from the row...
1140
1148
  try {
1141
1149
  if (resp.rows.empty()) {
1150
+ if (optional) {
1151
+ return op_completed_with_callback(std::move(cb), std::optional<transaction_get_result>());
1152
+ }
1142
1153
  return op_completed_with_error(
1143
1154
  std::move(cb), transaction_operation_failed(FAIL_DOC_NOT_FOUND, "document not found"));
1144
1155
  }
@@ -1905,10 +1916,10 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
1905
1916
  return fn(std::nullopt);
1906
1917
  case FAIL_AMBIGUOUS:
1907
1918
  // 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));
1919
+ CB_ATTEMPT_CTX_LOG_DEBUG(this, "got FAIL_AMBIGUOUS, retrying set atr pending", ec);
1920
+ return overall_.after_delay(std::chrono::milliseconds(1), [this, doc_id, &lock, fn = std::move(fn)]() {
1921
+ set_atr_pending_locked(doc_id, std::move(lock), std::move(fn));
1922
+ });
1912
1923
  case FAIL_TRANSIENT:
1913
1924
  // Retry txn
1914
1925
  return fn(err.retry());
@@ -1923,8 +1934,6 @@ attempt_context_impl::set_atr_pending_locked(const core::document_id& id, std::u
1923
1934
  }
1924
1935
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "updating atr {}", atr_id_.value());
1925
1936
 
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
1937
  std::chrono::nanoseconds remaining = overall_.remaining();
1929
1938
  // This bounds the value to [0-expirationTime]. It should always be in this range, this is just to protect
1930
1939
  // against the application clock changing.
@@ -2153,6 +2162,7 @@ attempt_context_impl::get_doc(
2153
2162
  lookup_in_specs::get(ATR_ID).xattr(),
2154
2163
  lookup_in_specs::get(TRANSACTION_ID).xattr(),
2155
2164
  lookup_in_specs::get(ATTEMPT_ID).xattr(),
2165
+ lookup_in_specs::get(OPERATION_ID).xattr(),
2156
2166
  lookup_in_specs::get(STAGED_DATA).xattr(),
2157
2167
  lookup_in_specs::get(ATR_BUCKET_NAME).xattr(),
2158
2168
  lookup_in_specs::get(ATR_SCOPE_NAME).xattr(),
@@ -2193,6 +2203,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2193
2203
  const std::vector<std::byte>& content,
2194
2204
  uint64_t cas,
2195
2205
  Delay&& delay,
2206
+ const std::string& op_id,
2196
2207
  Handler&& cb,
2197
2208
  error_class ec,
2198
2209
  const std::string& message)
@@ -2211,7 +2222,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2211
2222
  case FAIL_AMBIGUOUS:
2212
2223
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "FAIL_AMBIGUOUS in create_staged_insert, retrying");
2213
2224
  delay();
2214
- return create_staged_insert(id, content, cas, delay, std::forward<Handler>(cb));
2225
+ return create_staged_insert(id, content, cas, delay, op_id, std::forward<Handler>(cb));
2215
2226
  case FAIL_OTHER:
2216
2227
  return op_completed_with_error(std::forward<Handler>(cb), transaction_operation_failed(ec, "error in create_staged_insert"));
2217
2228
  case FAIL_HARD:
@@ -2221,7 +2232,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2221
2232
  case FAIL_CAS_MISMATCH: {
2222
2233
  // special handling for doc already existing
2223
2234
  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 {
2235
+ auto error_handler = [this, id, op_id, content](error_class ec2, const std::string& err_message, Handler&& cb) mutable {
2225
2236
  CB_ATTEMPT_CTX_LOG_TRACE(
2226
2237
  this, "after a CAS_MISMATCH or DOC_ALREADY_EXISTS, then got error {} in create_staged_insert", ec2);
2227
2238
  if (expiry_overtime_mode_.load()) {
@@ -2247,7 +2258,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2247
2258
  }
2248
2259
  return get_doc(
2249
2260
  id,
2250
- [this, id, content, cb = std::forward<Handler>(cb), error_handler, delay](
2261
+ [this, id, content, op_id, cb = std::forward<Handler>(cb), error_handler, delay](
2251
2262
  std::optional<error_class> ec3, std::optional<std::string> err_message, std::optional<transaction_get_result> doc) mutable {
2252
2263
  if (!ec3) {
2253
2264
  if (doc) {
@@ -2266,7 +2277,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2266
2277
  CB_ATTEMPT_CTX_LOG_DEBUG(
2267
2278
  this, "create staged insert found existing deleted doc, retrying with cas {}", doc->cas().value());
2268
2279
  delay();
2269
- return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
2280
+ return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
2270
2281
  }
2271
2282
  if (!doc->links().is_document_in_transaction()) {
2272
2283
  // doc was inserted outside txn elsewhere
@@ -2275,6 +2286,18 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2275
2286
  std::forward<Handler>(cb),
2276
2287
  document_exists({ couchbase::errc::transaction_op::document_exists_exception, key_value_error_context() }));
2277
2288
  }
2289
+ if (doc->links().staged_attempt_id() == this->id()) {
2290
+ if (doc->links().staged_operation_id() == op_id) {
2291
+ // this is us dealing with resolving an ambiguity. So, lets just update the staged_mutation with the
2292
+ // correct cas and continue...
2293
+ staged_mutations_->add(staged_mutation(*doc, content, staged_mutation_type::INSERT));
2294
+ return op_completed_with_callback(std::forward<Handler>(cb), doc);
2295
+ }
2296
+ return op_completed_with_error(
2297
+ std::forward<Handler>(cb),
2298
+ transaction_operation_failed(FAIL_OTHER, "concurrent operations on a document are not allowed")
2299
+ .cause(CONCURRENT_OPERATIONS_DETECTED_ON_SAME_DOCUMENT));
2300
+ }
2278
2301
  // CBD-3787 - Only a staged insert is ok to overwrite
2279
2302
  if (doc->links().op() && *doc->links().op() != "insert") {
2280
2303
  return op_completed_with_error(
@@ -2285,7 +2308,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2285
2308
  check_and_handle_blocking_transactions(
2286
2309
  *doc,
2287
2310
  forward_compat_stage::WWC_INSERTING,
2288
- [this, id, content, doc, cb = std::forward<Handler>(cb), delay](
2311
+ [this, id, op_id, content, doc, cb = std::forward<Handler>(cb), delay](
2289
2312
  std::optional<transaction_operation_failed> err) mutable {
2290
2313
  if (err) {
2291
2314
  return op_completed_with_error(std::move(cb), *err);
@@ -2293,7 +2316,7 @@ attempt_context_impl::create_staged_insert_error_handler(const core::document_id
2293
2316
  CB_ATTEMPT_CTX_LOG_DEBUG(
2294
2317
  this, "doc ok to overwrite, retrying create_staged_insert with cas {}", doc->cas().value());
2295
2318
  delay();
2296
- return create_staged_insert(id, content, doc->cas().value(), delay, std::forward<Handler>(cb));
2319
+ return create_staged_insert(id, content, doc->cas().value(), delay, op_id, std::forward<Handler>(cb));
2297
2320
  });
2298
2321
  } else {
2299
2322
  // no doc now, just retry entire txn
@@ -2320,20 +2343,27 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2320
2343
  const std::vector<std::byte>& content,
2321
2344
  uint64_t cas,
2322
2345
  Delay&& delay,
2346
+ const std::string& op_id,
2323
2347
  Handler&& cb)
2324
2348
  {
2325
2349
 
2326
2350
  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");
2351
+ return create_staged_insert_error_handler(id,
2352
+ content,
2353
+ cas,
2354
+ std::forward<Delay>(delay),
2355
+ op_id,
2356
+ std::forward<Handler>(cb),
2357
+ *ec,
2358
+ "create_staged_insert expired and not in overtime");
2329
2359
  }
2330
2360
 
2331
2361
  if (auto ec = hooks_.before_staged_insert(this, id.key()); ec) {
2332
2362
  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");
2363
+ id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, "before_staged_insert hook threw error");
2334
2364
  }
2335
2365
  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);
2366
+ auto req = create_staging_request(id, nullptr, "insert", op_id, content);
2337
2367
  req.access_deleted = true;
2338
2368
  req.create_as_deleted = true;
2339
2369
  req.cas = couchbase::cas(cas);
@@ -2341,11 +2371,13 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2341
2371
  wrap_durable_request(req, overall_.config());
2342
2372
  overall_.cluster_ref()->execute(
2343
2373
  req,
2344
- [this, id, content, cas, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
2374
+ [this, id, content, cas, op_id, cb = std::forward<Handler>(cb), delay = std::forward<Delay>(delay)](
2345
2375
  core::operations::mutate_in_response resp) mutable {
2346
- if (auto ec = hooks_.after_staged_insert_complete(this, id.key()); ec) {
2376
+ auto ec = resp.ctx.ec() ? error_class_from_response(resp) : hooks_.after_staged_insert_complete(this, id.key());
2377
+ if (ec) {
2378
+ auto msg = (resp.ctx.ec() ? resp.ctx.ec().message() : "after_staged_insert hook threw error");
2347
2379
  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");
2380
+ id, content, cas, std::forward<Delay>(delay), op_id, std::forward<Handler>(cb), *ec, msg);
2349
2381
  }
2350
2382
  if (!resp.ctx.ec()) {
2351
2383
  CB_ATTEMPT_CTX_LOG_DEBUG(this, "inserted doc {} CAS={}, {}", id, resp.cas.value(), resp.ctx.ec().message());
@@ -2357,6 +2389,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2357
2389
  id.collection(),
2358
2390
  overall_.transaction_id(),
2359
2391
  this->id(),
2392
+ op_id,
2360
2393
  content,
2361
2394
  std::nullopt,
2362
2395
  std::nullopt,
@@ -2373,6 +2406,7 @@ attempt_context_impl::create_staged_insert(const core::document_id& id,
2373
2406
  content,
2374
2407
  cas,
2375
2408
  std::forward<Delay>(delay),
2409
+ op_id,
2376
2410
  std::forward<Handler>(cb),
2377
2411
  error_class_from_response(resp).value(),
2378
2412
  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;
@@ -445,7 +456,7 @@ class attempt_context_impl
445
456
 
446
457
  void state(attempt_state s)
447
458
  {
448
- overall_.current_attempt().state = s;
459
+ overall_.current_attempt_state(s);
449
460
  }
450
461
 
451
462
  [[nodiscard]] const std::string atr_id()
@@ -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 {
@@ -49,30 +49,29 @@ class transaction_context
49
49
 
50
50
  [[nodiscard]] std::size_t num_attempts() const
51
51
  {
52
+ std::lock_guard<std::mutex> lock(mutex_);
52
53
  return attempts_.size();
53
54
  }
54
- [[nodiscard]] const std::vector<transaction_attempt>& attempts() const
55
- {
56
- return attempts_;
57
- }
58
- [[nodiscard]] std::vector<transaction_attempt>& attempts()
59
- {
60
- return const_cast<std::vector<transaction_attempt>&>(const_cast<const transaction_context*>(this)->attempts());
61
- }
62
55
  [[nodiscard]] const transaction_attempt& current_attempt() const
63
56
  {
57
+ std::lock_guard<std::mutex> lock(mutex_);
64
58
  if (attempts_.empty()) {
65
59
  throw std::runtime_error("transaction context has no attempts yet");
66
60
  }
67
61
  return attempts_.back();
68
62
  }
69
- [[nodiscard]] transaction_attempt& current_attempt()
70
- {
71
- return const_cast<transaction_attempt&>(const_cast<const transaction_context*>(this)->current_attempt());
72
- }
73
63
 
74
64
  void add_attempt();
75
65
 
66
+ void current_attempt_state(attempt_state s)
67
+ {
68
+ std::lock_guard<std::mutex> lock(mutex_);
69
+ if (attempts_.empty()) {
70
+ throw std::runtime_error("transaction_context has no attempts yet");
71
+ }
72
+ attempts_.back().state = s;
73
+ }
74
+
76
75
  [[nodiscard]] std::shared_ptr<core::cluster> cluster_ref()
77
76
  {
78
77
  return transactions_.cluster_ref();
@@ -90,7 +89,7 @@ class transaction_context
90
89
 
91
90
  [[nodiscard]] bool has_expired_client_side();
92
91
 
93
- void retry_delay();
92
+ void after_delay(std::chrono::milliseconds delay, std::function<void()> fn);
94
93
 
95
94
  [[nodiscard]] std::chrono::time_point<std::chrono::steady_clock> start_time_client() const
96
95
  {
@@ -191,6 +190,7 @@ class transaction_context
191
190
  std::string atr_collection_;
192
191
  transactions_cleanup& cleanup_;
193
192
  std::shared_ptr<attempt_context_impl> current_attempt_context_;
193
+ mutable std::mutex mutex_;
194
194
 
195
195
  std::unique_ptr<exp_delay> delay_;
196
196
  };
@@ -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
 
@@ -145,6 +145,12 @@ class transactions_cleanup
145
145
 
146
146
  void attempts_loop();
147
147
 
148
+ bool is_running()
149
+ {
150
+ std::unique_lock<std::mutex> lock(mutex_);
151
+ return running_;
152
+ }
153
+
148
154
  template<class R, class P>
149
155
  bool interruptable_wait(std::chrono::duration<R, P> time);
150
156
 
@@ -153,7 +159,7 @@ class transactions_cleanup
153
159
  void create_client_record(const couchbase::transactions::transaction_keyspace& keyspace);
154
160
  const atr_cleanup_stats handle_atr_cleanup(const core::document_id& atr_id,
155
161
  std::vector<transactions_cleanup_attempt>* result = nullptr);
156
- std::atomic<bool> running_{ false };
162
+ bool running_{ false };
157
163
  };
158
164
  } // namespace transactions
159
165
  } // namespace couchbase::core
@@ -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"
@@ -45,6 +45,7 @@ void
45
45
  transaction_context::add_attempt()
46
46
  {
47
47
  transaction_attempt attempt{};
48
+ std::lock_guard<std::mutex> lock(mutex_);
48
49
  attempts_.push_back(attempt);
49
50
  }
50
51
 
@@ -78,17 +79,14 @@ transaction_context::has_expired_client_side()
78
79
  }
79
80
 
80
81
  void
81
- transaction_context::retry_delay()
82
+ transaction_context::after_delay(std::chrono::milliseconds delay, std::function<void()> fn)
82
83
  {
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);
84
+ auto timer = std::make_shared<asio::steady_timer>(this->transactions_.cluster_ref()->io_context());
85
+ timer->expires_after(delay);
86
+ timer->async_wait([timer, fn](std::error_code) {
87
+ // have to always call the function, even if timer was canceled.
88
+ fn();
89
+ });
92
90
  }
93
91
 
94
92
  void