Skip to content

Commit fcae782

Browse files
authored
PCBC-984: fix compatiblity with pcntl_fork() (#157)
1 parent 4807e92 commit fcae782

10 files changed

+361
-21
lines changed

Couchbase/Cluster.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,34 @@ function (string $connectionString, ClusterOptions $options) {
111111
);
112112
}
113113

114+
/**
115+
* Notifies the SDK about usage of `fork(2)` syscall. Typically PHP exposes it using `pcntl_fork()` function, but
116+
* the library should have chance to properly close descriptors and reach safe point to allow forking the process.
117+
* This is not a problem in case of `proc_open()` as in this case the memory and descriptors are not inherited by
118+
* the child process.
119+
*
120+
* Allowed values for `$event` are:
121+
*
122+
* * ForkEvent::PREPARE - must be used before `fork()` to ensure the SDK reaches safe point
123+
* * ForkEvent::CHILD - must be used in the child process, the branch where `pcntl_fork()` returns zero
124+
* * ForkEvent::PARENT - must be used in the parent process, the branch where `pcntl_fork()` returns pid of the child process
125+
*
126+
* In case `pcntl_fork()` returns negative value, and the application decides to continue, `notifyFork(ForkEvent::PARENT)`
127+
* must be invoked to resume the SDK.
128+
*
129+
* @see https://www.php.net/manual/en/function.pcntl-fork.php
130+
* @see https://www.php.net/manual/en/function.proc-open.php
131+
*
132+
* @param string $event type of the event to send to the library (one of the constants in ForkEvent).
133+
* @return void
134+
*
135+
* @since 4.2.1
136+
*/
137+
public static function notifyFork(string $event)
138+
{
139+
return Extension\notifyFork($event);
140+
}
141+
114142
/**
115143
* Returns a new bucket object.
116144
*

Couchbase/ForkEvent.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2014-Present Couchbase, Inc.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
declare(strict_types=1);
20+
21+
namespace Couchbase;
22+
23+
/**
24+
* ForkEvent defines types of events, that can happen when forking the process.
25+
*
26+
* @see \Couchbase\Cluster::notifyFork()
27+
* @since 4.2.1
28+
*/
29+
interface ForkEvent
30+
{
31+
/**
32+
* Prepare the library for fork() call. This event should be used in the parent process before
33+
* invoking `pcntl_fork()`. Once \Couchbase\Cluster::notifyFork() the library reaches the safe
34+
* state when it is ready for fork() syscall (i.e. no background threads running, all operations
35+
* completed, etc.)
36+
*/
37+
public const PREPARE = "prepare";
38+
39+
/**
40+
* Resume progress of the child process. This usually gives the library the chance to open new
41+
* connections, and restart IO threads.
42+
*/
43+
public const CHILD = "child";
44+
45+
/**
46+
* Resume progress of the parent process. Typically parent process could continue using all
47+
* descriptors that were open before fork process, and also the library will restart background
48+
* IO threads.
49+
*/
50+
public const PARENT = "parent";
51+
}

src/php_couchbase.cxx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,22 @@ PHP_FUNCTION(version)
199199
couchbase::php::core_version(return_value);
200200
}
201201

202+
PHP_FUNCTION(notifyFork)
203+
{
204+
zend_string* fork_event = nullptr;
205+
206+
ZEND_PARSE_PARAMETERS_START(1, 1)
207+
Z_PARAM_STR(fork_event)
208+
ZEND_PARSE_PARAMETERS_END();
209+
210+
if (auto e = couchbase::php::notify_fork(fork_event); e.ec) {
211+
couchbase_throw_exception(e);
212+
RETURN_THROWS();
213+
}
214+
215+
RETURN_NULL();
216+
}
217+
202218
PHP_FUNCTION(createConnection)
203219
{
204220
zend_string* connection_hash = nullptr;
@@ -3226,6 +3242,10 @@ static PHP_MINFO_FUNCTION(couchbase)
32263242
ZEND_BEGIN_ARG_INFO_EX(ai_CouchbaseExtension_version, 0, 0, 0)
32273243
ZEND_END_ARG_INFO()
32283244

3245+
ZEND_BEGIN_ARG_INFO_EX(ai_CouchbaseExtension_notifyFork, 0, 0, 1)
3246+
ZEND_ARG_TYPE_INFO(0, forkEvent, IS_STRING, 0)
3247+
ZEND_END_ARG_INFO()
3248+
32293249
ZEND_BEGIN_ARG_INFO_EX(ai_CouchbaseExtension_clusterVersion, 0, 0, 2)
32303250
ZEND_ARG_INFO(0, connection)
32313251
ZEND_ARG_TYPE_INFO(0, bucketName, IS_STRING, 0)
@@ -3998,6 +4018,7 @@ ZEND_END_ARG_INFO()
39984018

39994019
// clang-format off
40004020
static zend_function_entry couchbase_functions[] = {
4021+
ZEND_NS_FE("Couchbase\\Extension", notifyFork, ai_CouchbaseExtension_notifyFork)
40014022
ZEND_NS_FE("Couchbase\\Extension", version, ai_CouchbaseExtension_version)
40024023
ZEND_NS_FE("Couchbase\\Extension", clusterVersion, ai_CouchbaseExtension_clusterVersion)
40034024
ZEND_NS_FE("Couchbase\\Extension", replicasConfiguredForBucket, ai_CouchbaseExtension_replicasConfiguredForBucket)

src/wrapper/connection_handle.cxx

Lines changed: 106 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ class connection_handle::impl : public std::enable_shared_from_this<connection_h
407407

408408
void start()
409409
{
410-
worker = std::thread([self = shared_from_this()]() { self->ctx_.run(); });
410+
worker_ = std::thread([self = shared_from_this()]() { self->ctx_.run(); });
411411
}
412412

413413
void stop()
@@ -418,8 +418,8 @@ class connection_handle::impl : public std::enable_shared_from_this<connection_h
418418
cluster_->close([barrier]() { barrier->set_value(); });
419419
f.wait();
420420
cluster_.reset();
421-
if (worker.joinable()) {
422-
worker.join();
421+
if (worker_.joinable()) {
422+
worker_.join();
423423
}
424424
}
425425
}
@@ -574,10 +574,33 @@ class connection_handle::impl : public std::enable_shared_from_this<connection_h
574574
return couchbase::cluster(*cluster_).bucket(bucket).scope(scope).collection(collection);
575575
}
576576

577+
void notify_fork(fork_event event)
578+
{
579+
switch (event) {
580+
case fork_event::prepare:
581+
ctx_.stop();
582+
worker_.join();
583+
ctx_.notify_fork(asio::execution_context::fork_prepare);
584+
break;
585+
586+
case fork_event::parent:
587+
ctx_.notify_fork(asio::execution_context::fork_parent);
588+
ctx_.restart();
589+
worker_ = std::thread([self = shared_from_this()]() { self->ctx_.run(); });
590+
break;
591+
592+
case fork_event::child:
593+
ctx_.notify_fork(asio::execution_context::fork_child);
594+
ctx_.restart();
595+
worker_ = std::thread([self = shared_from_this()]() { self->ctx_.run(); });
596+
break;
597+
}
598+
}
599+
577600
private:
578601
asio::io_context ctx_{};
579602
std::shared_ptr<couchbase::core::cluster> cluster_{ std::make_shared<couchbase::core::cluster>(ctx_) };
580-
std::thread worker;
603+
std::thread worker_;
581604
core::origin origin_;
582605
};
583606

@@ -620,6 +643,12 @@ connection_handle::replicas_configured_for_bucket(const zend_string* bucket_name
620643
return impl_->replicas_configured_for_bucket(cb_string_new(bucket_name));
621644
}
622645

646+
void
647+
connection_handle::notify_fork(fork_event event) const
648+
{
649+
return impl_->notify_fork(event);
650+
}
651+
623652
COUCHBASE_API
624653
core_error_info
625654
connection_handle::bucket_open(const std::string& name)
@@ -2659,9 +2688,9 @@ zval_to_search_index(couchbase::core::operations::management::search_index_upser
26592688
if (auto e = cb_assign_string(idx.plan_params_json, index, "planParams"); e.ec) {
26602689
return e;
26612690
}
2662-
request.index = idx;
2691+
request.index = idx;
26632692

2664-
return {};
2693+
return {};
26652694
}
26662695

26672696
COUCHBASE_API
@@ -2844,7 +2873,10 @@ connection_handle::search_index_control_plan_freeze(zval* return_value, const ze
28442873

28452874
COUCHBASE_API
28462875
core_error_info
2847-
connection_handle::search_index_analyze_document(zval* return_value, const zend_string* index_name, const zend_string* document, const zval* options)
2876+
connection_handle::search_index_analyze_document(zval* return_value,
2877+
const zend_string* index_name,
2878+
const zend_string* document,
2879+
const zval* options)
28482880
{
28492881
couchbase::core::operations::management::search_index_analyze_document_request request{};
28502882
request.index_name = cb_string_new(index_name);
@@ -2867,7 +2899,11 @@ connection_handle::search_index_analyze_document(zval* return_value, const zend_
28672899

28682900
COUCHBASE_API
28692901
core_error_info
2870-
connection_handle::scope_search_index_get(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* index_name, const zval* options)
2902+
connection_handle::scope_search_index_get(zval* return_value,
2903+
const zend_string* bucket_name,
2904+
const zend_string* scope_name,
2905+
const zend_string* index_name,
2906+
const zval* options)
28712907
{
28722908
couchbase::core::operations::management::search_index_get_request request{ cb_string_new(index_name) };
28732909

@@ -2892,7 +2928,10 @@ connection_handle::scope_search_index_get(zval* return_value, const zend_string*
28922928

28932929
COUCHBASE_API
28942930
core_error_info
2895-
connection_handle::scope_search_index_get_all(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zval* options)
2931+
connection_handle::scope_search_index_get_all(zval* return_value,
2932+
const zend_string* bucket_name,
2933+
const zend_string* scope_name,
2934+
const zval* options)
28962935
{
28972936
couchbase::core::operations::management::search_index_get_all_request request{};
28982937

@@ -2922,7 +2961,11 @@ connection_handle::scope_search_index_get_all(zval* return_value, const zend_str
29222961

29232962
COUCHBASE_API
29242963
core_error_info
2925-
connection_handle::scope_search_index_upsert(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zval* index, const zval* options)
2964+
connection_handle::scope_search_index_upsert(zval* return_value,
2965+
const zend_string* bucket_name,
2966+
const zend_string* scope_name,
2967+
const zval* index,
2968+
const zval* options)
29262969
{
29272970
couchbase::core::operations::management::search_index_upsert_request request{};
29282971

@@ -2951,7 +2994,11 @@ connection_handle::scope_search_index_upsert(zval* return_value, const zend_stri
29512994

29522995
COUCHBASE_API
29532996
core_error_info
2954-
connection_handle::scope_search_index_drop(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* index_name, const zval* options)
2997+
connection_handle::scope_search_index_drop(zval* return_value,
2998+
const zend_string* bucket_name,
2999+
const zend_string* scope_name,
3000+
const zend_string* index_name,
3001+
const zval* options)
29553002
{
29563003
couchbase::core::operations::management::search_index_drop_request request{ cb_string_new(index_name) };
29573004

@@ -2973,7 +3020,11 @@ connection_handle::scope_search_index_drop(zval* return_value, const zend_string
29733020

29743021
COUCHBASE_API
29753022
core_error_info
2976-
connection_handle::scope_search_index_get_documents_count(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* index_name, const zval* options)
3023+
connection_handle::scope_search_index_get_documents_count(zval* return_value,
3024+
const zend_string* bucket_name,
3025+
const zend_string* scope_name,
3026+
const zend_string* index_name,
3027+
const zval* options)
29773028
{
29783029
couchbase::core::operations::management::search_index_get_documents_count_request request{ cb_string_new(index_name) };
29793030

@@ -2997,7 +3048,12 @@ connection_handle::scope_search_index_get_documents_count(zval* return_value, co
29973048

29983049
COUCHBASE_API
29993050
core_error_info
3000-
connection_handle::scope_search_index_control_ingest(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* index_name, bool pause, const zval* options)
3051+
connection_handle::scope_search_index_control_ingest(zval* return_value,
3052+
const zend_string* bucket_name,
3053+
const zend_string* scope_name,
3054+
const zend_string* index_name,
3055+
bool pause,
3056+
const zval* options)
30013057
{
30023058
couchbase::core::operations::management::search_index_control_ingest_request request{};
30033059

@@ -3022,7 +3078,12 @@ connection_handle::scope_search_index_control_ingest(zval* return_value, const z
30223078

30233079
COUCHBASE_API
30243080
core_error_info
3025-
connection_handle::scope_search_index_control_query(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* index_name, bool allow, const zval* options)
3081+
connection_handle::scope_search_index_control_query(zval* return_value,
3082+
const zend_string* bucket_name,
3083+
const zend_string* scope_name,
3084+
const zend_string* index_name,
3085+
bool allow,
3086+
const zval* options)
30263087
{
30273088
couchbase::core::operations::management::search_index_control_query_request request{};
30283089

@@ -3047,7 +3108,12 @@ connection_handle::scope_search_index_control_query(zval* return_value, const ze
30473108

30483109
COUCHBASE_API
30493110
core_error_info
3050-
connection_handle::scope_search_index_control_plan_freeze(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* index_name, bool freeze, const zval* options)
3111+
connection_handle::scope_search_index_control_plan_freeze(zval* return_value,
3112+
const zend_string* bucket_name,
3113+
const zend_string* scope_name,
3114+
const zend_string* index_name,
3115+
bool freeze,
3116+
const zval* options)
30513117
{
30523118
couchbase::core::operations::management::search_index_control_plan_freeze_request request{};
30533119

@@ -3072,7 +3138,12 @@ connection_handle::scope_search_index_control_plan_freeze(zval* return_value, co
30723138

30733139
COUCHBASE_API
30743140
core_error_info
3075-
connection_handle::scope_search_index_analyze_document(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* index_name, const zend_string* document, const zval* options)
3141+
connection_handle::scope_search_index_analyze_document(zval* return_value,
3142+
const zend_string* bucket_name,
3143+
const zend_string* scope_name,
3144+
const zend_string* index_name,
3145+
const zend_string* document,
3146+
const zval* options)
30763147
{
30773148
couchbase::core::operations::management::search_index_analyze_document_request request{};
30783149

@@ -3286,7 +3357,8 @@ zval_to_bucket_settings(const zval* bucket_settings)
32863357
} else if (e.ec) {
32873358
return { e, {} };
32883359
}
3289-
if (auto e = cb_assign_boolean(bucket.history_retention_collection_default, bucket_settings, "historyRetentionCollectionDefault"); e.ec) {
3360+
if (auto e = cb_assign_boolean(bucket.history_retention_collection_default, bucket_settings, "historyRetentionCollectionDefault");
3361+
e.ec) {
32903362
return { e, {} };
32913363
}
32923364
if (auto e = cb_assign_integer(bucket.history_retention_bytes, bucket_settings, "historyRetentionBytes"); e.ec) {
@@ -3655,7 +3727,12 @@ connection_handle::scope_drop(zval* return_value, const zend_string* bucket_name
36553727

36563728
COUCHBASE_API
36573729
core_error_info
3658-
connection_handle::collection_create(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* collection_name, const zval* settings, const zval* options)
3730+
connection_handle::collection_create(zval* return_value,
3731+
const zend_string* bucket_name,
3732+
const zend_string* scope_name,
3733+
const zend_string* collection_name,
3734+
const zval* settings,
3735+
const zval* options)
36593736
{
36603737
couchbase::core::operations::management::collection_create_request request{};
36613738

@@ -3686,7 +3763,11 @@ connection_handle::collection_create(zval* return_value, const zend_string* buck
36863763

36873764
COUCHBASE_API
36883765
core_error_info
3689-
connection_handle::collection_drop(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* collection_name, const zval* options)
3766+
connection_handle::collection_drop(zval* return_value,
3767+
const zend_string* bucket_name,
3768+
const zend_string* scope_name,
3769+
const zend_string* collection_name,
3770+
const zval* options)
36903771
{
36913772
couchbase::core::operations::management::collection_drop_request request{};
36923773

@@ -3709,7 +3790,12 @@ connection_handle::collection_drop(zval* return_value, const zend_string* bucket
37093790

37103791
COUCHBASE_API
37113792
core_error_info
3712-
connection_handle::collection_update(zval* return_value, const zend_string* bucket_name, const zend_string* scope_name, const zend_string* collection_name, const zval* settings, const zval* options)
3793+
connection_handle::collection_update(zval* return_value,
3794+
const zend_string* bucket_name,
3795+
const zend_string* scope_name,
3796+
const zend_string* collection_name,
3797+
const zval* settings,
3798+
const zval* options)
37133799
{
37143800
couchbase::core::operations::management::collection_update_request request{};
37153801

0 commit comments

Comments
 (0)