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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/ext/cache/extconf_include.rb +4 -4
- data/ext/cache/mozilla-ca-bundle.crt +96 -675
- data/ext/cache/mozilla-ca-bundle.sha256 +1 -1
- data/ext/couchbase/CMakeLists.txt +3 -1
- data/ext/couchbase/core/error_context/base_error_context.hxx +32 -0
- data/ext/couchbase/core/error_context/key_value.cxx +1 -0
- data/ext/couchbase/core/error_context/key_value.hxx +23 -0
- data/ext/couchbase/core/error_context/key_value_error_context.hxx +35 -0
- data/ext/couchbase/core/error_context/subdocument_error_context.hxx +41 -0
- data/ext/couchbase/core/impl/binary_collection.cxx +123 -88
- data/ext/couchbase/core/impl/collection.cxx +416 -189
- data/ext/couchbase/core/impl/error.cxx +29 -4
- data/ext/couchbase/core/impl/invoke_with_node_id.hxx +44 -0
- data/ext/couchbase/core/impl/node_id.cxx +110 -0
- data/ext/couchbase/core/impl/node_id.hxx +40 -0
- data/ext/couchbase/core/impl/public_cluster.cxx +118 -5
- data/ext/couchbase/core/io/configuration_belongs_to_session.hxx +67 -0
- data/ext/couchbase/core/io/http_session_manager.hxx +97 -57
- data/ext/couchbase/core/io/mcbp_session.cxx +7 -16
- data/ext/couchbase/core/io/streams.cxx +143 -12
- data/ext/couchbase/core/io/streams.hxx +6 -0
- data/ext/couchbase/core/operations/document_get_all_replicas.hxx +14 -4
- data/ext/couchbase/core/operations/document_get_projected.cxx +3 -4
- data/ext/couchbase/core/operations/document_lookup_in_all_replicas.hxx +4 -0
- data/ext/couchbase/core/operations/document_query.cxx +12 -12
- data/ext/couchbase/core/operations/management/bucket_create.cxx +37 -38
- data/ext/couchbase/core/operations/management/bucket_update.cxx +26 -28
- data/ext/couchbase/core/operations/management/collection_create.cxx +11 -11
- data/ext/couchbase/core/operations/management/collection_drop.cxx +11 -11
- data/ext/couchbase/core/operations/management/collection_update.cxx +11 -11
- data/ext/couchbase/core/operations/management/error_utils.cxx +9 -6
- data/ext/couchbase/core/operations/management/query_index_create.cxx +5 -4
- data/ext/couchbase/core/operations/management/query_index_drop.cxx +7 -6
- data/ext/couchbase/core/operations/management/scope_create.cxx +12 -13
- data/ext/couchbase/core/operations/management/scope_drop.cxx +8 -9
- data/ext/couchbase/core/topology/configuration.cxx +21 -0
- data/ext/couchbase/core/topology/configuration.hxx +28 -0
- data/ext/couchbase/core/transactions/attempt_context_impl.cxx +254 -178
- data/ext/couchbase/core/transactions/attempt_context_impl.hxx +5 -1
- data/ext/couchbase/core/transactions/transactions_cleanup.cxx +42 -7
- data/ext/couchbase/core/transactions/waitable_op_list.hxx +13 -2
- data/ext/couchbase/core/utils/contains_string.cxx +61 -0
- data/ext/couchbase/core/utils/contains_string.hxx +26 -0
- data/ext/couchbase/couchbase/cluster.hxx +33 -0
- data/ext/couchbase/couchbase/collection.hxx +73 -0
- data/ext/couchbase/couchbase/error.hxx +16 -0
- data/ext/couchbase/couchbase/node_id.hxx +123 -0
- data/ext/couchbase/couchbase/node_id_for_options.hxx +61 -0
- data/ext/couchbase/couchbase/node_ids_options.hxx +62 -0
- data/ext/couchbase/couchbase/result.hxx +42 -0
- data/ext/rcb_crud.cxx +2 -0
- data/ext/rcb_logger.cxx +15 -17
- data/lib/couchbase/cluster.rb +16 -14
- data/lib/couchbase/datastructures/couchbase_list.rb +1 -0
- data/lib/couchbase/datastructures/couchbase_map.rb +1 -0
- data/lib/couchbase/datastructures/couchbase_queue.rb +1 -0
- data/lib/couchbase/datastructures/couchbase_set.rb +1 -0
- data/lib/couchbase/errors.rb +1 -1
- data/lib/couchbase/json_transcoder.rb +1 -1
- data/lib/couchbase/options.rb +9 -4
- data/lib/couchbase/protostellar/client.rb +0 -2
- data/lib/couchbase/protostellar/cluster.rb +4 -0
- data/lib/couchbase/protostellar/generated/admin/analytics/v1/analytics_pb.rb +54 -0
- data/lib/couchbase/protostellar/generated/admin/analytics/v1/analytics_services_pb.rb +51 -0
- data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_pb.rb +5 -24
- data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_services_pb.rb +16 -0
- data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_pb.rb +5 -24
- data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_services_pb.rb +16 -0
- data/lib/couchbase/protostellar/generated/admin/query/v1/query_pb.rb +5 -24
- data/lib/couchbase/protostellar/generated/admin/query/v1/query_services_pb.rb +18 -0
- data/lib/couchbase/protostellar/generated/admin/search/v1/search_pb.rb +2 -23
- data/lib/couchbase/protostellar/generated/admin/search/v1/search_services_pb.rb +23 -0
- data/lib/couchbase/protostellar/generated/analytics/v1/analytics_pb.rb +4 -25
- data/lib/couchbase/protostellar/generated/analytics/v1/analytics_services_pb.rb +10 -0
- data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_pb.rb +6 -25
- data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_services_pb.rb +18 -0
- data/lib/couchbase/protostellar/generated/internal/xdcr/v1/xdcr_pb.rb +46 -0
- data/lib/couchbase/protostellar/generated/internal/xdcr/v1/xdcr_services_pb.rb +56 -0
- data/lib/couchbase/protostellar/generated/kv/v1/kv_pb.rb +3 -26
- data/lib/couchbase/protostellar/generated/kv/v1/kv_services_pb.rb +47 -0
- data/lib/couchbase/protostellar/generated/query/v1/query_pb.rb +4 -26
- data/lib/couchbase/protostellar/generated/query/v1/query_services_pb.rb +10 -0
- data/lib/couchbase/protostellar/generated/routing/v2/routing_pb.rb +26 -0
- data/lib/couchbase/protostellar/generated/routing/v2/routing_services_pb.rb +43 -0
- data/lib/couchbase/protostellar/generated/search/v1/search_pb.rb +5 -26
- data/lib/couchbase/protostellar/generated/search/v1/search_services_pb.rb +11 -0
- data/lib/couchbase/protostellar/generated/transactions/v1/transactions_pb.rb +2 -23
- data/lib/couchbase/protostellar/generated/transactions/v1/transactions_services_pb.rb +30 -0
- data/lib/couchbase/protostellar/generated/view/v1/view_pb.rb +2 -23
- data/lib/couchbase/protostellar/generated/view/v1/view_services_pb.rb +9 -0
- data/lib/couchbase/protostellar/request_generator/admin/collection.rb +4 -2
- data/lib/couchbase/protostellar/request_generator/admin/query.rb +2 -0
- data/lib/couchbase/protostellar/request_generator/kv.rb +1 -1
- data/lib/couchbase/protostellar/scope.rb +4 -0
- data/lib/couchbase/utils/observability.rb +10 -4
- data/lib/couchbase/version.rb +1 -1
- metadata +20 -7
- data/lib/couchbase/protostellar/generated/routing/v1/routing_pb.rb +0 -52
- data/lib/couchbase/protostellar/generated/routing/v1/routing_services_pb.rb +0 -30
|
@@ -251,45 +251,45 @@ public:
|
|
|
251
251
|
app_telemetry_meter_,
|
|
252
252
|
options_.default_timeout_for(request.type));
|
|
253
253
|
#endif
|
|
254
|
-
cmd->start(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
254
|
+
cmd->start(
|
|
255
|
+
[start = std::chrono::steady_clock::now(),
|
|
256
|
+
self = shared_from_this(),
|
|
257
|
+
type,
|
|
258
|
+
cmd,
|
|
259
|
+
handler = collector->build_reporter()](operations::http_noop_response&& resp) {
|
|
260
|
+
diag::ping_state state = diag::ping_state::ok;
|
|
261
|
+
std::optional<std::string> error{};
|
|
262
|
+
if (auto ec = resp.ctx.ec; ec) {
|
|
263
|
+
if (ec == errc::common::unambiguous_timeout ||
|
|
264
|
+
ec == errc::common::ambiguous_timeout) {
|
|
265
|
+
state = diag::ping_state::timeout;
|
|
266
|
+
} else {
|
|
267
|
+
state = diag::ping_state::error;
|
|
268
|
+
}
|
|
269
|
+
error.emplace(fmt::format("code={}, message={}, http_code={}",
|
|
270
|
+
ec.value(),
|
|
271
|
+
ec.message(),
|
|
272
|
+
resp.ctx.http_status));
|
|
268
273
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
state,
|
|
289
|
-
{},
|
|
290
|
-
error });
|
|
291
|
-
self->check_in(type, cmd->session_);
|
|
292
|
-
});
|
|
274
|
+
auto remote_address = cmd->session_->remote_address();
|
|
275
|
+
// If not connected, the remote address will be empty. Better to
|
|
276
|
+
// give the user some context on the "attempted" remote address.
|
|
277
|
+
if (remote_address.empty()) {
|
|
278
|
+
remote_address =
|
|
279
|
+
fmt::format("{}:{}", cmd->session_->hostname(), cmd->session_->port());
|
|
280
|
+
}
|
|
281
|
+
handler->report(
|
|
282
|
+
diag::endpoint_ping_info{ type,
|
|
283
|
+
cmd->session_->id(),
|
|
284
|
+
std::chrono::duration_cast<std::chrono::microseconds>(
|
|
285
|
+
std::chrono::steady_clock::now() - start),
|
|
286
|
+
remote_address,
|
|
287
|
+
cmd->session_->local_address(),
|
|
288
|
+
state,
|
|
289
|
+
{},
|
|
290
|
+
error });
|
|
291
|
+
self->check_in(type, cmd->session_);
|
|
292
|
+
});
|
|
293
293
|
|
|
294
294
|
cmd->set_command_session(session);
|
|
295
295
|
if (!session->is_connected()) {
|
|
@@ -402,9 +402,20 @@ public:
|
|
|
402
402
|
return;
|
|
403
403
|
}
|
|
404
404
|
if (!session->is_connected()) {
|
|
405
|
-
|
|
406
|
-
|
|
405
|
+
{
|
|
406
|
+
std::scoped_lock lock(sessions_mutex_);
|
|
407
|
+
if (auto pend_it = pending_sessions_.find(type); pend_it != pending_sessions_.end()) {
|
|
408
|
+
pend_it->second.remove_if([id = session->id()](const auto& s) {
|
|
409
|
+
return !s || s->id() == id;
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
CB_LOG_DEBUG("{} HTTP session never connected. Ensured session is not in pending.",
|
|
414
|
+
session->log_prefix());
|
|
415
|
+
return;
|
|
407
416
|
}
|
|
417
|
+
bool should_stop = false;
|
|
418
|
+
std::chrono::milliseconds idle_timeout{};
|
|
408
419
|
{
|
|
409
420
|
std::scoped_lock lock(config_mutex_);
|
|
410
421
|
if (!session->keep_alive() || !config_.has_node(options_.network,
|
|
@@ -412,22 +423,34 @@ public:
|
|
|
412
423
|
options_.enable_tls,
|
|
413
424
|
session->hostname(),
|
|
414
425
|
session->port())) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
426
|
+
should_stop = true;
|
|
427
|
+
} else {
|
|
428
|
+
idle_timeout = options_.idle_http_connection_timeout;
|
|
418
429
|
}
|
|
419
430
|
}
|
|
431
|
+
if (should_stop) {
|
|
432
|
+
return asio::post(session->get_executor(), [session]() {
|
|
433
|
+
session->stop();
|
|
434
|
+
});
|
|
435
|
+
}
|
|
420
436
|
if (!session->is_stopped()) {
|
|
421
|
-
|
|
437
|
+
// set_idle() arms the idle timer via async_wait — it never calls stop() inline,
|
|
438
|
+
// so on_stop cannot re-enter sessions_mutex_ here. It must precede publication to
|
|
439
|
+
// idle_sessions_ so a concurrent check_out's reset_idle() finds a pending timer.
|
|
440
|
+
session->set_idle(idle_timeout);
|
|
422
441
|
CB_LOG_DEBUG("{} put HTTP session back to idle connections", session->log_prefix());
|
|
423
442
|
std::scoped_lock lock(sessions_mutex_);
|
|
424
443
|
idle_sessions_[type].push_back(session);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
444
|
+
if (auto busy_it = busy_sessions_.find(type); busy_it != busy_sessions_.end()) {
|
|
445
|
+
busy_it->second.remove_if([id = session->id()](const auto& s) -> bool {
|
|
446
|
+
return !s || s->id() == id;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (auto pend_it = pending_sessions_.find(type); pend_it != pending_sessions_.end()) {
|
|
450
|
+
pend_it->second.remove_if([id = session->id()](const auto& s) -> bool {
|
|
451
|
+
return !s || s->id() == id;
|
|
452
|
+
});
|
|
453
|
+
}
|
|
431
454
|
}
|
|
432
455
|
}
|
|
433
456
|
|
|
@@ -741,13 +764,30 @@ private:
|
|
|
741
764
|
}
|
|
742
765
|
|
|
743
766
|
session->on_stop([type, id = session->id(), self = this->shared_from_this()]() {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
767
|
+
// Declared outside the lock so destructors run after sessions_mutex_ is released.
|
|
768
|
+
// cppcheck-suppress variableScope
|
|
769
|
+
std::vector<std::shared_ptr<http_session>> dropped;
|
|
770
|
+
{
|
|
771
|
+
const std::scoped_lock inner_lock(self->sessions_mutex_);
|
|
772
|
+
for (auto* map :
|
|
773
|
+
{ &self->busy_sessions_, &self->idle_sessions_, &self->pending_sessions_ }) {
|
|
774
|
+
auto map_it = map->find(type);
|
|
775
|
+
if (map_it == map->end()) {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
auto& list = map_it->second;
|
|
779
|
+
for (auto it = list.begin(); it != list.end();) {
|
|
780
|
+
if (!*it || (*it)->id() == id) {
|
|
781
|
+
if (*it) {
|
|
782
|
+
dropped.push_back(std::move(*it));
|
|
783
|
+
}
|
|
784
|
+
it = list.erase(it);
|
|
785
|
+
} else {
|
|
786
|
+
++it;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
751
791
|
});
|
|
752
792
|
return session;
|
|
753
793
|
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
#ifdef COUCHBASE_CXX_CLIENT_COLUMNAR
|
|
23
23
|
#include "core/columnar/background_bootstrap_listener.hxx"
|
|
24
24
|
#endif
|
|
25
|
+
#include "configuration_belongs_to_session.hxx"
|
|
25
26
|
#include "core/config_listener.hxx"
|
|
26
27
|
#include "core/diagnostics.hxx"
|
|
27
28
|
#include "core/impl/bootstrap_error.hxx"
|
|
@@ -634,14 +635,9 @@ class mcbp_session_impl
|
|
|
634
635
|
}
|
|
635
636
|
}
|
|
636
637
|
std::optional<topology::configuration> config = req.body().config();
|
|
637
|
-
if (session_ && config.has_value()
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
// TODO(CXXCBC-549)
|
|
641
|
-
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
642
|
-
session_->bucket_name_.value() == req.body().bucket())) {
|
|
643
|
-
session_->update_configuration(std::move(config.value()));
|
|
644
|
-
}
|
|
638
|
+
if (session_ && config.has_value() &&
|
|
639
|
+
configuration_belongs_to_session(config->bucket, session_->bucket_name_)) {
|
|
640
|
+
session_->update_configuration(std::move(config.value()));
|
|
645
641
|
}
|
|
646
642
|
} break;
|
|
647
643
|
default:
|
|
@@ -824,14 +820,9 @@ class mcbp_session_impl
|
|
|
824
820
|
}
|
|
825
821
|
}
|
|
826
822
|
std::optional<topology::configuration> config = req.body().config();
|
|
827
|
-
if (session_ && config.has_value()
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
// TODO(CXXCBC-549)
|
|
831
|
-
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
|
|
832
|
-
session_->bucket_name_.value() == req.body().bucket())) {
|
|
833
|
-
session_->update_configuration(std::move(config.value()));
|
|
834
|
-
}
|
|
823
|
+
if (session_ && config.has_value() &&
|
|
824
|
+
configuration_belongs_to_session(config->bucket, session_->bucket_name_)) {
|
|
825
|
+
session_->update_configuration(std::move(config.value()));
|
|
835
826
|
}
|
|
836
827
|
} break;
|
|
837
828
|
default:
|
|
@@ -23,12 +23,128 @@
|
|
|
23
23
|
#include <asio/error.hpp>
|
|
24
24
|
#include <asio/ssl.hpp>
|
|
25
25
|
|
|
26
|
+
#include <cerrno>
|
|
27
|
+
|
|
28
|
+
#if defined(_WIN32)
|
|
29
|
+
#include <process.h>
|
|
30
|
+
#else
|
|
31
|
+
#include <unistd.h>
|
|
32
|
+
#endif
|
|
33
|
+
|
|
26
34
|
namespace couchbase::core::io
|
|
27
35
|
{
|
|
36
|
+
namespace
|
|
37
|
+
{
|
|
38
|
+
auto
|
|
39
|
+
current_process_id() -> long long
|
|
40
|
+
{
|
|
41
|
+
#if defined(_WIN32)
|
|
42
|
+
return static_cast<long long>(::_getpid());
|
|
43
|
+
#else
|
|
44
|
+
return static_cast<long long>(::getpid());
|
|
45
|
+
#endif
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Release a socket this process inherited across fork(2), without disturbing the
|
|
50
|
+
* connection the parent is still using.
|
|
51
|
+
*
|
|
52
|
+
* Two things differ from the ordinary close path, and both matter:
|
|
53
|
+
*
|
|
54
|
+
* * No shutdown(2). It acts on the open file description, which fork(2) shares,
|
|
55
|
+
* so it would send FIN and break the parent's connection. close(2) drops only
|
|
56
|
+
* our own descriptor; the peer sees nothing.
|
|
57
|
+
* * release() before closing, rather than asio's close(). asio's close() skips
|
|
58
|
+
* EPOLL_CTL_DEL on the assumption that closing a descriptor removes its epoll
|
|
59
|
+
* registration. Per epoll(7) that happens only once *every* descriptor
|
|
60
|
+
* referring to the description is closed -- and the parent still holds one --
|
|
61
|
+
* so the registration would outlive the descriptor_state asio recycles, which
|
|
62
|
+
* is precisely the stale-pointer shape the reactor faults on. release() emits
|
|
63
|
+
* the EPOLL_CTL_DEL explicitly and hands back the raw descriptor to close.
|
|
64
|
+
*
|
|
65
|
+
* release() also completes any outstanding asynchronous operations on this socket
|
|
66
|
+
* with operation_aborted, which is what we want: those reads belong to the parent.
|
|
67
|
+
*/
|
|
68
|
+
// Takes the basic_socket base rather than tcp::socket so the same code serves both
|
|
69
|
+
// the plain socket and a TLS stream's lowest_layer(), whose type is that base.
|
|
70
|
+
void
|
|
71
|
+
detach_inherited_socket(asio::basic_socket<asio::ip::tcp>& socket, asio::error_code& ec)
|
|
72
|
+
{
|
|
73
|
+
if (!socket.is_open()) {
|
|
74
|
+
ec = asio::error::bad_descriptor;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const auto handle = socket.release(ec);
|
|
79
|
+
if (ec) {
|
|
80
|
+
// Report it. Do not reach for asio's close() here: that is the very call
|
|
81
|
+
// whose epoll behaviour makes it unsafe on an inherited descriptor, and the
|
|
82
|
+
// descriptor goes away when this process exits anyway.
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#if defined(_WIN32)
|
|
87
|
+
// Unreachable: without fork(2) no socket is ever inherited, so the owner pid
|
|
88
|
+
// always matches and close() never takes this path.
|
|
89
|
+
(void)handle;
|
|
90
|
+
ec = asio::error::operation_not_supported;
|
|
91
|
+
#else
|
|
92
|
+
if (::close(handle) != 0) {
|
|
93
|
+
ec.assign(errno, asio::error::get_system_category());
|
|
94
|
+
}
|
|
95
|
+
#endif
|
|
96
|
+
}
|
|
97
|
+
} // namespace
|
|
98
|
+
|
|
99
|
+
auto
|
|
100
|
+
configure_tls_handshake(asio::ssl::stream<asio::ip::tcp::socket>& stream,
|
|
101
|
+
const std::string& hostname) -> std::error_code
|
|
102
|
+
{
|
|
103
|
+
if (hostname.empty()) {
|
|
104
|
+
// Fail closed: a TLS client connection must have a hostname to verify the
|
|
105
|
+
// server's identity against. Returning success here would silently fall back
|
|
106
|
+
// to CA-only validation -- the CWE-297 problem this function exists to prevent.
|
|
107
|
+
return asio::error::make_error_code(asio::error::invalid_argument);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Send SNI so that name-based / multi-tenant TLS endpoints present the correct
|
|
111
|
+
// certificate for this hostname.
|
|
112
|
+
{
|
|
113
|
+
// SSL_set_tlsext_host_name() is a macro that expands to an internal C-style
|
|
114
|
+
// cast to void*, which trips -Wold-style-cast; suppress it locally.
|
|
115
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
116
|
+
#pragma GCC diagnostic push
|
|
117
|
+
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
118
|
+
#endif
|
|
119
|
+
ERR_clear_error();
|
|
120
|
+
const auto sni_result = SSL_set_tlsext_host_name(stream.native_handle(), hostname.c_str());
|
|
121
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
122
|
+
#pragma GCC diagnostic pop
|
|
123
|
+
#endif
|
|
124
|
+
if (sni_result != 1) {
|
|
125
|
+
// Prefer the underlying SSL reason (if any) so failures are diagnosable,
|
|
126
|
+
// and fall back to a generic error when the SSL error queue is empty.
|
|
127
|
+
if (const auto reason = ERR_get_error(); reason != 0) {
|
|
128
|
+
return { static_cast<int>(reason), asio::error::get_ssl_category() };
|
|
129
|
+
}
|
|
130
|
+
return asio::error::make_error_code(asio::error::invalid_argument);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Verify that the presented certificate actually identifies `hostname`
|
|
135
|
+
// (SAN/CN). asio::ssl::verify_peer on its own only validates the certificate
|
|
136
|
+
// chain, not the name. In verify_none mode OpenSSL ignores the callback
|
|
137
|
+
// result, so this has no effect there.
|
|
138
|
+
std::error_code ec{};
|
|
139
|
+
stream.set_verify_callback(asio::ssl::host_name_verification(hostname), ec);
|
|
140
|
+
return ec;
|
|
141
|
+
}
|
|
142
|
+
|
|
28
143
|
stream_impl::stream_impl(asio::io_context& ctx, bool is_tls)
|
|
29
144
|
: strand_(asio::make_strand(ctx))
|
|
30
145
|
, tls_(is_tls)
|
|
31
146
|
, id_(uuid::to_string(uuid::random()))
|
|
147
|
+
, owner_pid_(current_process_id())
|
|
32
148
|
{
|
|
33
149
|
}
|
|
34
150
|
|
|
@@ -79,12 +195,20 @@ plain_stream_impl::close(utils::movable_function<void(std::error_code)>&& handle
|
|
|
79
195
|
if (!stream_) {
|
|
80
196
|
return handler(asio::error::bad_descriptor);
|
|
81
197
|
}
|
|
82
|
-
return asio::post(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
198
|
+
return asio::post(
|
|
199
|
+
strand_, [stream = std::move(stream_), owner_pid = owner_pid_, handler = std::move(handler)]() {
|
|
200
|
+
asio::error_code ec{};
|
|
201
|
+
// Decide here rather than when this was posted: io_.stop() at fork_prepare
|
|
202
|
+
// does not drain the queue, so a close queued before the fork runs in the
|
|
203
|
+
// child once the io_context is restarted, and there the answer differs.
|
|
204
|
+
if (owner_pid != current_process_id()) {
|
|
205
|
+
detach_inherited_socket(*stream, ec);
|
|
206
|
+
} else {
|
|
207
|
+
stream->shutdown(asio::socket_base::shutdown_both, ec);
|
|
208
|
+
stream->close(ec);
|
|
209
|
+
}
|
|
210
|
+
handler(ec);
|
|
211
|
+
});
|
|
88
212
|
}
|
|
89
213
|
|
|
90
214
|
void
|
|
@@ -178,12 +302,19 @@ tls_stream_impl::close(utils::movable_function<void(std::error_code)>&& handler)
|
|
|
178
302
|
if (!stream_) {
|
|
179
303
|
return handler(asio::error::bad_descriptor);
|
|
180
304
|
}
|
|
181
|
-
return asio::post(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
305
|
+
return asio::post(
|
|
306
|
+
strand_, [stream = std::move(stream_), owner_pid = owner_pid_, handler = std::move(handler)]() {
|
|
307
|
+
asio::error_code ec{};
|
|
308
|
+
// See plain_stream_impl::close(): the inheritance test belongs here, not at
|
|
309
|
+
// post time.
|
|
310
|
+
if (owner_pid != current_process_id()) {
|
|
311
|
+
detach_inherited_socket(stream->lowest_layer(), ec);
|
|
312
|
+
} else {
|
|
313
|
+
stream->lowest_layer().shutdown(asio::socket_base::shutdown_both, ec);
|
|
314
|
+
stream->lowest_layer().close(ec);
|
|
315
|
+
}
|
|
316
|
+
handler(ec);
|
|
317
|
+
});
|
|
187
318
|
}
|
|
188
319
|
|
|
189
320
|
void
|
|
@@ -65,6 +65,12 @@ protected:
|
|
|
65
65
|
asio::strand<asio::io_context::executor_type> strand_;
|
|
66
66
|
bool tls_;
|
|
67
67
|
std::string id_{};
|
|
68
|
+
// Identifies the process that opened this socket. close() compares it against
|
|
69
|
+
// the process running at that moment to tell a socket inherited across fork(2)
|
|
70
|
+
// apart from one this process opened itself, because an inherited socket must not
|
|
71
|
+
// be shut down: shutdown(2) acts on the open file description, which fork(2)
|
|
72
|
+
// shares, so it would tear down a connection the parent is still using.
|
|
73
|
+
long long owner_pid_;
|
|
68
74
|
|
|
69
75
|
public:
|
|
70
76
|
stream_impl(asio::io_context& ctx, bool is_tls);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#pragma once
|
|
19
19
|
|
|
20
20
|
#include <couchbase/error_codes.hxx>
|
|
21
|
+
#include <couchbase/node_id.hxx>
|
|
21
22
|
|
|
22
23
|
#include "core/error_context/key_value.hxx"
|
|
23
24
|
#include "core/impl/get_replica.hxx"
|
|
@@ -40,6 +41,7 @@ struct get_all_replicas_response {
|
|
|
40
41
|
couchbase::cas cas{};
|
|
41
42
|
std::uint32_t flags{};
|
|
42
43
|
bool replica{ true };
|
|
44
|
+
couchbase::node_id dispatched_to_node_id{};
|
|
43
45
|
};
|
|
44
46
|
key_value_error_context ctx{};
|
|
45
47
|
std::vector<entry> entries{};
|
|
@@ -156,8 +158,12 @@ struct get_all_replicas_request {
|
|
|
156
158
|
return;
|
|
157
159
|
}
|
|
158
160
|
} else {
|
|
159
|
-
ctx->result_.emplace_back(
|
|
160
|
-
std::move(resp.value),
|
|
161
|
+
ctx->result_.emplace_back(
|
|
162
|
+
get_all_replicas_response::entry{ std::move(resp.value),
|
|
163
|
+
resp.cas,
|
|
164
|
+
resp.flags,
|
|
165
|
+
true /* replica */,
|
|
166
|
+
resp.ctx.last_dispatched_to_node_id() });
|
|
161
167
|
}
|
|
162
168
|
if (ctx->expected_responses_ == 0) {
|
|
163
169
|
ctx->done_ = true;
|
|
@@ -203,8 +209,12 @@ struct get_all_replicas_request {
|
|
|
203
209
|
return;
|
|
204
210
|
}
|
|
205
211
|
} else {
|
|
206
|
-
ctx->result_.emplace_back(
|
|
207
|
-
std::move(resp.value),
|
|
212
|
+
ctx->result_.emplace_back(
|
|
213
|
+
get_all_replicas_response::entry{ std::move(resp.value),
|
|
214
|
+
resp.cas,
|
|
215
|
+
resp.flags,
|
|
216
|
+
false /* active */,
|
|
217
|
+
resp.ctx.last_dispatched_to_node_id() });
|
|
208
218
|
}
|
|
209
219
|
if (ctx->expected_responses_ == 0) {
|
|
210
220
|
ctx->done_ = true;
|
|
@@ -210,14 +210,13 @@ get_projected_request::make_response(key_value_error_context&& ctx,
|
|
|
210
210
|
response.ctx.override_ec(errc::common::parsing_failure);
|
|
211
211
|
return response;
|
|
212
212
|
}
|
|
213
|
-
tao::json::value new_doc;
|
|
213
|
+
tao::json::value new_doc = tao::json::empty_object;
|
|
214
214
|
for (const auto& projection : projections) {
|
|
215
215
|
if (auto value_to_apply = subdoc_lookup(full_doc, projection)) {
|
|
216
216
|
subdoc_apply_projection(new_doc, projection, *value_to_apply, preserve_array_indexes);
|
|
217
|
-
} else {
|
|
218
|
-
response.ctx.override_ec(errc::key_value::path_not_found);
|
|
219
|
-
return response;
|
|
220
217
|
}
|
|
218
|
+
// We ignore paths that were not found, similar to how we ignore them in the subdoc
|
|
219
|
+
// multi-lookup below.
|
|
221
220
|
}
|
|
222
221
|
response.value = utils::json::generate_binary(new_doc);
|
|
223
222
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
#include <couchbase/codec/encoded_value.hxx>
|
|
30
30
|
#include <couchbase/error_codes.hxx>
|
|
31
|
+
#include <couchbase/node_id.hxx>
|
|
31
32
|
|
|
32
33
|
#include <functional>
|
|
33
34
|
#include <memory>
|
|
@@ -50,6 +51,7 @@ struct lookup_in_all_replicas_response {
|
|
|
50
51
|
couchbase::cas cas{};
|
|
51
52
|
bool deleted{ false };
|
|
52
53
|
bool is_replica{ true };
|
|
54
|
+
couchbase::node_id dispatched_to_node_id{};
|
|
53
55
|
};
|
|
54
56
|
subdocument_error_context ctx{};
|
|
55
57
|
std::vector<entry> entries{};
|
|
@@ -203,6 +205,7 @@ struct lookup_in_all_replicas_request {
|
|
|
203
205
|
top_entry.cas = resp.cas;
|
|
204
206
|
top_entry.deleted = resp.deleted;
|
|
205
207
|
top_entry.is_replica = true;
|
|
208
|
+
top_entry.dispatched_to_node_id = resp.ctx.last_dispatched_to_node_id();
|
|
206
209
|
for (auto& field : resp.fields) {
|
|
207
210
|
lookup_in_all_replicas_response::entry::lookup_in_entry lookup_in_entry{};
|
|
208
211
|
lookup_in_entry.path = field.path;
|
|
@@ -267,6 +270,7 @@ struct lookup_in_all_replicas_request {
|
|
|
267
270
|
top_entry.cas = resp.cas;
|
|
268
271
|
top_entry.deleted = resp.deleted;
|
|
269
272
|
top_entry.is_replica = false;
|
|
273
|
+
top_entry.dispatched_to_node_id = resp.ctx.last_dispatched_to_node_id();
|
|
270
274
|
for (auto& field : resp.fields) {
|
|
271
275
|
lookup_in_all_replicas_response::entry::lookup_in_entry lookup_in_entry{};
|
|
272
276
|
lookup_in_entry.path = field.path;
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
#include "core/cluster_options.hxx"
|
|
21
21
|
#include "core/logger/logger.hxx"
|
|
22
22
|
#include "core/operations/management/error_utils.hxx"
|
|
23
|
+
#include "core/utils/contains_string.hxx"
|
|
23
24
|
#include "core/utils/duration_parser.hxx"
|
|
24
25
|
#include "core/utils/json.hxx"
|
|
25
26
|
|
|
@@ -28,13 +29,11 @@
|
|
|
28
29
|
#include <gsl/assert>
|
|
29
30
|
#include <tao/json/value.hpp>
|
|
30
31
|
|
|
31
|
-
#include <regex>
|
|
32
|
-
|
|
33
32
|
namespace couchbase::core::operations
|
|
34
33
|
{
|
|
35
34
|
auto
|
|
36
|
-
query_request::encode_to(query_request::encoded_request_type& encoded,
|
|
37
|
-
|
|
35
|
+
query_request::encode_to(query_request::encoded_request_type& encoded, http_context& context)
|
|
36
|
+
-> std::error_code
|
|
38
37
|
{
|
|
39
38
|
ctx_.emplace(context);
|
|
40
39
|
tao::json::value body{
|
|
@@ -209,8 +208,8 @@ query_request::encode_to(query_request::encoded_request_type& encoded,
|
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
auto
|
|
212
|
-
query_request::make_response(error_context::query&& ctx,
|
|
213
|
-
|
|
211
|
+
query_request::make_response(error_context::query&& ctx, const encoded_response_type& encoded)
|
|
212
|
+
-> query_response
|
|
214
213
|
{
|
|
215
214
|
query_response response{ std::move(ctx) };
|
|
216
215
|
response.ctx.statement = statement;
|
|
@@ -374,13 +373,14 @@ query_request::make_response(error_context::query&& ctx,
|
|
|
374
373
|
response.ctx.ec = errc::common::index_exists;
|
|
375
374
|
break;
|
|
376
375
|
case 5000: /* IKey: "Internal Error" */
|
|
377
|
-
if (
|
|
378
|
-
|
|
376
|
+
if (utils::contains_string(response.ctx.first_error_message, "index", true) &&
|
|
377
|
+
utils::contains_string(response.ctx.first_error_message, "already exist", true)) {
|
|
379
378
|
response.ctx.ec = errc::common::index_exists;
|
|
380
|
-
} else if (response.ctx.first_error_message
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
379
|
+
} else if (utils::contains_string(response.ctx.first_error_message,
|
|
380
|
+
"Index does not exist") ||
|
|
381
|
+
(utils::contains_string(response.ctx.first_error_message, "index", true) &&
|
|
382
|
+
utils::contains_string(
|
|
383
|
+
response.ctx.first_error_message, "not found", true))) {
|
|
384
384
|
response.ctx.ec = errc::common::index_not_found;
|
|
385
385
|
} else if (response.ctx.first_error_message.find("Bucket Not Found") !=
|
|
386
386
|
std::string::npos) {
|