agent/native/ext/lifecycle.cpp (498 lines of code) (raw):
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "lifecycle.h"
#if defined(PHP_WIN32) && ! defined(CURL_STATICLIB)
# define CURL_STATICLIB
#endif
#include <curl/curl.h>
#include <inttypes.h> // PRIu64
#include <stdbool.h>
#include <php.h>
#include <zend_compile.h>
#include <zend_exceptions.h>
#include <zend_builtin_functions.h>
#include "php_elastic_apm.h"
#include "log.h"
#include "ConfigSnapshot.h"
#include "SystemMetrics.h"
#include "php_error.h"
#include "util_for_PHP.h"
#include "elastic_apm_assert.h"
#include "MemoryTracker.h"
#include "supportability.h"
#include "elastic_apm_alloc.h"
#include "elastic_apm_API.h"
#include "tracer_PHP_part.h"
#include "backend_comm.h"
#include "AST_instrumentation.h"
#include "Hooking.h"
#include "CommonUtils.h"
#include "Diagnostics.h"
#include "Hooking.h"
#define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_LIFECYCLE
static const char JSON_METRICSET[] =
"{\"metricset\":{\"samples\":{\"system.cpu.total.norm.pct\":{\"value\":%.2f},\"system.process.cpu.total.norm.pct\":{\"value\":%.2f},\"system.memory.actual.free\":{\"value\":%" PRIu64 "},\"system.memory.total\":{\"value\":%" PRIu64 "},\"system.process.memory.size\":{\"value\":%" PRIu64 "},\"system.process.memory.rss.bytes\":{\"value\":%" PRIu64 "}},\"timestamp\":%" PRIu64 "}}\n";
static uint64_t requestCounter = 0;
static
String buildSupportabilityInfo( size_t supportInfoBufferSize, char* supportInfoBuffer )
{
TextOutputStream txtOutStream = makeTextOutputStream( supportInfoBuffer, supportInfoBufferSize );
TextOutputStreamState txtOutStreamStateOnEntryStart;
if ( ! textOutputStreamStartEntry( &txtOutStream, &txtOutStreamStateOnEntryStart ) )
{
return ELASTIC_APM_TEXT_OUTPUT_STREAM_NOT_ENOUGH_SPACE_MARKER;
}
StructuredTextToOutputStreamPrinter structTxtToOutStreamPrinter;
initStructuredTextToOutputStreamPrinter(
/* in */ &txtOutStream
, /* prefix */ ELASTIC_APM_STRING_LITERAL_TO_VIEW( "" )
, /* out */ &structTxtToOutStreamPrinter );
printSupportabilityInfo( (StructuredTextPrinter*) &structTxtToOutStreamPrinter );
return textOutputStreamEndEntry( &txtOutStreamStateOnEntryStart, &txtOutStream );
}
void logSupportabilityInfo( LogLevel logLevel )
{
ResultCode resultCode;
enum { supportInfoBufferSize = 100 * 1000 + 1 };
char* supportInfoBuffer = NULL;
char txtOutStreamBuf[ ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE ];
TextOutputStream txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
String supportabilityInfo;
StringView textRemainder;
const char *textEnd;
ELASTIC_APM_LOG_WITH_LEVEL( logLevel, "Version of agent C part: " PHP_ELASTIC_APM_VERSION );
ELASTIC_APM_LOG_WITH_LEVEL( logLevel, "Current process command line: %s", streamCurrentProcessCommandLine( &txtOutStream, /* maxLength */ ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE ) );
ELASTIC_APM_PEMALLOC_STRING_IF_FAILED_GOTO( supportInfoBufferSize, supportInfoBuffer );
supportabilityInfo = buildSupportabilityInfo( supportInfoBufferSize, supportInfoBuffer );
textEnd = supportabilityInfo + strlen( supportabilityInfo );
textRemainder = makeStringViewFromBeginEnd( supportabilityInfo, textEnd );
for ( ;; )
{
StringView eolSeq = findEndOfLineSequence( textRemainder );
if ( isEmptyStringView( eolSeq ) ) break;
ELASTIC_APM_LOG_WITH_LEVEL( logLevel, "%.*s", (int)( eolSeq.begin - textRemainder.begin), textRemainder.begin );
textRemainder = makeStringViewFromBeginEnd( stringViewEnd( eolSeq ), textEnd );
}
ELASTIC_APM_LOG_WITH_LEVEL( logLevel, "%.*s", (int)( textEnd - textRemainder.begin), textRemainder.begin );
resultCode = resultSuccess;
finally:
ELASTIC_APM_PEFREE_STRING_SIZE_AND_SET_TO_NULL( supportInfoBufferSize, supportInfoBuffer );
ELASTIC_APM_UNUSED( resultCode );
return;
failure:
goto finally;
}
static pid_t g_pidOnRequestInit = -1;
bool doesCurrentPidMatchPidOnInit( pid_t pidOnInit, String dbgDesc )
{
pid_t currentPid = getCurrentProcessId();
if ( pidOnInit != currentPid )
{
ELASTIC_APM_LOG_DEBUG( "Process ID on %s init doesn't match the current process ID"
" (maybe the current process is a child process forked after the init step?)"
"; PID on init: %d, current PID: %d, parent PID: %d"
, dbgDesc, (int)pidOnInit, (int)currentPid, (int)(getParentProcessId()) );
return false;
}
return true;
}
typedef void (* ZendThrowExceptionHook )(
#if PHP_MAJOR_VERSION >= 8 /* if PHP version is 8.* and later */
zend_object* exception
#else
zval* exception
#endif
);
// static bool elasticApmZendErrorCallbackSet = false;
static bool elasticApmZendThrowExceptionHookReplaced = false;
static ZendThrowExceptionHook originalZendThrowExceptionHook = NULL;
void resetLastThrown() {
zval_dtor(&ELASTICAPM_G(lastException));
ZVAL_UNDEF(&ELASTICAPM_G(lastException));
}
void elasticApmZendThrowExceptionHookImpl(
#if PHP_MAJOR_VERSION >= 8 /* if PHP version is 8.* and later */
zend_object* thrownAsPzobj
#else
zval* thrownAsPzval
#endif
)
{
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "lastException set: %s", boolToString( Z_TYPE(ELASTICAPM_G(lastException)) != IS_UNDEF ) );
resetLastThrown();
#if PHP_MAJOR_VERSION >= 8 /* if PHP version is 8.* and later */
ZVAL_OBJ_COPY(&ELASTICAPM_G( lastException ), thrownAsPzobj );
#else
ZVAL_COPY(&ELASTICAPM_G(lastException), thrownAsPzval );
#endif
ELASTIC_APM_LOG_DEBUG_FUNCTION_EXIT();
}
void elasticApmGetLastThrown(zval *return_value) {
if (Z_TYPE(ELASTICAPM_G(lastException)) == IS_UNDEF) {
RETURN_NULL();
}
RETURN_ZVAL(&ELASTICAPM_G(lastException), /* copy */ true, /* dtor */ false );
}
void elasticApmZendThrowExceptionHook(
#if PHP_MAJOR_VERSION >= 8 /* if PHP version is 8.* and later */
zend_object* thrownObj
#else
zval* thrownObj
#endif
)
{
elasticApmZendThrowExceptionHookImpl( thrownObj );
if (originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook) {
ELASTIC_APM_LOG_CRITICAL( "originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook" );
return;
}
if ( originalZendThrowExceptionHook != NULL )
{
originalZendThrowExceptionHook( thrownObj );
}
}
static void registerExceptionHooks(const ConfigSnapshot& config) {
if (!config.captureErrors) {
ELASTIC_APM_LOG_DEBUG( "NOT replacing zend_throw_exception_hook hook because capture_errors configuration option is set to false" );
return;
}
if (elasticApmZendThrowExceptionHookReplaced) {
ELASTIC_APM_LOG_WARNING( "zend_throw_exception_hook already replaced: %p. Original: %p, Elastic: %p", zend_throw_exception_hook, originalZendThrowExceptionHook, elasticApmZendThrowExceptionHook );
return;
}
originalZendThrowExceptionHook = zend_throw_exception_hook;
zend_throw_exception_hook = elasticApmZendThrowExceptionHook;
elasticApmZendThrowExceptionHookReplaced = true;
ELASTIC_APM_LOG_DEBUG( "Replaced zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook) -> %p"
, originalZendThrowExceptionHook, originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook ? "==" : "!="
, elasticApmZendThrowExceptionHook );
}
static void unregisterExceptionHooks() {
if (elasticApmZendThrowExceptionHookReplaced) {
ZendThrowExceptionHook zendThrowExceptionHookBeforeRestore = zend_throw_exception_hook;
zend_throw_exception_hook = originalZendThrowExceptionHook;
ELASTIC_APM_LOG_DEBUG( "Restored zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook: %p) -> %p"
, zendThrowExceptionHookBeforeRestore, zendThrowExceptionHookBeforeRestore == elasticApmZendThrowExceptionHook ? "==" : "!="
, elasticApmZendThrowExceptionHook, originalZendThrowExceptionHook );
originalZendThrowExceptionHook = NULL;
} else {
ELASTIC_APM_LOG_DEBUG("zend_throw_exception_hook not restored: %p, elastic: %p", zend_throw_exception_hook, elasticApmZendThrowExceptionHook);
}
}
void elasticApmModuleInit( int moduleType, int moduleNumber )
{
auto const &sapi = ELASTICAPM_G(globals)->sapi_;
ELASTIC_APM_LOG_DIRECT_DEBUG( "%s entered: moduleType: %d, moduleNumber: %d, parent PID: %d, SAPI: %s (%d) is %s", __FUNCTION__, moduleType, moduleNumber, (int)(getParentProcessId()), sapi.getName().data(), static_cast<uint8_t>(sapi.getType()), sapi.isSupported() ? "supported" : "unsupported");
if (!sapi.isSupported()) {
return;
}
registerOsSignalHandler();
elasticapm::php::Hooking::getInstance().fetchOriginalHooks();
ResultCode resultCode;
Tracer* const tracer = getGlobalTracer();
const ConfigSnapshot* config = NULL;
CURLcode curlCode;
ELASTIC_APM_CALL_IF_FAILED_GOTO( constructTracer( tracer ) );
if ( ! tracer->isInited )
{
ELASTIC_APM_LOG_DEBUG( "Extension is not initialized" );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
registerElasticApmIniEntries( moduleType, moduleNumber, &tracer->iniEntriesRegistrationState );
ELASTIC_APM_CALL_IF_FAILED_GOTO( ensureLoggerInitialConfigIsLatest( tracer ) );
ELASTIC_APM_CALL_IF_FAILED_GOTO( ensureAllComponentsHaveLatestConfig( tracer ) );
logSupportabilityInfo( logLevel_debug );
config = getTracerCurrentConfigSnapshot( tracer );
if ( ! config->enabled )
{
resultCode = resultSuccess;
ELASTIC_APM_LOG_DEBUG( "Extension is disabled" );
goto finally;
}
registerCallbacksToLogFork();
registerAtExitLogging();
registerExceptionHooks(*config);
curlCode = curl_global_init( CURL_GLOBAL_ALL );
if ( curlCode != CURLE_OK )
{
resultCode = resultFailure;
ELASTIC_APM_LOG_ERROR( "curl_global_init failed: %s (%d)", curl_easy_strerror( curlCode ), (int)curlCode );
goto finally;
}
tracer->curlInited = true;
astInstrumentationOnModuleInit( config );
elasticapm::php::Hooking::getInstance().replaceHooks(config->captureErrors, config->profilingInferredSpansEnabled);
if (php_check_open_basedir_ex(config->bootstrapPhpPartFile, false) != 0) {
ELASTIC_APM_LOG_WARNING(
"Elastic Agent bootstrap file (%s) is located outside of paths allowed by open_basedir ini setting."
" For more details see https://www.elastic.co/guide/en/apm/agent/php/current/setup.html#limitation-open_basedir"
, config->bootstrapPhpPartFile
);
}
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT();
// We ignore errors because we want the monitored application to continue working
// even if APM encountered an issue that prevent it from working
return;
failure:
moveTracerToFailedState( tracer );
goto finally;
}
void elasticApmModuleShutdown( int moduleType, int moduleNumber )
{
ResultCode resultCode;
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "moduleType: %d, moduleNumber: %d", moduleType, moduleNumber );
if (!ELASTICAPM_G(globals)->sapi_.isSupported()) {
return;
}
Tracer* const tracer = getGlobalTracer();
const ConfigSnapshot* const config = getTracerCurrentConfigSnapshot( tracer );
if ( ! config->enabled )
{
resultCode = resultSuccess;
ELASTIC_APM_LOG_DEBUG_FUNCTION_EXIT_MSG( "Because extension is not enabled" );
goto finally;
}
elasticapm::php::Hooking::getInstance().restoreOriginalHooks();
astInstrumentationOnModuleShutdown();
unregisterExceptionHooks();
backgroundBackendCommOnModuleShutdown( config );
if ( tracer->curlInited )
{
curl_global_cleanup();
tracer->curlInited = false;
}
unregisterElasticApmIniEntries( moduleType, moduleNumber, &tracer->iniEntriesRegistrationState );
resultCode = resultSuccess;
finally:
destructTracer( tracer );
// We ignore errors because we want the monitored application to continue working
// even if APM encountered an issue that prevent it from working
ELASTIC_APM_UNUSED( resultCode );
unregisterOsSignalHandler();
ELASTIC_APM_LOG_DIRECT_DEBUG( "%s exiting...", __FUNCTION__ );
}
void elasticApmGetLastPhpError(zval* return_value) {
if (!ELASTICAPM_G(lastErrorData)) {
RETURN_NULL();
}
array_init( return_value );
ELASTIC_APM_ZEND_ADD_ASSOC(return_value, "type", long, static_cast<zend_long>(ELASTICAPM_G(lastErrorData)->getType()));
ELASTIC_APM_ZEND_ADD_ASSOC_NULLABLE_STRING( return_value, "fileName", ELASTICAPM_G(lastErrorData)->getFileName().data() );
ELASTIC_APM_ZEND_ADD_ASSOC(return_value, "lineNumber", long, static_cast<zend_long>(ELASTICAPM_G(lastErrorData)->getLineNumber()));
ELASTIC_APM_ZEND_ADD_ASSOC_NULLABLE_STRING( return_value, "message", ELASTICAPM_G(lastErrorData)->getMessage().data());
Z_TRY_ADDREF_P((ELASTICAPM_G(lastErrorData)->getStackTrace()));
ELASTIC_APM_ZEND_ADD_ASSOC(return_value, "stackTrace", zval, (ELASTICAPM_G(lastErrorData)->getStackTrace()));
}
auto buildPeriodicTaskExecutor() {
auto periodicTaskExecutor = std::make_unique<elasticapm::php::PeriodicTaskExecutor>(
std::vector<elasticapm::php::PeriodicTaskExecutor::task_t>{
[inferredSpans = ELASTICAPM_G(globals)->inferredSpans_](elasticapm::php::PeriodicTaskExecutor::time_point_t now) { inferredSpans->tryRequestInterrupt(now); }
},
[]() {
// block signals for this thread to be handled by main Apache/PHP thread
// list of signals from Apaches mpm handlers
elasticapm::utils::blockSignal(SIGTERM);
elasticapm::utils::blockSignal(SIGHUP);
elasticapm::utils::blockSignal(SIGINT);
elasticapm::utils::blockSignal(SIGWINCH);
elasticapm::utils::blockSignal(SIGUSR1);
elasticapm::utils::blockSignal(SIGPROF); // php timeout signal
}
);
ELASTIC_APM_LOG_DEBUG("starting inferred spans thread");
return periodicTaskExecutor;
}
void elasticApmRequestInit()
{
if (!ELASTICAPM_G(globals)->sapi_.isSupported()) {
return;
}
requestCounter++;
Tracer* const tracer = getGlobalTracer();
const ConfigSnapshot* config = getTracerCurrentConfigSnapshot( tracer );
enableAccessToServerGlobal();
bool preloadDetected = requestCounter == 1 ? detectOpcachePreload() : false;
if (!preloadDetected && requestCounter <= 2) {
if (ELASTICAPM_G(globals)->sharedMemory_->shouldExecuteOneTimeTaskAmongWorkers()) {
using namespace std::string_view_literals;
if ( ELASTICAPM_G( globals )->bridge_->isExtensionLoaded( "xdebug"sv ) ) {
ELASTIC_APM_LOG_WARNING( "Xdebug is loaded, which is not supported by the Elastic APM Agent. This may lead to stability or memory issues");
}
if (config && config->debugDiagnosticsFile) {
try {
elasticapm::utils::storeDiagnosticInformation(elasticapm::utils::getParameterizedString(config->debugDiagnosticsFile), *(ELASTICAPM_G(globals)->bridge_));
} catch (std::exception const &e) {
ELASTIC_APM_LOG_WARNING( "Unable to write agent diagnostics: %s", e.what() );
}
}
}
}
tracerPhpPartOnRequestInitSetInitialTracerState();
TimePoint requestInitStartTime;
getCurrentTime( &requestInitStartTime );
#if defined(ZTS) && defined(COMPILE_DL_ELASTIC_APM)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
g_pidOnRequestInit = getCurrentProcessId();
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "parent PID: %d", (int)(getParentProcessId()) );
ResultCode resultCode;
if ( ! tracer->isInited )
{
ELASTIC_APM_LOG_DEBUG( "Extension is not initialized" );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
if ( tracer->isFailed )
{
ELASTIC_APM_LOG_ERROR( "Extension is in failed state" );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
if (!isScriptRestricedByOpcacheAPI() && detectOpcacheRestartPending()) {
ELASTIC_APM_LOG_WARNING("Detected that opcache reset is in a pending state. Instrumentation has been disabled for this request. There may be warnings or errors logged for this request.");
resultCode = resultSuccess;
goto finally;
}
if ( ! config->enabled )
{
ELASTIC_APM_LOG_DEBUG( "Not enabled" );
resultCode = resultSuccess;
goto finally;
}
if ( isMemoryTrackingEnabled( &tracer->memTracker ) ) memoryTrackerRequestInit( &tracer->memTracker );
ELASTIC_APM_CALL_IF_FAILED_GOTO( ensureAllComponentsHaveLatestConfig( tracer ) );
logSupportabilityInfo( logLevel_trace );
enableAccessToServerGlobal();
if (requestCounter == 1 && preloadDetected) {
ELASTIC_APM_LOG_DEBUG( "opcache.preload request detected on init" );
resultCode = resultSuccess;
goto finally;
}
if (!config->captureErrors) {
ELASTIC_APM_LOG_DEBUG( "capture_errors (captureErrors) configuration option is set to false which means errors will NOT be captured" );
}
ELASTICAPM_G(captureErrors) = config->captureErrors;
if ( config->astProcessEnabled )
{
astInstrumentationOnRequestInit( config );
}
ELASTIC_APM_CALL_IF_FAILED_GOTO( tracerPhpPartOnRequestInit( config, &requestInitStartTime ) );
if (config->profilingInferredSpansEnabled) {
if (!ELASTICAPM_G(globals)->periodicTaskExecutor_) {
ELASTICAPM_G(globals)->periodicTaskExecutor_ = buildPeriodicTaskExecutor();
}
std::chrono::milliseconds interval{50};
try {
if (config->profilingInferredSpansSamplingInterval) {
interval = elasticapm::utils::convertDurationWithUnit(config->profilingInferredSpansSamplingInterval);
}
} catch (std::invalid_argument const &e) {
ELASTIC_APM_LOG_ERROR( "profilingInferredSpansSamplingInterval '%s': '%s'", e.what(), config->profilingInferredSpansSamplingInterval);
}
if (interval.count() == 0) {
interval = std::chrono::milliseconds{50};
ELASTIC_APM_LOG_DEBUG("inferred spans thread interval too low, forced to default %zums", interval.count());
}
ELASTIC_APM_LOG_DEBUG("resuming inferred spans thread with sampling interval %zums", interval.count());
ELASTICAPM_G(globals)->inferredSpans_->setInterval(interval);
ELASTICAPM_G(globals)->inferredSpans_->reset();
ELASTICAPM_G(globals)->periodicTaskExecutor_->setInterval(interval);
ELASTICAPM_G(globals)->periodicTaskExecutor_->resumePeriodicTasks();
}
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT();
// We ignore errors because we want the monitored application to continue working
// even if APM encountered an issue that prevent it from working
return;
failure:
goto finally;
}
// static
// void appendMetrics( const SystemMetricsReading* startSystemMetricsReading, const TimePoint* currentTime, TextOutputStream* serializedEventsTxtOutStream )
// {
// SystemMetricsReading endSystemMetricsReading;
// readSystemMetrics( &endSystemMetricsReading );
// SystemMetrics system_metrics;
// getSystemMetrics( startSystemMetricsReading, &endSystemMetricsReading, &system_metrics );
// streamPrintf(
// serializedEventsTxtOutStream
// , JSON_METRICSET
// , system_metrics.machineCpu // system.cpu.total.norm.pct
// , system_metrics.processCpu // system.process.cpu.total.norm.pct
// , system_metrics.machineMemoryFree // system.memory.actual.free
// , system_metrics.machineMemoryTotal // system.memory.total
// , system_metrics.processMemorySize // system.process.memory.size
// , system_metrics.processMemoryRss // system.process.memory.rss.bytes
// , timePointToEpochMicroseconds( currentTime ) );
// }
void elasticApmRequestShutdown()
{
if (!ELASTICAPM_G(globals)->sapi_.isSupported()) {
return;
}
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY();
Tracer* const tracer = getGlobalTracer();
const ConfigSnapshot* const config = getTracerCurrentConfigSnapshot( tracer );
if (!doesCurrentPidMatchPidOnInit( g_pidOnRequestInit, "request" )) {
return;
}
if (!tracer->isInited) {
ELASTIC_APM_LOG_TRACE( "Extension is not initialized" );
return;
}
if ( ! config->enabled )
{
ELASTIC_APM_LOG_DEBUG( "Extension is not enabled" );
return;
}
if (requestCounter == 1 && detectOpcachePreload()) {
ELASTIC_APM_LOG_DEBUG( "opcache.preload request detected on shutdown" );
return;
}
if (ELASTICAPM_G(globals)->periodicTaskExecutor_) {
ELASTIC_APM_LOG_DEBUG("pausing inferred spans thread");
ELASTICAPM_G(globals)->periodicTaskExecutor_->suspendPeriodicTasks();
}
ELASTICAPM_G(captureErrors) = false; // disabling error capturing on shutdown
tracerPhpPartOnRequestShutdown();
// there is no guarantee that following code will be executed - in case of error on php side
ELASTIC_APM_LOG_DEBUG_FUNCTION_EXIT();
// We ignore errors because we want the monitored application to continue working
// even if APM encountered an issue that prevent it from working
}
#if PHP_VERSION_ID >= 80000
ZEND_RESULT_CODE elasticApmRequestPostDeactivate(void) {
#else
int elasticApmRequestPostDeactivate(void) {
#endif
if (!ELASTICAPM_G(globals)->sapi_.isSupported()) {
return SUCCESS;
}
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY();
if (requestCounter == 1 && detectOpcachePreload()) {
ELASTIC_APM_LOG_DEBUG( "opcache.preload request detected on post deactivate" );
return SUCCESS;
}
Tracer* const tracer = getGlobalTracer();
const ConfigSnapshot* const config = getTracerCurrentConfigSnapshot( tracer );
if ( config->astProcessEnabled )
{
astInstrumentationOnRequestShutdown();
}
resetCallInterceptionOnRequestShutdown();
ELASTICAPM_G(lastErrorData).reset(nullptr);
resetLastThrown();
if ( tracer->isInited && isMemoryTrackingEnabled( &tracer->memTracker ) )
{
memoryTrackerRequestShutdown( &tracer->memTracker );
}
return SUCCESS;
}
static pid_t g_lastDetectedCurrentProcessId = -1;
ResultCode resetStateIfForkedChild( String dbgCalledFromFile, int dbgCalledFromLine, String dbgCalledFromFunction )
{
ResultCode resultCode;
pid_t lastDetectedCurrentProcessIdSaved;
if ( g_lastDetectedCurrentProcessId == -1 )
{
g_lastDetectedCurrentProcessId = getCurrentProcessId();
resultCode = resultSuccess;
goto finally;
}
if ( g_lastDetectedCurrentProcessId == getCurrentProcessId() )
{
resultCode = resultSuccess;
goto finally;
}
lastDetectedCurrentProcessIdSaved = g_lastDetectedCurrentProcessId;
g_lastDetectedCurrentProcessId = getCurrentProcessId();
ELASTIC_APM_CALL_IF_FAILED_GOTO( resetLoggingStateInForkedChild() );
ELASTIC_APM_LOG_DEBUG( "Detected change in current process ID (PID) - handling it..."
"; old PID: %d; parent PID: %d"
, (int)lastDetectedCurrentProcessIdSaved, (int)(getParentProcessId()) );
ELASTIC_APM_CALL_IF_FAILED_GOTO( resetBackgroundBackendCommStateInForkedChild() );
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}
ResultCode elasticApmEnterAgentCode( String dbgCalledFromFile, int dbgCalledFromLine, String dbgCalledFromFunction )
{
ResultCode resultCode;
// We SHOULD NOT log before resetting state if forked because logging might be using thread synchronization
// which might deadlock in forked child
ELASTIC_APM_CALL_IF_FAILED_GOTO( resetStateIfForkedChild( dbgCalledFromFile, dbgCalledFromLine, dbgCalledFromFunction ) );
resultCode = resultSuccess;
finally:
return resultCode;
failure:
goto finally;
}