couchbase 3.4.3 → 3.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/couchbase/CMakeLists.txt +15 -1
  4. data/ext/couchbase/core/bucket.cxx +183 -152
  5. data/ext/couchbase/core/bucket.hxx +17 -4
  6. data/ext/couchbase/core/cluster.hxx +34 -13
  7. data/ext/couchbase/core/cluster_options.hxx +3 -0
  8. data/ext/couchbase/core/crud_component.cxx +51 -22
  9. data/ext/couchbase/core/error_context/key_value.cxx +2 -1
  10. data/ext/couchbase/core/error_context/key_value.hxx +10 -12
  11. data/ext/couchbase/core/impl/build_deferred_query_indexes.cxx +115 -50
  12. data/ext/couchbase/core/impl/cluster.cxx +6 -0
  13. data/ext/couchbase/core/impl/create_bucket.cxx +155 -0
  14. data/ext/couchbase/core/impl/create_query_index.cxx +172 -59
  15. data/ext/couchbase/core/impl/dns_srv_tracker.cxx +2 -1
  16. data/ext/couchbase/core/impl/drop_bucket.cxx +66 -0
  17. data/ext/couchbase/core/impl/drop_query_index.cxx +138 -59
  18. data/ext/couchbase/core/impl/flush_bucket.cxx +66 -0
  19. data/ext/couchbase/core/impl/get_all_buckets.cxx +163 -0
  20. data/ext/couchbase/core/impl/get_all_query_indexes.cxx +67 -37
  21. data/ext/couchbase/core/impl/get_bucket.cxx +153 -0
  22. data/ext/couchbase/core/impl/internal_manager_error_context.cxx +113 -0
  23. data/ext/couchbase/core/impl/internal_manager_error_context.hxx +60 -0
  24. data/ext/couchbase/core/impl/key_value_error_category.cxx +2 -4
  25. data/ext/couchbase/core/impl/key_value_error_context.cxx +98 -0
  26. data/ext/couchbase/core/impl/lookup_in.cxx +1 -0
  27. data/ext/couchbase/core/impl/lookup_in_all_replicas.cxx +176 -0
  28. data/ext/couchbase/core/impl/lookup_in_all_replicas.hxx +80 -0
  29. data/ext/couchbase/core/impl/lookup_in_any_replica.cxx +167 -0
  30. data/ext/couchbase/core/impl/lookup_in_any_replica.hxx +75 -0
  31. data/ext/couchbase/core/impl/lookup_in_replica.cxx +97 -0
  32. data/ext/couchbase/core/impl/lookup_in_replica.hxx +67 -0
  33. data/ext/couchbase/core/impl/manager_error_context.cxx +100 -0
  34. data/ext/couchbase/core/impl/query.cxx +1 -0
  35. data/ext/couchbase/core/impl/query_error_context.cxx +75 -0
  36. data/ext/couchbase/core/impl/update_bucket.cxx +130 -0
  37. data/ext/couchbase/core/impl/watch_query_indexes.cxx +53 -29
  38. data/ext/couchbase/core/io/dns_client.cxx +111 -40
  39. data/ext/couchbase/core/io/dns_config.cxx +5 -4
  40. data/ext/couchbase/core/io/http_session.hxx +24 -1
  41. data/ext/couchbase/core/io/mcbp_command.hxx +9 -2
  42. data/ext/couchbase/core/io/mcbp_session.cxx +80 -43
  43. data/ext/couchbase/core/io/mcbp_session.hxx +4 -3
  44. data/ext/couchbase/core/logger/custom_rotating_file_sink.cxx +1 -1
  45. data/ext/couchbase/core/logger/logger.cxx +80 -20
  46. data/ext/couchbase/core/logger/logger.hxx +31 -0
  47. data/ext/couchbase/core/meta/features.hxx +25 -0
  48. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +192 -0
  49. data/ext/couchbase/core/operations/document_lookup_in_any_replica.hxx +188 -0
  50. data/ext/couchbase/core/operations/document_query.cxx +11 -0
  51. data/ext/couchbase/core/operations/document_query.hxx +1 -0
  52. data/ext/couchbase/core/operations.hxx +2 -0
  53. data/ext/couchbase/core/origin.cxx +270 -0
  54. data/ext/couchbase/core/origin.hxx +2 -0
  55. data/ext/couchbase/core/protocol/client_response.hxx +1 -0
  56. data/ext/couchbase/core/protocol/cmd_hello.hxx +1 -0
  57. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.cxx +107 -0
  58. data/ext/couchbase/core/protocol/cmd_lookup_in_replica.hxx +137 -0
  59. data/ext/couchbase/core/protocol/hello_feature.hxx +6 -0
  60. data/ext/couchbase/core/protocol/hello_feature_fmt.hxx +3 -0
  61. data/ext/couchbase/core/protocol/status.cxx +2 -2
  62. data/ext/couchbase/core/range_scan_options.cxx +3 -27
  63. data/ext/couchbase/core/range_scan_options.hxx +13 -17
  64. data/ext/couchbase/core/range_scan_orchestrator.cxx +388 -170
  65. data/ext/couchbase/core/range_scan_orchestrator.hxx +13 -2
  66. data/ext/couchbase/core/range_scan_orchestrator_options.hxx +5 -3
  67. data/ext/couchbase/core/scan_options.hxx +0 -19
  68. data/ext/couchbase/core/scan_result.cxx +19 -5
  69. data/ext/couchbase/core/scan_result.hxx +5 -2
  70. data/ext/couchbase/core/timeout_defaults.hxx +2 -3
  71. data/ext/couchbase/core/topology/capabilities.hxx +3 -0
  72. data/ext/couchbase/core/topology/capabilities_fmt.hxx +8 -0
  73. data/ext/couchbase/core/topology/collections_manifest_fmt.hxx +1 -1
  74. data/ext/couchbase/core/topology/configuration.hxx +15 -0
  75. data/ext/couchbase/core/topology/configuration_json.hxx +6 -1
  76. data/ext/couchbase/core/utils/connection_string.cxx +62 -47
  77. data/ext/couchbase/core/utils/connection_string.hxx +1 -0
  78. data/ext/couchbase/couchbase/analytics_error_context.hxx +1 -1
  79. data/ext/couchbase/couchbase/behavior_options.hxx +19 -2
  80. data/ext/couchbase/couchbase/bucket_manager.hxx +135 -0
  81. data/ext/couchbase/couchbase/build_query_index_options.hxx +0 -30
  82. data/ext/couchbase/couchbase/cluster.hxx +14 -0
  83. data/ext/couchbase/couchbase/collection.hxx +111 -0
  84. data/ext/couchbase/couchbase/collection_query_index_manager.hxx +7 -48
  85. data/ext/couchbase/couchbase/create_bucket_options.hxx +41 -0
  86. data/ext/couchbase/couchbase/create_primary_query_index_options.hxx +0 -29
  87. data/ext/couchbase/couchbase/create_query_index_options.hxx +0 -33
  88. data/ext/couchbase/couchbase/drop_bucket_options.hxx +41 -0
  89. data/ext/couchbase/couchbase/drop_primary_query_index_options.hxx +0 -30
  90. data/ext/couchbase/couchbase/drop_query_index_options.hxx +0 -31
  91. data/ext/couchbase/couchbase/error_codes.hxx +1 -2
  92. data/ext/couchbase/couchbase/error_context.hxx +10 -2
  93. data/ext/couchbase/couchbase/flush_bucket_options.hxx +41 -0
  94. data/ext/couchbase/{core/topology/error_map_fmt.hxx → couchbase/fmt/key_value_error_map_attribute.hxx} +21 -21
  95. data/ext/couchbase/couchbase/get_all_buckets_options.hxx +44 -0
  96. data/ext/couchbase/couchbase/get_all_query_indexes_options.hxx +0 -30
  97. data/ext/couchbase/couchbase/get_and_lock_options.hxx +2 -2
  98. data/ext/couchbase/couchbase/get_and_touch_options.hxx +2 -2
  99. data/ext/couchbase/couchbase/get_bucket_options.hxx +43 -0
  100. data/ext/couchbase/couchbase/get_options.hxx +2 -2
  101. data/ext/couchbase/couchbase/insert_options.hxx +3 -3
  102. data/ext/couchbase/couchbase/key_value_error_context.hxx +7 -2
  103. data/ext/couchbase/couchbase/lookup_in_all_replicas_options.hxx +109 -0
  104. data/ext/couchbase/couchbase/lookup_in_any_replica_options.hxx +101 -0
  105. data/ext/couchbase/couchbase/lookup_in_options.hxx +2 -2
  106. data/ext/couchbase/couchbase/lookup_in_replica_result.hxx +74 -0
  107. data/ext/couchbase/couchbase/lookup_in_result.hxx +26 -0
  108. data/ext/couchbase/couchbase/management/bucket_settings.hxx +116 -0
  109. data/ext/couchbase/couchbase/manager_error_context.hxx +29 -53
  110. data/ext/couchbase/couchbase/mutate_in_options.hxx +2 -2
  111. data/ext/couchbase/couchbase/query_error_context.hxx +3 -1
  112. data/ext/couchbase/couchbase/query_index_manager.hxx +16 -83
  113. data/ext/couchbase/couchbase/query_options.hxx +18 -0
  114. data/ext/couchbase/couchbase/remove_options.hxx +2 -2
  115. data/ext/couchbase/couchbase/replace_options.hxx +3 -3
  116. data/ext/couchbase/couchbase/security_options.hxx +15 -0
  117. data/ext/couchbase/couchbase/subdocument_error_context.hxx +4 -2
  118. data/ext/couchbase/couchbase/touch_options.hxx +2 -2
  119. data/ext/couchbase/couchbase/unlock_options.hxx +2 -2
  120. data/ext/couchbase/couchbase/update_bucket_options.hxx +41 -0
  121. data/ext/couchbase/couchbase/upsert_options.hxx +3 -3
  122. data/ext/couchbase/couchbase/watch_query_indexes_options.hxx +0 -31
  123. data/ext/couchbase/test/CMakeLists.txt +1 -0
  124. data/ext/couchbase/test/test_integration_collections.cxx +6 -0
  125. data/ext/couchbase/test/test_integration_crud.cxx +5 -0
  126. data/ext/couchbase/test/test_integration_examples.cxx +137 -1
  127. data/ext/couchbase/test/test_integration_management.cxx +709 -266
  128. data/ext/couchbase/test/test_integration_query.cxx +19 -7
  129. data/ext/couchbase/test/test_integration_range_scan.cxx +351 -112
  130. data/ext/couchbase/test/test_integration_search.cxx +10 -1
  131. data/ext/couchbase/test/test_integration_subdoc.cxx +655 -0
  132. data/ext/couchbase/test/test_transaction_public_async_api.cxx +13 -12
  133. data/ext/couchbase/test/test_transaction_public_blocking_api.cxx +27 -21
  134. data/ext/couchbase/test/test_unit_connection_string.cxx +29 -0
  135. data/ext/couchbase/test/test_unit_query.cxx +75 -0
  136. data/ext/couchbase.cxx +583 -29
  137. data/ext/revisions.rb +3 -3
  138. data/lib/couchbase/cluster.rb +1 -1
  139. data/lib/couchbase/collection.rb +108 -0
  140. data/lib/couchbase/collection_options.rb +100 -0
  141. data/lib/couchbase/errors.rb +5 -0
  142. data/lib/couchbase/key_value_scan.rb +125 -0
  143. data/lib/couchbase/options.rb +151 -0
  144. data/lib/couchbase/scope.rb +1 -1
  145. data/lib/couchbase/utils/time.rb +14 -1
  146. data/lib/couchbase/version.rb +1 -1
  147. metadata +41 -7
  148. data/ext/couchbase/core/impl/collection_query_index_manager.cxx +0 -93
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 573df37049b91d6dc00b674904dbda7883a4a20e7078191fc01f3b29f139df13
4
- data.tar.gz: ec07bdbe1a3cdd5caab78b7bc4af3e525d0dba25e54e8bfb13b5d07203c0361a
3
+ metadata.gz: b3c1e2edfe7f74eb3f3094dd526e7dc1896f3002caca935ddeb76923b593f341
4
+ data.tar.gz: b5409f7083156b38aee870037ce0c5d3a14dc7ffe3aa3ac64b5862245b47e4d5
5
5
  SHA512:
6
- metadata.gz: 3b945f9a1942520c04fb22810049bda99e4daecccbadab60c9df474ec9d606a92f253f56e815739e3d64c927727d83d74265973fa72a6493ab38cc27df5f0913
7
- data.tar.gz: 7cfa5628c2dc3a4c43741e139e064ac20f1085fd446f3b10cc480fc622464d0396cfa31d470cdca9c7c8323bb37d6b38ef1200c4b8d4b511fea0642a56f699d5
6
+ metadata.gz: 16705649dc3954db461a7f8a63ee1168a4b191cacb0744e4e5b5b7b110788491d42fab78611b4898078a0276bbe0d9930352e24b4f95db87ab79ed6b6e8f9101
7
+ data.tar.gz: 157af7be99dc745ef7d95ef45490c0025f16ead6abb2f75079188405e82fcec9a64abb681695720e9080fd36b3c17883a56f84bafd06062e56c22ad9286c7ed4
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![license](https://img.shields.io/github/license/couchbase/couchbase-ruby-client?color=brightgreen)](https://opensource.org/licenses/Apache-2.0)
4
4
  [![gem](https://img.shields.io/gem/v/couchbase?color=brightgreen)](https://rubygems.org/gems/couchbase)
5
- [![commits](https://img.shields.io/github/commits-since/couchbase/couchbase-ruby-client/latest?color=brightgreen)](https://github.com/couchbase/couchbase-ruby-client/commits/master)
5
+ [![commits](https://img.shields.io/github/commits-since/couchbase/couchbase-ruby-client/latest?color=brightgreen)](https://github.com/couchbase/couchbase-ruby-client/commits/main)
6
6
  [![linters](https://img.shields.io/github/actions/workflow/status/couchbase/couchbase-ruby-client/linters.yml?branch=main&label=linters)](https://github.com/couchbase/couchbase-ruby-client/actions?query=workflow%3Alinters)
7
7
 
8
8
  This repository contains the third generation of the official Couchbase SDK for Ruby (aka. SDKv3)
@@ -23,7 +23,7 @@ The library has been tested with MRI 3.0, 3.1 and 3.2. Supported platforms are L
23
23
  Add this line to your application's Gemfile:
24
24
 
25
25
  ```ruby
26
- gem "couchbase", "3.4.3"
26
+ gem "couchbase", "3.4.4"
27
27
  ```
28
28
 
29
29
  And then execute:
@@ -100,6 +100,7 @@ set(couchbase_cxx_client_FILES
100
100
  core/free_form_http_request.cxx
101
101
  core/key_value_config.cxx
102
102
  core/n1ql_query_options.cxx
103
+ core/origin.cxx
103
104
  core/range_scan_options.cxx
104
105
  core/range_scan_orchestrator.cxx
105
106
  core/retry_orchestrator.cxx
@@ -232,6 +233,7 @@ set(couchbase_cxx_client_FILES
232
233
  core/protocol/cmd_increment.cxx
233
234
  core/protocol/cmd_insert.cxx
234
235
  core/protocol/cmd_lookup_in.cxx
236
+ core/protocol/cmd_lookup_in_replica.cxx
235
237
  core/protocol/cmd_mutate_in.cxx
236
238
  core/protocol/cmd_noop.cxx
237
239
  core/protocol/cmd_observe_seqno.cxx
@@ -265,10 +267,10 @@ set(couchbase_cxx_client_FILES
265
267
  core/impl/boolean_query.cxx
266
268
  core/impl/build_deferred_query_indexes.cxx
267
269
  core/impl/cluster.cxx
268
- core/impl/collection_query_index_manager.cxx
269
270
  core/impl/common_error_category.cxx
270
271
  core/impl/configuration_profiles_registry.cxx
271
272
  core/impl/conjunction_query.cxx
273
+ core/impl/create_bucket.cxx
272
274
  core/impl/create_query_index.cxx
273
275
  core/impl/date_range.cxx
274
276
  core/impl/date_range_facet.cxx
@@ -277,24 +279,29 @@ set(couchbase_cxx_client_FILES
277
279
  core/impl/decrement.cxx
278
280
  core/impl/disjunction_query.cxx
279
281
  core/impl/dns_srv_tracker.cxx
282
+ core/impl/drop_bucket.cxx
280
283
  core/impl/drop_query_index.cxx
281
284
  core/impl/exists.cxx
282
285
  core/impl/expiry.cxx
283
286
  core/impl/fail_fast_retry_strategy.cxx
284
287
  core/impl/field_level_encryption_error_category.cxx
288
+ core/impl/flush_bucket.cxx
285
289
  core/impl/geo_bounding_box_query.cxx
286
290
  core/impl/geo_distance_query.cxx
287
291
  core/impl/geo_polygon_query.cxx
288
292
  core/impl/get.cxx
293
+ core/impl/get_all_buckets.cxx
289
294
  core/impl/get_all_query_indexes.cxx
290
295
  core/impl/get_all_replicas.cxx
291
296
  core/impl/get_and_lock.cxx
292
297
  core/impl/get_and_touch.cxx
293
298
  core/impl/get_any_replica.cxx
299
+ core/impl/get_bucket.cxx
294
300
  core/impl/get_replica.cxx
295
301
  core/impl/increment.cxx
296
302
  core/impl/insert.cxx
297
303
  core/impl/internal_date_range_facet_result.cxx
304
+ core/impl/internal_manager_error_context.cxx
298
305
  core/impl/internal_numeric_range_facet_result.cxx
299
306
  core/impl/internal_search_error_context.cxx
300
307
  core/impl/internal_search_meta_data.cxx
@@ -303,8 +310,13 @@ set(couchbase_cxx_client_FILES
303
310
  core/impl/internal_search_row_locations.cxx
304
311
  core/impl/internal_term_facet_result.cxx
305
312
  core/impl/key_value_error_category.cxx
313
+ core/impl/key_value_error_context.cxx
306
314
  core/impl/lookup_in.cxx
315
+ core/impl/lookup_in_replica.cxx
316
+ core/impl/lookup_in_all_replicas.cxx
317
+ core/impl/lookup_in_any_replica.cxx
307
318
  core/impl/management_error_category.cxx
319
+ core/impl/manager_error_context.cxx
308
320
  core/impl/match_all_query.cxx
309
321
  core/impl/match_none_query.cxx
310
322
  core/impl/match_phrase_query.cxx
@@ -322,6 +334,7 @@ set(couchbase_cxx_client_FILES
322
334
  core/impl/prepend.cxx
323
335
  core/impl/query.cxx
324
336
  core/impl/query_error_category.cxx
337
+ core/impl/query_error_context.cxx
325
338
  core/impl/query_string_query.cxx
326
339
  core/impl/regexp_query.cxx
327
340
  core/impl/remove.cxx
@@ -366,6 +379,7 @@ set(couchbase_cxx_client_FILES
366
379
  core/impl/transaction_get_result.cxx
367
380
  core/impl/transaction_op_error_category.cxx
368
381
  core/impl/unlock.cxx
382
+ core/impl/update_bucket.cxx
369
383
  core/impl/upsert.cxx
370
384
  core/impl/view_error_category.cxx
371
385
  core/impl/watch_query_indexes.cxx
@@ -22,7 +22,6 @@
22
22
  #include "core/mcbp/codec.hxx"
23
23
  #include "dispatcher.hxx"
24
24
  #include "impl/bootstrap_state_listener.hxx"
25
- #include "mcbp/completion_token.hxx"
26
25
  #include "mcbp/operation_queue.hxx"
27
26
  #include "mcbp/queue_request.hxx"
28
27
  #include "mcbp/queue_response.hxx"
@@ -41,28 +40,6 @@
41
40
 
42
41
  namespace couchbase::core
43
42
  {
44
- /**
45
- * copies nodes from rhs that are not in lhs to output vector
46
- */
47
- static void
48
- diff_nodes(const std::vector<topology::configuration::node>& lhs,
49
- const std::vector<topology::configuration::node>& rhs,
50
- std::vector<topology::configuration::node>& output)
51
- {
52
- for (const auto& re : rhs) {
53
- bool known = false;
54
- for (const auto& le : lhs) {
55
- if (le.hostname == re.hostname && le.services_plain.management.value_or(0) == re.services_plain.management.value_or(0)) {
56
- known = true;
57
- break;
58
- }
59
- }
60
- if (!known) {
61
- output.push_back(re);
62
- }
63
- }
64
- }
65
-
66
43
  class bucket_impl
67
44
  : public std::enable_shared_from_this<bucket_impl>
68
45
  , public config_listener
@@ -303,82 +280,111 @@ class bucket_impl
303
280
  return config_->map_key(key, node_index);
304
281
  }
305
282
 
306
- void restart_node(std::size_t index, const std::string& hostname, const std::string& port)
283
+ void restart_sessions()
307
284
  {
308
- if (closed_) {
309
- CB_LOG_DEBUG(R"({} requested to restart session, but the bucket has been closed already. idx={}, address="{}:{}")",
310
- log_prefix_,
311
- index,
312
- hostname,
313
- port);
285
+ const std::scoped_lock lock(config_mutex_, sessions_mutex_);
286
+ if (!config_.has_value()) {
314
287
  return;
315
288
  }
316
- {
317
- std::scoped_lock lock(config_mutex_);
318
- if (!config_->has_node(origin_.options().network, service_type::key_value, origin_.options().enable_tls, hostname, port)) {
319
- CB_LOG_TRACE(
320
- R"({} requested to restart session, but the node has been ejected from current configuration already. idx={}, network={}, address="{}:{}")",
321
- log_prefix_,
322
- index,
323
- origin_.options().network,
324
- hostname,
325
- port);
326
- return;
327
- }
328
- }
329
- couchbase::core::origin origin(origin_.credentials(), hostname, port, origin_.options());
330
289
 
331
- io::mcbp_session session = origin_.options().enable_tls
332
- ? io::mcbp_session(client_id_, ctx_, tls_, origin, state_listener_, name_, known_features_)
333
- : io::mcbp_session(client_id_, ctx_, origin, state_listener_, name_, known_features_);
290
+ std::size_t kv_node_index{ 0 };
291
+ for (std::size_t index = 0; index < config_->nodes.size(); ++index) {
292
+ const auto& node = config_->nodes[index];
334
293
 
335
- std::scoped_lock lock(sessions_mutex_);
336
- if (auto ptr = sessions_.find(index); ptr == sessions_.end()) {
337
- CB_LOG_DEBUG(R"({} requested to restart session idx={}, which does not exist yet, initiate new one id="{}", address="{}:{}")",
338
- log_prefix_,
339
- index,
340
- session.id(),
341
- hostname,
342
- port);
343
- } else {
344
- const auto& old_session = ptr->second;
345
- auto old_id = old_session.id();
346
- sessions_.erase(ptr);
347
- Expects(sessions_.count(index) == 0);
348
- CB_LOG_DEBUG(R"({} restarting session idx={}, id=("{}" -> "{}"), address="{}:{}")",
294
+ const auto& hostname = node.hostname_for(origin_.options().network);
295
+ auto port = node.port_or(origin_.options().network, service_type::key_value, origin_.options().enable_tls, 0);
296
+ if (port == 0) {
297
+ continue;
298
+ }
299
+
300
+ auto ptr = std::find_if(sessions_.begin(), sessions_.end(), [&hostname, &port](const auto& session) {
301
+ return session.second.bootstrap_hostname() == hostname && session.second.bootstrap_port_number() == port;
302
+ });
303
+ if (ptr != sessions_.end()) {
304
+
305
+ if (auto found_kv_node_index = ptr->first; found_kv_node_index != kv_node_index) {
306
+ if (auto current = sessions_.find(kv_node_index); current == sessions_.end()) {
307
+ CB_LOG_WARNING(R"({} KV node index mismatch: config rev={} states that address="{}:{}" should be at idx={}, )"
308
+ R"(but it is at idx={} ("{}"). Moving session to idx={}.)",
309
+ log_prefix_,
310
+ config_->rev_str(),
311
+ hostname,
312
+ port,
313
+ kv_node_index,
314
+ found_kv_node_index,
315
+ ptr->second.id(),
316
+ kv_node_index);
317
+ sessions_.insert_or_assign(kv_node_index, std::move(ptr->second));
318
+ sessions_.erase(ptr);
319
+ } else {
320
+ CB_LOG_WARNING(
321
+ R"({} KV node index mismatch: config rev={} states that address="{}:{}" should be at idx={}, )"
322
+ R"(but it is at idx={} ("{}"). Slot with idx={} is holds session with address="{}" ("{}"), swapping them.)",
323
+ log_prefix_,
324
+ config_->rev_str(),
325
+ hostname,
326
+ port,
327
+ kv_node_index,
328
+ found_kv_node_index,
329
+ ptr->second.id(),
330
+ kv_node_index,
331
+ current->second.bootstrap_address(),
332
+ current->second.id());
333
+ std::swap(current->second, ptr->second);
334
+ }
335
+ }
336
+ ++kv_node_index;
337
+ continue;
338
+ }
339
+ couchbase::core::origin origin(origin_.credentials(), hostname, port, origin_.options());
340
+ io::mcbp_session session = origin_.options().enable_tls
341
+ ? io::mcbp_session(client_id_, ctx_, tls_, origin, state_listener_, name_, known_features_)
342
+ : io::mcbp_session(client_id_, ctx_, origin, state_listener_, name_, known_features_);
343
+ CB_LOG_DEBUG(R"({} rev={}, restart idx={}, session="{}", address="{}:{}")",
349
344
  log_prefix_,
350
- index,
351
- old_id,
345
+ config_->rev_str(),
346
+ node.index,
352
347
  session.id(),
353
348
  hostname,
354
349
  port);
350
+ session.bootstrap(
351
+ [self = shared_from_this(), session](std::error_code err, topology::configuration cfg) mutable {
352
+ if (err) {
353
+ return self->remove_session(session.id());
354
+ }
355
+ self->update_config(std::move(cfg));
356
+ session.on_configuration_update(self);
357
+ session.on_stop([id = session.id(), self]() { self->remove_session(id); });
358
+ self->drain_deferred_queue();
359
+ },
360
+ true);
361
+ sessions_.insert_or_assign(index, std::move(session));
362
+ ++kv_node_index;
355
363
  }
364
+ }
356
365
 
357
- session.bootstrap(
358
- [self = shared_from_this(), session, this_index = index, hostname, port](std::error_code ec,
359
- const topology::configuration& config) mutable {
360
- if (self->closed_) {
361
- asio::post(asio::bind_executor(
362
- self->ctx_, [session = std::move(session)]() mutable { return session.stop(retry_reason::do_not_retry); }));
363
- return;
364
- }
365
- if (ec) {
366
- CB_LOG_WARNING(R"({} failed to restart session idx={}, ec={})", session.log_prefix(), this_index, ec.message());
367
- self->restart_node(this_index, hostname, port);
368
- return;
369
- }
370
- session.on_configuration_update(self);
371
- session.on_stop([this_index, hostname, port, self](retry_reason reason) {
372
- if (reason == retry_reason::socket_closed_while_in_flight) {
373
- self->restart_node(this_index, hostname, port);
374
- }
375
- });
366
+ void remove_session(const std::string& id)
367
+ {
368
+ bool found{ false };
369
+ const std::scoped_lock lock(sessions_mutex_);
370
+ for (auto ptr = sessions_.cbegin(); ptr != sessions_.cend();) {
371
+ if (ptr->second.id() == id) {
372
+ CB_LOG_DEBUG(R"({} removed session id="{}", address="{}", bootstrap_address="{}:{}")",
373
+ log_prefix_,
374
+ ptr->second.id(),
375
+ ptr->second.remote_address(),
376
+ ptr->second.bootstrap_hostname(),
377
+ ptr->second.bootstrap_port());
378
+ ptr = sessions_.erase(ptr);
379
+ found = true;
380
+ } else {
381
+ ptr = std::next(ptr);
382
+ }
383
+ }
376
384
 
377
- self->update_config(config);
378
- self->drain_deferred_queue();
379
- },
380
- true);
381
- sessions_.insert_or_assign(index, std::move(session));
385
+ if (found) {
386
+ asio::post(asio::bind_executor(ctx_, [self = shared_from_this()]() { return self->restart_sessions(); }));
387
+ }
382
388
  }
383
389
 
384
390
  void bootstrap(utils::movable_function<void(std::error_code, topology::configuration)>&& handler)
@@ -393,15 +399,11 @@ class bucket_impl
393
399
  topology::configuration cfg) mutable {
394
400
  if (ec) {
395
401
  CB_LOG_WARNING(R"({} failed to bootstrap session ec={}, bucket="{}")", new_session.log_prefix(), ec.message(), self->name_);
402
+ self->remove_session(new_session.id());
396
403
  } else {
397
404
  const std::size_t this_index = new_session.index();
398
405
  new_session.on_configuration_update(self);
399
- new_session.on_stop([this_index, hostname = new_session.bootstrap_hostname(), port = new_session.bootstrap_port(), self](
400
- retry_reason reason) {
401
- if (reason == retry_reason::socket_closed_while_in_flight) {
402
- self->restart_node(this_index, hostname, port);
403
- }
404
- });
406
+ new_session.on_stop([id = new_session.id(), self]() { self->remove_session(id); });
405
407
 
406
408
  {
407
409
  std::scoped_lock lock(self->sessions_mutex_);
@@ -455,6 +457,9 @@ class bucket_impl
455
457
  std::scoped_lock lock(deferred_commands_mutex_);
456
458
  std::swap(deferred_commands_, commands);
457
459
  }
460
+ if (!commands.empty()) {
461
+ CB_LOG_TRACE(R"({} draining deferred operation queue, size={})", log_prefix_, commands.size());
462
+ }
458
463
  while (!commands.empty()) {
459
464
  commands.front()();
460
465
  commands.pop();
@@ -489,9 +494,33 @@ class bucket_impl
489
494
  }
490
495
  }
491
496
 
497
+ /**
498
+ * copies nodes from rhs that are not in lhs to output vector
499
+ */
500
+ void diff_nodes(const std::vector<topology::configuration::node>& lhs,
501
+ const std::vector<topology::configuration::node>& rhs,
502
+ std::vector<topology::configuration::node>& output)
503
+ {
504
+ for (const auto& re : rhs) {
505
+ bool known = false;
506
+ const auto& rhost = re.hostname_for(origin_.options().network);
507
+ const auto rport = re.port_or(origin_.options().network, service_type::key_value, origin_.options().enable_tls, 0);
508
+ for (const auto& le : lhs) {
509
+ const auto& lhost = le.hostname_for(origin_.options().network);
510
+ const auto lport = le.port_or(origin_.options().network, service_type::key_value, origin_.options().enable_tls, 0);
511
+ if (rhost == lhost && rport == lport) {
512
+ known = true;
513
+ break;
514
+ }
515
+ }
516
+ if (!known) {
517
+ output.push_back(re);
518
+ }
519
+ }
520
+ }
521
+
492
522
  void update_config(topology::configuration config) override
493
523
  {
494
- bool forced_config = false;
495
524
  std::vector<topology::configuration::node> added{};
496
525
  std::vector<topology::configuration::node> removed{};
497
526
  {
@@ -500,7 +529,6 @@ class bucket_impl
500
529
  CB_LOG_DEBUG("{} initialize configuration rev={}", log_prefix_, config.rev_str());
501
530
  } else if (config.force) {
502
531
  CB_LOG_DEBUG("{} forced to accept configuration rev={}", log_prefix_, config.rev_str());
503
- forced_config = true;
504
532
  } else if (!config.vbmap) {
505
533
  CB_LOG_DEBUG("{} will not update the configuration old={} -> new={}, because new config does not have partition map",
506
534
  log_prefix_,
@@ -533,78 +561,81 @@ class bucket_impl
533
561
  std::scoped_lock lock(sessions_mutex_);
534
562
  std::map<size_t, io::mcbp_session> new_sessions{};
535
563
 
536
- for (auto& [index, session] : sessions_) {
537
- std::size_t new_index = config.nodes.size() + 1;
538
- for (const auto& node : config.nodes) {
539
- if (session.bootstrap_hostname() == node.hostname_for(origin_.options().network) &&
540
- session.bootstrap_port() ==
541
- std::to_string(
542
- node.port_or(origin_.options().network, service_type::key_value, origin_.options().enable_tls, 0))) {
543
- new_index = node.index;
544
- break;
545
- }
546
- }
547
- if (new_index < config.nodes.size()) {
548
- CB_LOG_DEBUG(R"({} rev={}, preserve session="{}", address="{}:{}", index={}->{})",
549
- log_prefix_,
550
- config.rev_str(),
551
- session.id(),
552
- session.bootstrap_hostname(),
553
- session.bootstrap_port(),
554
- index,
555
- new_index);
556
- new_sessions.insert_or_assign(new_index, std::move(session));
557
- } else {
558
- CB_LOG_DEBUG(R"({} rev={}, drop session="{}", address="{}:{}", index={})",
559
- log_prefix_,
560
- config.rev_str(),
561
- session.id(),
562
- session.bootstrap_hostname(),
563
- session.bootstrap_port(),
564
- index);
565
- asio::post(asio::bind_executor(
566
- ctx_, [session = std::move(session)]() mutable { return session.stop(retry_reason::do_not_retry); }));
567
- }
568
- }
569
-
564
+ std::size_t next_index{ 0 };
570
565
  for (const auto& node : config.nodes) {
571
- if (new_sessions.find(node.index) != new_sessions.end()) {
572
- continue;
573
- }
574
-
575
566
  const auto& hostname = node.hostname_for(origin_.options().network);
576
567
  auto port = node.port_or(origin_.options().network, service_type::key_value, origin_.options().enable_tls, 0);
577
568
  if (port == 0) {
578
569
  continue;
579
570
  }
571
+
572
+ bool reused_session{ false };
573
+ for (auto it = sessions_.begin(); it != sessions_.end(); ++it) {
574
+ if (it->second.bootstrap_hostname() == hostname && it->second.bootstrap_port_number() == port) {
575
+ CB_LOG_DEBUG(R"({} rev={}, preserve session="{}", address="{}:{}", index={}->{})",
576
+ log_prefix_,
577
+ config.rev_str(),
578
+ it->second.id(),
579
+ it->second.bootstrap_hostname(),
580
+ it->second.bootstrap_port(),
581
+ it->first,
582
+ next_index);
583
+ new_sessions.insert_or_assign(next_index, std::move(it->second));
584
+ reused_session = true;
585
+ ++next_index;
586
+ sessions_.erase(it);
587
+ break;
588
+ }
589
+ }
590
+ if (reused_session) {
591
+ continue;
592
+ }
593
+
580
594
  couchbase::core::origin origin(origin_.credentials(), hostname, port, origin_.options());
581
595
  io::mcbp_session session = origin_.options().enable_tls
582
596
  ? io::mcbp_session(client_id_, ctx_, tls_, origin, state_listener_, name_, known_features_)
583
597
  : io::mcbp_session(client_id_, ctx_, origin, state_listener_, name_, known_features_);
584
- CB_LOG_DEBUG(
585
- R"({} rev={}, add session="{}", address="{}:{}")", log_prefix_, config.rev_str(), session.id(), hostname, port);
598
+ CB_LOG_DEBUG(R"({} rev={}, add session="{}", address="{}:{}", index={})",
599
+ log_prefix_,
600
+ config.rev_str(),
601
+ session.id(),
602
+ hostname,
603
+ port,
604
+ node.index);
586
605
  session.bootstrap(
587
- [self = shared_from_this(), session, forced_config, idx = node.index](std::error_code err,
588
- topology::configuration cfg) mutable {
589
- if (!err) {
590
- self->update_config(std::move(cfg));
591
- session.on_configuration_update(self);
592
- session.on_stop(
593
- [index = session.index(), hostname = session.bootstrap_hostname(), port = session.bootstrap_port(), self](
594
- retry_reason reason) {
595
- if (reason == retry_reason::socket_closed_while_in_flight) {
596
- self->restart_node(index, hostname, port);
597
- }
598
- });
599
- self->drain_deferred_queue();
600
- } else if (err == errc::common::unambiguous_timeout && forced_config) {
601
- self->restart_node(idx, session.bootstrap_hostname(), session.bootstrap_port());
606
+ [self = shared_from_this(), session, idx = next_index](std::error_code err, topology::configuration cfg) mutable {
607
+ if (err) {
608
+ CB_LOG_WARNING(R"({} failed to bootstrap session="{}", address="{}:{}", index={}, ec={})",
609
+ session.log_prefix(),
610
+ session.id(),
611
+ session.bootstrap_hostname(),
612
+ session.bootstrap_port(),
613
+ idx,
614
+ err.message());
615
+ return self->remove_session(session.id());
602
616
  }
617
+ self->update_config(std::move(cfg));
618
+ session.on_configuration_update(self);
619
+ session.on_stop([id = session.id(), self]() { self->remove_session(id); });
620
+ self->drain_deferred_queue();
603
621
  },
604
622
  true);
605
- new_sessions.insert_or_assign(node.index, std::move(session));
623
+ new_sessions.insert_or_assign(next_index, std::move(session));
624
+ ++next_index;
625
+ }
626
+ std::swap(sessions_, new_sessions);
627
+
628
+ for (auto it = new_sessions.begin(); it != new_sessions.end(); ++it) {
629
+ CB_LOG_DEBUG(R"({} rev={}, drop session="{}", address="{}:{}", index={})",
630
+ log_prefix_,
631
+ config.rev_str(),
632
+ it->second.id(),
633
+ it->second.bootstrap_hostname(),
634
+ it->second.bootstrap_port(),
635
+ it->first);
636
+ asio::post(asio::bind_executor(
637
+ ctx_, [session = std::move(it->second)]() mutable { return session.stop(retry_reason::do_not_retry); }));
606
638
  }
607
- sessions_ = new_sessions;
608
639
  }
609
640
  }
610
641
 
@@ -83,7 +83,7 @@ class bucket
83
83
  auto cmd = std::make_shared<operations::mcbp_command<bucket, Request>>(ctx_, shared_from_this(), request, default_timeout());
84
84
  cmd->start([cmd, handler = std::forward<Handler>(handler)](std::error_code ec, std::optional<io::mcbp_message>&& msg) mutable {
85
85
  using encoded_response_type = typename Request::encoded_response_type;
86
- std::uint16_t status_code = msg ? msg->header.status() : 0U;
86
+ std::uint16_t status_code = msg ? msg->header.status() : 0xffffU;
87
87
  auto resp = msg ? encoded_response_type(std::move(*msg)) : encoded_response_type{};
88
88
  auto ctx = make_key_value_error_context(ec, status_code, cmd, resp);
89
89
  handler(cmd->request.make_response(std::move(ctx), std::move(resp)));
@@ -107,7 +107,7 @@ class bucket
107
107
  auto [partition, server] = map_id(cmd->request.id);
108
108
  if (!server.has_value()) {
109
109
  CB_LOG_TRACE(
110
- R"({} unable to map key=\"{}\" to the node, id={}, partition={})", log_prefix(), cmd->request.id, cmd->id_, partition);
110
+ R"({} unable to map key="{}" to the node, id={}, partition={})", log_prefix(), cmd->request.id, cmd->id_, partition);
111
111
  return io::retry_orchestrator::maybe_retry(
112
112
  cmd->manager_, cmd, retry_reason::node_not_available, errc::common::request_canceled);
113
113
  }
@@ -116,19 +116,32 @@ class bucket
116
116
  }
117
117
  auto session = find_session_by_index(index);
118
118
  if (!session || !session->has_config()) {
119
- CB_LOG_TRACE(R"({} defer operation id={}, session={}, has_config={})",
119
+ CB_LOG_TRACE(R"({} defer operation id={}, key="{}", partition={}, index={}, session={}, address="{}", has_config={})",
120
120
  log_prefix(),
121
121
  cmd->id_,
122
+ cmd->request.id,
123
+ cmd->request.partition,
124
+ index,
122
125
  session.has_value(),
126
+ session.has_value() ? session->bootstrap_address() : "",
123
127
  session.has_value() && session->has_config());
124
128
  return defer_command([self = shared_from_this(), cmd]() { self->map_and_send(cmd); });
125
129
  }
126
130
  if (session->is_stopped()) {
127
131
  CB_LOG_TRACE(
128
- R"({} the session has been found, but it is stopped, retrying id={}, session={})", log_prefix(), cmd->id_, session->id());
132
+ R"({} the session has been found for idx={}, but it is stopped, retrying id={}, key="{}", partition={}, session={}, address="{}")",
133
+ log_prefix(),
134
+ index,
135
+ cmd->id_,
136
+ cmd->request.id,
137
+ cmd->request.partition,
138
+ session->id(),
139
+ session->bootstrap_address());
129
140
  return io::retry_orchestrator::maybe_retry(
130
141
  cmd->manager_, cmd, retry_reason::node_not_available, errc::common::request_canceled);
131
142
  }
143
+ cmd->last_dispatched_from_ = session->local_address();
144
+ cmd->last_dispatched_to_ = session->bootstrap_address();
132
145
  cmd->send_to(session.value());
133
146
  }
134
147