couchbase 3.8.0 → 3.8.2

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/ext/cache/extconf_include.rb +4 -4
  4. data/ext/cache/mozilla-ca-bundle.crt +96 -675
  5. data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
  6. data/ext/couchbase/CMakeLists.txt +3 -1
  7. data/ext/couchbase/core/error_context/base_error_context.hxx +32 -0
  8. data/ext/couchbase/core/error_context/key_value.cxx +1 -0
  9. data/ext/couchbase/core/error_context/key_value.hxx +23 -0
  10. data/ext/couchbase/core/error_context/key_value_error_context.hxx +35 -0
  11. data/ext/couchbase/core/error_context/subdocument_error_context.hxx +41 -0
  12. data/ext/couchbase/core/impl/binary_collection.cxx +123 -88
  13. data/ext/couchbase/core/impl/collection.cxx +416 -189
  14. data/ext/couchbase/core/impl/error.cxx +29 -4
  15. data/ext/couchbase/core/impl/invoke_with_node_id.hxx +44 -0
  16. data/ext/couchbase/core/impl/node_id.cxx +110 -0
  17. data/ext/couchbase/core/impl/node_id.hxx +40 -0
  18. data/ext/couchbase/core/impl/public_cluster.cxx +118 -5
  19. data/ext/couchbase/core/io/configuration_belongs_to_session.hxx +67 -0
  20. data/ext/couchbase/core/io/http_session_manager.hxx +97 -57
  21. data/ext/couchbase/core/io/mcbp_session.cxx +7 -16
  22. data/ext/couchbase/core/io/streams.cxx +143 -12
  23. data/ext/couchbase/core/io/streams.hxx +6 -0
  24. data/ext/couchbase/core/operations/document_get_all_replicas.hxx +14 -4
  25. data/ext/couchbase/core/operations/document_get_projected.cxx +3 -4
  26. data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +4 -0
  27. data/ext/couchbase/core/operations/document_query.cxx +12 -12
  28. data/ext/couchbase/core/operations/management/bucket_create.cxx +37 -38
  29. data/ext/couchbase/core/operations/management/bucket_update.cxx +26 -28
  30. data/ext/couchbase/core/operations/management/collection_create.cxx +11 -11
  31. data/ext/couchbase/core/operations/management/collection_drop.cxx +11 -11
  32. data/ext/couchbase/core/operations/management/collection_update.cxx +11 -11
  33. data/ext/couchbase/core/operations/management/error_utils.cxx +9 -6
  34. data/ext/couchbase/core/operations/management/query_index_create.cxx +5 -4
  35. data/ext/couchbase/core/operations/management/query_index_drop.cxx +7 -6
  36. data/ext/couchbase/core/operations/management/scope_create.cxx +12 -13
  37. data/ext/couchbase/core/operations/management/scope_drop.cxx +8 -9
  38. data/ext/couchbase/core/topology/configuration.cxx +21 -0
  39. data/ext/couchbase/core/topology/configuration.hxx +28 -0
  40. data/ext/couchbase/core/transactions/attempt_context_impl.cxx +254 -178
  41. data/ext/couchbase/core/transactions/attempt_context_impl.hxx +5 -1
  42. data/ext/couchbase/core/transactions/transactions_cleanup.cxx +42 -7
  43. data/ext/couchbase/core/transactions/waitable_op_list.hxx +13 -2
  44. data/ext/couchbase/core/utils/contains_string.cxx +61 -0
  45. data/ext/couchbase/core/utils/contains_string.hxx +26 -0
  46. data/ext/couchbase/couchbase/cluster.hxx +33 -0
  47. data/ext/couchbase/couchbase/collection.hxx +73 -0
  48. data/ext/couchbase/couchbase/error.hxx +16 -0
  49. data/ext/couchbase/couchbase/node_id.hxx +123 -0
  50. data/ext/couchbase/couchbase/node_id_for_options.hxx +61 -0
  51. data/ext/couchbase/couchbase/node_ids_options.hxx +62 -0
  52. data/ext/couchbase/couchbase/result.hxx +42 -0
  53. data/ext/rcb_crud.cxx +2 -0
  54. data/ext/rcb_logger.cxx +15 -17
  55. data/lib/couchbase/cluster.rb +16 -14
  56. data/lib/couchbase/datastructures/couchbase_list.rb +1 -0
  57. data/lib/couchbase/datastructures/couchbase_map.rb +1 -0
  58. data/lib/couchbase/datastructures/couchbase_queue.rb +1 -0
  59. data/lib/couchbase/datastructures/couchbase_set.rb +1 -0
  60. data/lib/couchbase/errors.rb +1 -1
  61. data/lib/couchbase/json_transcoder.rb +1 -1
  62. data/lib/couchbase/options.rb +9 -4
  63. data/lib/couchbase/protostellar/client.rb +0 -2
  64. data/lib/couchbase/protostellar/cluster.rb +4 -0
  65. data/lib/couchbase/protostellar/generated/admin/analytics/v1/analytics_pb.rb +54 -0
  66. data/lib/couchbase/protostellar/generated/admin/analytics/v1/analytics_services_pb.rb +51 -0
  67. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_pb.rb +5 -24
  68. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_services_pb.rb +16 -0
  69. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_pb.rb +5 -24
  70. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_services_pb.rb +16 -0
  71. data/lib/couchbase/protostellar/generated/admin/query/v1/query_pb.rb +5 -24
  72. data/lib/couchbase/protostellar/generated/admin/query/v1/query_services_pb.rb +18 -0
  73. data/lib/couchbase/protostellar/generated/admin/search/v1/search_pb.rb +2 -23
  74. data/lib/couchbase/protostellar/generated/admin/search/v1/search_services_pb.rb +23 -0
  75. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_pb.rb +4 -25
  76. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_services_pb.rb +10 -0
  77. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_pb.rb +6 -25
  78. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_services_pb.rb +18 -0
  79. data/lib/couchbase/protostellar/generated/internal/xdcr/v1/xdcr_pb.rb +46 -0
  80. data/lib/couchbase/protostellar/generated/internal/xdcr/v1/xdcr_services_pb.rb +56 -0
  81. data/lib/couchbase/protostellar/generated/kv/v1/kv_pb.rb +3 -26
  82. data/lib/couchbase/protostellar/generated/kv/v1/kv_services_pb.rb +47 -0
  83. data/lib/couchbase/protostellar/generated/query/v1/query_pb.rb +4 -26
  84. data/lib/couchbase/protostellar/generated/query/v1/query_services_pb.rb +10 -0
  85. data/lib/couchbase/protostellar/generated/routing/v2/routing_pb.rb +26 -0
  86. data/lib/couchbase/protostellar/generated/routing/v2/routing_services_pb.rb +43 -0
  87. data/lib/couchbase/protostellar/generated/search/v1/search_pb.rb +5 -26
  88. data/lib/couchbase/protostellar/generated/search/v1/search_services_pb.rb +11 -0
  89. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_pb.rb +2 -23
  90. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_services_pb.rb +30 -0
  91. data/lib/couchbase/protostellar/generated/view/v1/view_pb.rb +2 -23
  92. data/lib/couchbase/protostellar/generated/view/v1/view_services_pb.rb +9 -0
  93. data/lib/couchbase/protostellar/request_generator/admin/collection.rb +4 -2
  94. data/lib/couchbase/protostellar/request_generator/admin/query.rb +2 -0
  95. data/lib/couchbase/protostellar/request_generator/kv.rb +1 -1
  96. data/lib/couchbase/protostellar/scope.rb +4 -0
  97. data/lib/couchbase/utils/observability.rb +10 -4
  98. data/lib/couchbase/version.rb +1 -1
  99. metadata +20 -7
  100. data/lib/couchbase/protostellar/generated/routing/v1/routing_pb.rb +0 -52
  101. data/lib/couchbase/protostellar/generated/routing/v1/routing_services_pb.rb +0 -30
@@ -75,6 +75,17 @@ error::error(std::error_code ec,
75
75
  {
76
76
  }
77
77
 
78
+ error::error(std::error_code ec,
79
+ std::string message,
80
+ couchbase::error_context ctx,
81
+ couchbase::node_id node_id)
82
+ : ec_{ ec }
83
+ , message_{ std::move(message) }
84
+ , ctx_{ std::move(ctx) }
85
+ , node_id_{ std::move(node_id) }
86
+ {
87
+ }
88
+
78
89
  auto
79
90
  error::ec() const -> std::error_code
80
91
  {
@@ -103,6 +114,12 @@ error::cause() const -> std::optional<error>
103
114
  return *cause_;
104
115
  }
105
116
 
117
+ auto
118
+ error::node_id() const -> const couchbase::node_id&
119
+ {
120
+ return node_id_;
121
+ }
122
+
106
123
  error::operator bool() const
107
124
  {
108
125
  return ec_.value() != 0;
@@ -164,19 +181,27 @@ make_error(const core::error_context::http& core_ctx) -> couchbase::error
164
181
  auto
165
182
  make_error(const couchbase::core::key_value_error_context& core_ctx) -> couchbase::error
166
183
  {
184
+ // Always preserve the node_id so that callers on both success and error
185
+ // paths can identify which node handled the request.
167
186
  if (!core_ctx.ec()) {
168
- return {};
187
+ return { {}, {}, {}, core_ctx.last_dispatched_to_node_id() };
169
188
  }
170
- return { core_ctx.ec(), {}, internal_error_context::build_error_context(core_ctx) };
189
+ return { core_ctx.ec(),
190
+ {},
191
+ internal_error_context::build_error_context(core_ctx),
192
+ core_ctx.last_dispatched_to_node_id() };
171
193
  }
172
194
 
173
195
  auto
174
196
  make_error(const couchbase::core::subdocument_error_context& core_ctx) -> couchbase::error
175
197
  {
176
198
  if (!core_ctx.ec()) {
177
- return {};
199
+ return { {}, {}, {}, core_ctx.last_dispatched_to_node_id() };
178
200
  }
179
- return { core_ctx.ec(), {}, internal_error_context::build_error_context(core_ctx) };
201
+ return { core_ctx.ec(),
202
+ {},
203
+ internal_error_context::build_error_context(core_ctx),
204
+ core_ctx.last_dispatched_to_node_id() };
180
205
  }
181
206
 
182
207
  auto
@@ -0,0 +1,44 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2026-Present Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #pragma once
19
+
20
+ #include <couchbase/error.hxx>
21
+
22
+ #include <utility>
23
+
24
+ namespace couchbase::core::impl
25
+ {
26
+ /**
27
+ * Forwards (err, result) to the caller's handler, propagating the node_id
28
+ * carried by the error (which make_error populates for KV contexts) onto
29
+ * the result. Using a plain function template avoids the extra std::function
30
+ * allocation a type-erasing wrapper would introduce for the sole purpose of
31
+ * setting node_id - that extra allocation was the source of the 18
32
+ * "Potential memory leak" reports clang-static-analyzer flagged against the
33
+ * previous wrap_with_node_id helper. The handler is taken by forwarding
34
+ * reference and perfect-forwarded to the invocation so that callers moving
35
+ * an rvalue handler consume it without an intermediate copy.
36
+ */
37
+ template<typename Handler, typename Result>
38
+ void
39
+ invoke_with_node_id(Handler&& handler, couchbase::error err, Result result)
40
+ {
41
+ result.node_id(err.node_id());
42
+ std::forward<Handler>(handler)(std::move(err), std::move(result));
43
+ }
44
+ } // namespace couchbase::core::impl
@@ -0,0 +1,110 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2024-Present Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "node_id.hxx"
19
+
20
+ #include "core/utils/crc32.hxx"
21
+
22
+ #include <spdlog/fmt/bundled/core.h>
23
+
24
+ #include <cstdint>
25
+ #include <string>
26
+
27
+ namespace couchbase
28
+ {
29
+
30
+ namespace
31
+ {
32
+ /**
33
+ * Produces a stable, opaque hex string from hostname + KV port.
34
+ *
35
+ * Uses CRC32 (already in-tree for vBucket mapping) rather than a
36
+ * cryptographic hash — collision resistance across a handful of cluster
37
+ * nodes is more than sufficient, and this avoids pulling in OpenSSL headers.
38
+ */
39
+ auto
40
+ derive_fallback_id(const std::string& hostname, std::uint16_t port) -> std::string
41
+ {
42
+ auto input = fmt::format("{}:{}", hostname, port);
43
+ auto crc = core::utils::hash_crc32(input.data(), input.size());
44
+ return fmt::format("{:08x}", crc);
45
+ }
46
+ } // namespace
47
+
48
+ node_id::node_id(std::string node_uuid, std::string hostname, std::uint16_t port)
49
+ : node_uuid_{ std::move(node_uuid) }
50
+ , hostname_{ std::move(hostname) }
51
+ , port_{ port }
52
+ , id_{ node_uuid_.empty() ? derive_fallback_id(hostname_, port_) : node_uuid_ }
53
+ {
54
+ }
55
+
56
+ auto
57
+ node_id::id() const -> const std::string&
58
+ {
59
+ return id_;
60
+ }
61
+
62
+ auto
63
+ node_id::node_uuid() const -> const std::string&
64
+ {
65
+ return node_uuid_;
66
+ }
67
+
68
+ auto
69
+ node_id::hostname() const -> const std::string&
70
+ {
71
+ return hostname_;
72
+ }
73
+
74
+ auto
75
+ node_id::port() const -> std::uint16_t
76
+ {
77
+ return port_;
78
+ }
79
+
80
+ node_id::
81
+ operator bool() const
82
+ {
83
+ return !id_.empty();
84
+ }
85
+
86
+ auto
87
+ node_id::operator==(const node_id& other) const -> bool
88
+ {
89
+ return id_ == other.id_;
90
+ }
91
+
92
+ auto
93
+ node_id::operator!=(const node_id& other) const -> bool
94
+ {
95
+ return !(*this == other);
96
+ }
97
+
98
+ auto
99
+ node_id::operator<(const node_id& other) const -> bool
100
+ {
101
+ return id_ < other.id_;
102
+ }
103
+
104
+ auto
105
+ internal_node_id::build(std::string node_uuid, std::string hostname, std::uint16_t port) -> node_id
106
+ {
107
+ return { std::move(node_uuid), std::move(hostname), port };
108
+ }
109
+
110
+ } // namespace couchbase
@@ -0,0 +1,40 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2024-Present Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #pragma once
19
+
20
+ #include <couchbase/node_id.hxx>
21
+
22
+ #include <cstdint>
23
+ #include <string>
24
+
25
+ namespace couchbase
26
+ {
27
+
28
+ /**
29
+ * Internal factory for constructing node_id instances from core-level data.
30
+ *
31
+ * This class is a friend of node_id and provides the only way to construct
32
+ * a non-default node_id outside the couchbase namespace.
33
+ */
34
+ class internal_node_id
35
+ {
36
+ public:
37
+ static auto build(std::string node_uuid, std::string hostname, std::uint16_t port) -> node_id;
38
+ };
39
+
40
+ } // namespace couchbase
@@ -65,6 +65,8 @@
65
65
  #include <asio/execution_context.hpp>
66
66
  #include <asio/post.hpp>
67
67
 
68
+ #include <gsl/assert>
69
+
68
70
  #include <functional>
69
71
  #include <future>
70
72
  #include <memory>
@@ -414,19 +416,110 @@ public:
414
416
 
415
417
  void notify_fork(fork_event event)
416
418
  {
419
+ // asio requires notify_fork() to run with no thread inside io_context::run():
420
+ //
421
+ // "This function must not be called while any other execution_context
422
+ // function, or any function on an I/O object associated with the
423
+ // execution_context, is being called in another thread."
424
+ // -- asio/execution_context.hpp
425
+ //
426
+ // That is not a formality for fork_child: epoll_reactor::notify_fork() closes
427
+ // and recreates epoll_fd_, timer_fd_ and the interrupter, then rewrites every
428
+ // descriptor registration. Running it against a live reactor makes the IO
429
+ // thread epoll_wait() on a descriptor another thread is closing, and then
430
+ // dereference whatever epoll reports as a descriptor_state.
431
+ //
432
+ // So the fixup goes in the window where the IO thread is not running: after
433
+ // the join in fork_prepare, and before the restart that starts the new one.
417
434
  if (event == fork_event::prepare) {
435
+ // Quiesce the transactions cleanup threads BEFORE stopping the io_context.
436
+ // transactions_cleanup::stop() joins workers that may be blocking on a KV
437
+ // operation, and only the IO thread can complete those -- so stopping the
438
+ // io_context first can leave that join waiting forever.
439
+ if (transactions_) {
440
+ transactions_->notify_fork(event);
441
+ }
418
442
  io_.stop();
419
- io_thread_.join();
443
+ // Guarded like do_close() below, and for the same reason: close() already
444
+ // stopped the io_context and joined this thread, so a notify_fork(prepare)
445
+ // afterwards has nothing left to join -- and join() on a thread that is not
446
+ // joinable throws std::system_error, out of a void public API.
447
+ if (io_thread_.joinable()) {
448
+ io_thread_.join();
449
+ }
450
+ }
451
+
452
+ // Our own IO thread is gone at this point, checkable in program order
453
+ // rather than only by a sanitizer: fork_prepare has just joined it, and
454
+ // fork_child/fork_parent inherit an already-joined one, because prepare runs
455
+ // before fork() and only the forking thread survives into the child. If a
456
+ // later change moves the restart back above this line, this fires
457
+ // immediately instead of turning into an intermittent use-after-free.
458
+ //
459
+ // The transactions cleanup threads were stopped above, before io_.stop(), so
460
+ // by here the only threads this cluster owned are gone.
461
+ Expects(!io_thread_.joinable());
462
+
463
+ if (event == fork_event::prepare) {
464
+ try {
465
+ io_.notify_fork(fork_event_to_asio(event));
466
+ } catch (...) {
467
+ // Undo everything this prepare did, then report. Returning stopped and threadless
468
+ // would hang ~cluster_impl, which waits on a completion only the IO thread can
469
+ // deliver -- the same hazard as the fork_child/fork_parent path below. The fork
470
+ // must not go ahead, but the cluster stays usable and destructible.
471
+ //
472
+ // The io_context comes back first: restarting cleanup spawns workers that issue KV
473
+ // operations, and only the IO thread completes those. Restoring cleanup matters as
474
+ // much as restoring the thread -- it was stopped above, and leaving it stopped
475
+ // would silently disable lost-attempts cleanup for the rest of the process while
476
+ // this function claimed the cluster was fine.
477
+ //
478
+ // Note for the caller: do not try to compensate by calling notify_fork(parent)
479
+ // after this throws. Prepare has already been undone here, and that call would
480
+ // find a restarted IO thread and trip the precondition above.
481
+ io_.restart();
482
+ io_thread_ = std::thread{ [&io = io_] {
483
+ io.run();
484
+ } };
485
+ if (transactions_) {
486
+ transactions_->notify_fork(fork_event::parent);
487
+ }
488
+ throw;
489
+ }
420
490
  } else {
421
- // TODO(SA): close all sockets in fork_event::child
491
+ // TODO(CXXCBC-913): disown this cluster's sockets here, instead of leaving it to the
492
+ // reconnect. epoll_reactor::notify_fork(fork_child) below re-registers every
493
+ // descriptor inherited from the parent into the child's new epoll instance, so
494
+ // from here until those sockets are closed the child polls file descriptions the
495
+ // parent is still using. Closing them is at least no longer destructive -- see
496
+ // stream_impl::close(), which detaches a socket it did not open rather than
497
+ // shutting it down -- but it happens on the reconnect path, asynchronously, and
498
+ // only once the replaced impl is destroyed.
422
499
  io_.restart();
500
+ try {
501
+ io_.notify_fork(fork_event_to_asio(event));
502
+ } catch (...) {
503
+ // notify_fork() throws if re-registering a descriptor with the new
504
+ // epoll instance fails. The io_context is unusable after that (asio says
505
+ // to destroy it), but destruction itself needs a runner: do_close()
506
+ // waits on a completion only the IO thread can deliver, so returning
507
+ // from here stopped and threadless would hang ~cluster_impl instead of
508
+ // surfacing the failure.
509
+ io_thread_ = std::thread{ [&io = io_] {
510
+ io.run();
511
+ } };
512
+ throw;
513
+ }
423
514
  io_thread_ = std::thread{ [&io = io_] {
424
515
  io.run();
425
516
  } };
426
517
  }
427
- io_.notify_fork(fork_event_to_asio(event));
428
518
 
429
- if (event != fork_event::child && transactions_) {
519
+ // prepare was handled above, before the io_context was stopped. The child does
520
+ // not restart cleanup on this impl: it is about to be replaced, and the
521
+ // replacement starts its own.
522
+ if (event == fork_event::parent && transactions_) {
430
523
  transactions_->notify_fork(event);
431
524
  }
432
525
  }
@@ -640,6 +733,16 @@ cluster::connect(const std::string& connection_string,
640
733
  auto
641
734
  cluster::notify_fork(fork_event event) -> void
642
735
  {
736
+ #if defined(_WIN32)
737
+ // No fork(2) on Windows, so there is nothing to prepare for or recover from.
738
+ // The function stays part of the API on every platform -- callers, the wrapper
739
+ // SDKs especially, should not have to compile differently per platform -- but
740
+ // here it does nothing. Doing the POSIX work instead would stop and restart the
741
+ // io_context and re-bootstrap the cluster for no reason, and asio's IOCP
742
+ // backend has no notify_fork to apply either.
743
+ (void)event;
744
+ return;
745
+ #else
643
746
  if (!impl_) {
644
747
  return;
645
748
  }
@@ -659,14 +762,24 @@ cluster::notify_fork(fork_event event) -> void
659
762
  if (err.ec()) {
660
763
  // TODO(SA): we should fall to background reconnect loop similar to Columnar build
661
764
  CB_LOG_ERROR("Unable to reconnect instance after fork: {}", err.ec().message());
662
- return;
663
765
  }
766
+ // Adopt the new impl either way. Leaving impl_ null on failure turned the
767
+ // child's next operation into a segfault, because every data-plane method
768
+ // dereferences impl_ unchecked; an impl that failed to open reports
769
+ // errc::network::cluster_closed instead, which is what a caller already gets
770
+ // from a handle it acquired before the fork.
664
771
  impl_ = new_impl;
772
+ // Always fulfill the barrier. Returning early here used to hang this
773
+ // function forever: `barrier` is still owned by the enclosing scope below,
774
+ // so the promise is never destroyed and future.get() never sees a
775
+ // broken_promise either -- a failed post-fork reconnect just wedged the
776
+ // caller.
665
777
  barrier->set_value();
666
778
  });
667
779
 
668
780
  future.get();
669
781
  }
782
+ #endif
670
783
  }
671
784
 
672
785
  void
@@ -0,0 +1,67 @@
1
+ /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2020-2021 Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #pragma once
19
+
20
+ #include <optional>
21
+ #include <string>
22
+
23
+ namespace couchbase::core::io
24
+ {
25
+ /**
26
+ * Decide whether a freshly received cluster map belongs to this session and must
27
+ * therefore be accepted.
28
+ *
29
+ * A session's bucket binding is fixed for its entire lifetime (set once in the
30
+ * constructor, never reassigned), so the rule reduces to a single invariant:
31
+ * the bucket identity carried by the configuration must match the bucket identity
32
+ * of the session.
33
+ *
34
+ * - a cluster-level (bucket-less) session accepts only cluster-level configs;
35
+ * - a bucket-level session accepts only configs for *its own* bucket.
36
+ *
37
+ * @param config_bucket bucket name embedded in the configuration payload
38
+ * (std::nullopt => cluster-level / GCCCP config)
39
+ * @param session_bucket bucket this session is bound to
40
+ * (std::nullopt => cluster-level / GCCCP session)
41
+ * @return true if the configuration must be accepted, false if it must be ignored
42
+ */
43
+ [[nodiscard]] inline auto
44
+ configuration_belongs_to_session(const std::optional<std::string>& config_bucket,
45
+ const std::optional<std::string>& session_bucket) -> bool
46
+ {
47
+ // Stage 1 — cluster-level config for a cluster-level session.
48
+ // Neither side names a bucket: this is a GCCCP (cluster) map and the session is
49
+ // not bound to any bucket, so the config belongs here. Accept.
50
+ if (!config_bucket.has_value() && !session_bucket.has_value()) {
51
+ return true;
52
+ }
53
+
54
+ // Stage 2 — cardinality mismatch: exactly one side names a bucket. Reject.
55
+ // Either a bucket-less config arrived on a bucket-bound session, or a bucket
56
+ // config arrived on a cluster-level session. In both cases the config does not
57
+ // belong to this session.
58
+ if (config_bucket.has_value() != session_bucket.has_value()) {
59
+ return false;
60
+ }
61
+
62
+ // Stage 3 — both sides name a bucket: accept only if it is the *same* bucket.
63
+ // Reaching here guarantees both optionals are engaged, so value() is safe.
64
+ // Guards against applying another bucket's map to this session.
65
+ return config_bucket.value() == session_bucket.value();
66
+ }
67
+ } // namespace couchbase::core::io