Skip to content

Commit db31e8f

Browse files
authored
Fixed fork handling (#12)
1 parent 6faae30 commit db31e8f

File tree

6 files changed

+98
-27
lines changed

6 files changed

+98
-27
lines changed

prod/native/extension/code/ForkHandler.cpp

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,34 @@
77
#include "os/OsUtils.h"
88
#include "LoggerInterface.h"
99
#include "ModuleGlobals.h"
10-
#include "PeriodicTaskExecutor.h"
11-
#include "transport/HttpTransportAsync.h"
10+
#include "ForkableRegistry.h"
1211

13-
static void callbackToLogForkBeforeInParent() {
12+
namespace opentelemetry::php {
13+
14+
static void beforeFork() {
1415
ELOGF_NF_DEBUG(OTEL_GL(logger_), "Before process fork (i.e., in parent context); its parent (i.e., grandparent) PID: %d", static_cast<int>(opentelemetry::osutils::getParentProcessId()));
1516
// TODO implement forkable registry
16-
if (OTEL_G(globals) && OTEL_G(globals)->periodicTaskExecutor_) {
17-
OTEL_G(globals)->periodicTaskExecutor_->prefork();
18-
}
19-
if (OTEL_G(globals) && OTEL_G(globals)->httpTransportAsync_) {
20-
OTEL_G(globals)->httpTransportAsync_->prefork();
17+
if (OTEL_G(globals) && OTEL_G(globals)->forkableRegistry_) {
18+
OTEL_G(globals)->forkableRegistry_->preFork();
2119
}
2220
}
2321

24-
static void callbackToLogForkAfterInParent() {
22+
static void afterForkInParent() {
2523
ELOGF_NF_DEBUG(OTEL_GL(logger_), "After process fork (in parent context)");
26-
if (OTEL_G(globals) && OTEL_G(globals)->periodicTaskExecutor_) {
27-
OTEL_G(globals)->periodicTaskExecutor_->postfork(false);
28-
}
29-
if (OTEL_G(globals) && OTEL_G(globals)->httpTransportAsync_) {
30-
OTEL_G(globals)->httpTransportAsync_->postfork(false);
24+
if (OTEL_G(globals) && OTEL_G(globals)->forkableRegistry_) {
25+
OTEL_G(globals)->forkableRegistry_->postFork(false);
3126
}
3227
}
3328

34-
static void callbackToLogForkAfterInChild() {
29+
static void afterForkInChild() {
3530
ELOGF_NF_DEBUG(OTEL_GL(logger_), "After process fork (in child context); parent PID: %d", static_cast<int>(opentelemetry::osutils::getParentProcessId()));
36-
if (OTEL_G(globals) && OTEL_G(globals)->periodicTaskExecutor_) {
37-
OTEL_G(globals)->periodicTaskExecutor_->postfork(true);
38-
}
39-
if (OTEL_G(globals) && OTEL_G(globals)->httpTransportAsync_) {
40-
OTEL_G(globals)->httpTransportAsync_->postfork(true);
31+
if (OTEL_G(globals) && OTEL_G(globals)->forkableRegistry_) {
32+
OTEL_G(globals)->forkableRegistry_->postFork(true);
4133
}
4234
}
4335

44-
void registerCallbacksToLogFork() {
45-
int retVal = pthread_atfork(callbackToLogForkBeforeInParent, callbackToLogForkAfterInParent, callbackToLogForkAfterInChild);
36+
void registerCallbacksToHandleFork() {
37+
int retVal = pthread_atfork(beforeFork, afterForkInParent, afterForkInChild);
4638
if (retVal == 0) {
4739
ELOGF_NF_DEBUG(OTEL_GL(logger_), "Registered callbacks to log process fork");
4840
} else {
@@ -51,6 +43,7 @@ void registerCallbacksToLogFork() {
5143
}
5244

5345
#else
54-
void registerCallbacksToLogFork() {
46+
void registerCallbacksToHandleFork() {
47+
}
48+
#endif
5549
}
56-
#endif
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
#pragma once
22

3-
void registerCallbacksToLogFork();
3+
namespace opentelemetry::php {
4+
5+
void registerCallbacksToHandleFork();
6+
7+
}

prod/native/extension/code/ModuleInit.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ void moduleInit(int moduleType, int moduleNumber) {
9494
if (php_check_open_basedir_ex(OTEL_GL(config_)->get(&opentelemetry::php::ConfigurationSnapshot::bootstrap_php_part_file).c_str(), false) != 0) {
9595
ELOGF_WARNING(globals->logger_, MODULE, "OpenTelemetry PHP distro bootstrap file (%s) is located outside of paths allowed by open_basedir ini setting.", OTEL_GL(config_)->get(&opentelemetry::php::ConfigurationSnapshot::bootstrap_php_part_file).c_str());
9696
}
97+
98+
// Registering fork handlers in module init to ensure that we will handle all forks properly - even those triggered before request start (e.g. in MINIT of other extensions) or when fpm/apache spawns worker processes. We cannot be sure that some of the classes implementing ForkableInterface will not start thread before request start.
99+
registerCallbacksToHandleFork();
97100
}
98101

99102
void moduleShutdown( int moduleType, int moduleNumber ) {

prod/native/libcommon/code/AgentGlobals.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "PhpBridgeInterface.h"
44
#include "SharedMemoryState.h"
5+
#include "ForkableRegistry.h"
56
#include "InferredSpans.h"
67
#include "PeriodicTaskExecutor.h"
78
#include "PeriodicTaskExecutor.h"
@@ -32,6 +33,7 @@ AgentGlobals::AgentGlobals(std::shared_ptr<LoggerInterface> logger,
3233
std::shared_ptr<InstrumentedFunctionHooksStorageInterface> hooksStorage,
3334
std::shared_ptr<InferredSpans> inferredSpans,
3435
ConfigurationStorage::configUpdate_t updateConfigurationSnapshot) :
36+
forkableRegistry_(std::make_shared<ForkableRegistry>()),
3537
config_(std::make_shared<opentelemetry::php::ConfigurationStorage>(std::move(updateConfigurationSnapshot))),
3638
logger_(std::move(logger)),
3739
logSinkStdErr_(std::move(logSinkStdErr)),
@@ -52,6 +54,9 @@ AgentGlobals::AgentGlobals(std::shared_ptr<LoggerInterface> logger,
5254
coordinatorConfigProvider_(std::make_shared<opentelemetry::php::coordinator::CoordinatorConfigurationProvider>(logger_, opAmp_)),
5355
coordinatorProcess_(std::make_shared<opentelemetry::php::coordinator::CoordinatorProcess>(logger_, messagesDispatcher_, coordinatorConfigProvider_))
5456
{
57+
forkableRegistry_->registerForkable(httpTransportAsync_);
58+
forkableRegistry_->registerForkable(opAmp_);
59+
5560
config_->addConfigUpdateWatcher([logger = logger_, stderrsink = logSinkStdErr_, syslogsink = logSinkSysLog_, filesink = logSinkFile_](ConfigurationSnapshot const &cfg) {
5661
stderrsink->setLevel(cfg.log_level_stderr);
5762
syslogsink->setLevel(cfg.log_level_syslog);
@@ -95,8 +100,10 @@ std::shared_ptr<PeriodicTaskExecutor> AgentGlobals::getPeriodicTaskExecutor() {
95100
opentelemetry::utils::blockSignal(SIGPROF); // php timeout signal
96101
}
97102
);
98-
return periodicTaskExecutor_;
99-
}
103+
forkableRegistry_->registerForkable(periodicTaskExecutor_);
104+
105+
return periodicTaskExecutor_;
106+
}
100107

101108

102109
}

prod/native/libcommon/code/AgentGlobals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class OpAmp;
1717

1818
namespace opentelemetry::php {
1919

20+
class ForkableRegistry;
2021
class LoggerInterface;
2122
class PhpBridgeInterface;
2223
class InferredSpans;
@@ -58,6 +59,7 @@ class AgentGlobals {
5859

5960
std::shared_ptr<PeriodicTaskExecutor> getPeriodicTaskExecutor();
6061

62+
std::shared_ptr<ForkableRegistry> forkableRegistry_;
6163
std::shared_ptr<ConfigurationStorage> config_;
6264
std::shared_ptr<LoggerInterface> logger_;
6365
std::shared_ptr<LoggerSinkInterface> logSinkStdErr_;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#pragma once
2+
3+
#include "ForkableInterface.h"
4+
5+
#include <memory>
6+
#include <vector>
7+
8+
namespace opentelemetry::php {
9+
10+
/**
11+
* @class ForkableRegistry
12+
* @brief A registry for managing fork-aware objects in a PHP extension.
13+
*
14+
* This class is NOT thread-safe. Registration of forkable objects should only be performed
15+
* during extension initialization or within the Zend Engine's main execution thread (the primary PHP thread).
16+
* Attempting to register forkables from other threads may lead to unpredictable behavior and race conditions.
17+
*
18+
* The deliberate absence of mutex protection is intentional: adding synchronization primitives could
19+
* introduce deadlock scenarios during process forking. Specifically, if another thread holds a mutex
20+
* lock during the pre-fork phase, the child process would inherit a locked mutex that can never be
21+
* unlocked (since the thread that locked it doesn't exist in the child process), resulting in a deadlock.
22+
*
23+
* @note Thread Safety: This class is not thread-safe by design. All operations must be performed
24+
* from the main PHP thread.
25+
* @note Fork Safety: The class provides pre-fork and post-fork hooks to ensure proper state
26+
* management across process boundaries.
27+
*/
28+
class ForkableRegistry {
29+
public:
30+
ForkableRegistry() = default;
31+
~ForkableRegistry() = default;
32+
33+
ForkableRegistry(const ForkableRegistry &) = delete;
34+
ForkableRegistry &operator=(const ForkableRegistry &) = delete;
35+
ForkableRegistry(ForkableRegistry &&) = delete;
36+
ForkableRegistry &operator=(ForkableRegistry &&) = delete;
37+
38+
void registerForkable(std::shared_ptr<ForkableInterface> forkable) {
39+
forkables_.emplace_back(std::move(forkable));
40+
}
41+
42+
void preFork() {
43+
for (const auto &forkable : forkables_) {
44+
if (forkable) {
45+
forkable->prefork();
46+
}
47+
}
48+
}
49+
50+
void postFork(bool isChild) {
51+
for (const auto &forkable : forkables_) {
52+
if (forkable) {
53+
forkable->postfork(isChild);
54+
}
55+
}
56+
}
57+
58+
private:
59+
std::vector<std::shared_ptr<ForkableInterface>> forkables_;
60+
};
61+
62+
}

0 commit comments

Comments
 (0)