Skip to content

Commit b7b971c

Browse files
committed
RCBC-512: Invoke fork hooks to protect SDK internal state
1 parent 3f775de commit b7b971c

File tree

11 files changed

+135
-9
lines changed

11 files changed

+135
-9
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
/.yardoc
1212
/_yardoc/
1313
/cmake-build-*/
14-
/build-*/
14+
/build*
1515
/coverage/
1616
/doc/
1717
/ext/cache/

ext/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/.idea/sonarlint/
22
/.idea/editor.xml
33
/.idea/workspace.xml
4-
/build/
4+
/build*
55
/cmake-build-*/
66
/cmake-build-report.tar.gz
77
/revisions.rb

ext/couchbase

ext/couchbase.cxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ __declspec(dllexport)
4141
void
4242
Init_libcouchbase(void)
4343
{
44+
couchbase::ruby::install_terminate_handler();
4445
couchbase::ruby::init_logger();
4546

4647
VALUE mCouchbase = rb_define_module("Couchbase");

ext/extconf.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ def sys(*cmd)
154154
File.join(Dir.tmpdir, "cb-#{build_type}-#{RUBY_VERSION}-#{RUBY_PATCHLEVEL}-#{RUBY_PLATFORM}-#{SDK_VERSION}")
155155
FileUtils.rm_rf(build_dir, verbose: true) unless ENV['CB_PRESERVE_BUILD_DIR']
156156
FileUtils.mkdir_p(build_dir, verbose: true)
157+
if ENV["CB_CREATE_BUILD_DIR_LINK"]
158+
links = [
159+
File.expand_path(File.join(project_path, "..", "build")),
160+
File.expand_path(File.join(project_path, "build"))
161+
]
162+
links.each do |link|
163+
next if link == build_dir
164+
FileUtils.ln_sf(build_dir, link, verbose: true)
165+
end
166+
end
157167
Dir.chdir(build_dir) do
158168
puts "-- build #{build_type} extension #{SDK_VERSION} for ruby #{RUBY_VERSION}-#{RUBY_PATCHLEVEL}-#{RUBY_PLATFORM}"
159169
sys(cmake, *cmake_flags, "-B#{build_dir}", "-S#{project_path}")

ext/rcb_backend.cxx

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
#include <couchbase/cluster.hxx>
1919

20+
#include <couchbase/fork_event.hxx>
21+
#include <couchbase/ip_protocol.hxx>
22+
2023
#include <core/cluster.hxx>
2124
#include <core/logger/logger.hxx>
2225
#include <core/utils/connection_string.hxx>
@@ -25,11 +28,12 @@
2528
#include <spdlog/fmt/bundled/core.h>
2629

2730
#include <future>
31+
#include <list>
2832
#include <memory>
33+
#include <mutex>
2934

3035
#include <ruby.h>
3136

32-
#include "couchbase/ip_protocol.hxx"
3337
#include "rcb_backend.hxx"
3438
#include "rcb_exceptions.hxx"
3539
#include "rcb_logger.hxx"
@@ -44,10 +48,79 @@ struct cb_backend_data {
4448
std::unique_ptr<cluster> instance{ nullptr };
4549
};
4650

51+
class instance_registry
52+
{
53+
public:
54+
void add(cluster* instance)
55+
{
56+
std::scoped_lock lock(instances_mutex_);
57+
known_instances_.push_back(instance);
58+
}
59+
60+
void remove(cluster* instance)
61+
{
62+
std::scoped_lock lock(instances_mutex_);
63+
known_instances_.remove(instance);
64+
}
65+
66+
void notify_fork(couchbase::fork_event event)
67+
{
68+
if (event != couchbase::fork_event::prepare) {
69+
init_logger();
70+
}
71+
72+
{
73+
std::scoped_lock lock(instances_mutex_);
74+
for (auto* instance : known_instances_) {
75+
instance->notify_fork(event);
76+
}
77+
}
78+
79+
if (event == couchbase::fork_event::prepare) {
80+
flush_logger();
81+
couchbase::core::logger::shutdown();
82+
}
83+
}
84+
85+
private:
86+
std::mutex instances_mutex_;
87+
std::list<cluster*> known_instances_;
88+
};
89+
90+
instance_registry instances;
91+
92+
VALUE
93+
cb_Backend_notify_fork(VALUE self, VALUE event)
94+
{
95+
static const auto id_prepare{ rb_intern("prepare") };
96+
static const auto id_parent{ rb_intern("parent") };
97+
static const auto id_child{ rb_intern("child") };
98+
99+
try {
100+
cb_check_type(event, T_SYMBOL);
101+
102+
if (rb_sym2id(event) == id_prepare) {
103+
instances.notify_fork(couchbase::fork_event::prepare);
104+
} else if (rb_sym2id(event) == id_parent) {
105+
instances.notify_fork(couchbase::fork_event::parent);
106+
} else if (rb_sym2id(event) == id_child) {
107+
instances.notify_fork(couchbase::fork_event::child);
108+
} else {
109+
throw ruby_exception(rb_eTypeError,
110+
rb_sprintf("unexpected fork event type %" PRIsVALUE "", event));
111+
}
112+
} catch (const ruby_exception& e) {
113+
rb_exc_raise(e.exception_object());
114+
}
115+
116+
return Qnil;
117+
}
118+
47119
void
48120
cb_backend_close(cb_backend_data* backend)
49121
{
50122
if (auto instance = std::move(backend->instance); instance) {
123+
instances.remove(instance.get());
51124
auto promise = std::make_shared<std::promise<void>>();
52125
auto f = promise->get_future();
53126
instance->close([promise = std::move(promise)]() mutable {
@@ -446,6 +519,7 @@ cb_Backend_open(VALUE self, VALUE connstr, VALUE credentials, VALUE options)
446519
error, fmt::format("failed to connect to the Couchbase Server \"{}\"", connection_string));
447520
}
448521
backend->instance = std::make_unique<couchbase::cluster>(std::move(cluster));
522+
instances.add(backend->instance.get());
449523
} catch (const std::system_error& se) {
450524
rb_exc_raise(cb_map_error_code(
451525
se.code(), fmt::format("failed to perform {}: {}", __func__, se.what()), false));
@@ -509,6 +583,8 @@ init_backend(VALUE mCouchbase)
509583
rb_define_method(cBackend, "open", cb_Backend_open, 3);
510584
rb_define_method(cBackend, "open_bucket", cb_Backend_open_bucket, 2);
511585
rb_define_method(cBackend, "close", cb_Backend_close, 0);
586+
587+
rb_define_singleton_method(cBackend, "notify_fork", cb_Backend_notify_fork, 1);
512588
return cBackend;
513589
}
514590

ext/rcb_logger.cxx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,18 @@ cb_Backend_install_logger_shim(VALUE self, VALUE logger, VALUE log_level)
268268
} // namespace
269269

270270
void
271-
init_logger()
271+
install_terminate_handler()
272272
{
273273
if (auto env_val =
274274
spdlog::details::os::getenv("COUCHBASE_BACKEND_DONT_INSTALL_TERMINATE_HANDLER");
275275
env_val.empty()) {
276276
core::platform::install_backtrace_terminate_handler();
277277
}
278+
}
279+
280+
void
281+
init_logger()
282+
{
278283
if (auto env_val = spdlog::details::os::getenv("COUCHBASE_BACKEND_DONT_USE_BUILTIN_LOGGER");
279284
env_val.empty()) {
280285
auto default_log_level = core::logger::level::info;

ext/rcb_logger.hxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
namespace couchbase::ruby
2424
{
25+
void
26+
install_terminate_handler();
27+
2528
void
2629
init_logger();
2730

ext/rcb_range_scan.cxx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,11 @@ cb_Backend_document_scan_create(VALUE self,
245245
std::promise<tl::expected<couchbase::core::topology::configuration, std::error_code>> promise;
246246
auto f = promise.get_future();
247247
cluster.with_bucket_configuration(
248-
bucket_name,
249-
[promise = std::move(promise)](
250-
std::error_code ec, const couchbase::core::topology::configuration& config) mutable {
248+
bucket_name, [promise = std::move(promise)](std::error_code ec, const auto& config) mutable {
251249
if (ec) {
252250
return promise.set_value(tl::unexpected(ec));
253251
}
254-
promise.set_value(config);
252+
promise.set_value(*config);
255253
});
256254
auto config = cb_wait_for_future(f);
257255
if (!config.has_value()) {

lib/couchbase.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
require "couchbase/version"
1818
require "couchbase/libcouchbase"
19+
require "couchbase/fork_hooks"
1920
require "couchbase/logger"
2021
require "couchbase/cluster"
2122
require "couchbase/deprecations"

lib/couchbase/fork_hooks.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 2020-2025 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+
module Couchbase
18+
module ForkHooks
19+
def _fork
20+
Couchbase::Backend.notify_fork(:prepare)
21+
pid = super
22+
if pid
23+
Couchbase::Backend.notify_fork(:parent)
24+
else
25+
Couchbase::Backend.notify_fork(:child)
26+
end
27+
pid
28+
end
29+
end
30+
end
31+
32+
Process.singleton_class.prepend(Couchbase::ForkHooks)

0 commit comments

Comments
 (0)