Skip to content

Commit c28343c

Browse files
authored
Prevent deadloop in exception and error handlers (#955)
* added logging in case of potential deadloop * moved hook registration into MINIT/MSHUTDOWN
1 parent 0a72e24 commit c28343c

File tree

1 file changed

+186
-151
lines changed

1 file changed

+186
-151
lines changed

src/ext/lifecycle.c

Lines changed: 186 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -120,103 +120,6 @@ bool doesCurrentPidMatchPidOnInit( pid_t pidOnInit, String dbgDesc )
120120
return true;
121121
}
122122

123-
void elasticApmModuleInit( int moduleType, int moduleNumber )
124-
{
125-
registerOsSignalHandler();
126-
127-
ELASTIC_APM_LOG_DIRECT_DEBUG( "%s entered: moduleType: %d, moduleNumber: %d, parent PID: %d", __FUNCTION__, moduleType, moduleNumber, (int)(getParentProcessId()) );
128-
129-
ResultCode resultCode;
130-
Tracer* const tracer = getGlobalTracer();
131-
const ConfigSnapshot* config = NULL;
132-
133-
ELASTIC_APM_CALL_IF_FAILED_GOTO( constructTracer( tracer ) );
134-
135-
if ( ! tracer->isInited )
136-
{
137-
ELASTIC_APM_LOG_DEBUG( "Extension is not initialized" );
138-
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
139-
}
140-
141-
registerElasticApmIniEntries( moduleType, moduleNumber, &tracer->iniEntriesRegistrationState );
142-
143-
ELASTIC_APM_CALL_IF_FAILED_GOTO( ensureLoggerInitialConfigIsLatest( tracer ) );
144-
ELASTIC_APM_CALL_IF_FAILED_GOTO( ensureAllComponentsHaveLatestConfig( tracer ) );
145-
146-
logSupportabilityInfo( logLevel_debug );
147-
148-
config = getTracerCurrentConfigSnapshot( tracer );
149-
150-
if ( ! config->enabled )
151-
{
152-
resultCode = resultSuccess;
153-
ELASTIC_APM_LOG_DEBUG( "Extension is disabled" );
154-
goto finally;
155-
}
156-
157-
registerCallbacksToLogFork();
158-
registerAtExitLogging();
159-
160-
CURLcode curlCode = curl_global_init( CURL_GLOBAL_ALL );
161-
if ( curlCode != CURLE_OK )
162-
{
163-
resultCode = resultFailure;
164-
ELASTIC_APM_LOG_ERROR( "curl_global_init failed: %s (%d)", curl_easy_strerror( curlCode ), (int)curlCode );
165-
goto finally;
166-
}
167-
tracer->curlInited = true;
168-
169-
resultCode = resultSuccess;
170-
finally:
171-
172-
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT();
173-
// We ignore errors because we want the monitored application to continue working
174-
// even if APM encountered an issue that prevent it from working
175-
return;
176-
177-
failure:
178-
moveTracerToFailedState( tracer );
179-
goto finally;
180-
}
181-
182-
void elasticApmModuleShutdown( int moduleType, int moduleNumber )
183-
{
184-
ResultCode resultCode;
185-
186-
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "moduleType: %d, moduleNumber: %d", moduleType, moduleNumber );
187-
188-
Tracer* const tracer = getGlobalTracer();
189-
const ConfigSnapshot* const config = getTracerCurrentConfigSnapshot( tracer );
190-
191-
if ( ! config->enabled )
192-
{
193-
resultCode = resultSuccess;
194-
ELASTIC_APM_LOG_DEBUG_FUNCTION_EXIT_MSG( "Because extension is not enabled" );
195-
goto finally;
196-
}
197-
198-
backgroundBackendCommOnModuleShutdown( config );
199-
200-
if ( tracer->curlInited )
201-
{
202-
curl_global_cleanup();
203-
tracer->curlInited = false;
204-
}
205-
206-
unregisterElasticApmIniEntries( moduleType, moduleNumber, &tracer->iniEntriesRegistrationState );
207-
208-
resultCode = resultSuccess;
209-
210-
finally:
211-
destructTracer( tracer );
212-
213-
// We ignore errors because we want the monitored application to continue working
214-
// even if APM encountered an issue that prevent it from working
215-
ELASTIC_APM_UNUSED( resultCode );
216-
217-
ELASTIC_APM_LOG_DIRECT_DEBUG( "%s exiting...", __FUNCTION__ );
218-
}
219-
220123
typedef void (* ZendThrowExceptionHook )(
221124
#if PHP_MAJOR_VERSION >= 8 /* if PHP version is 8.* and later */
222125
zend_object* exception
@@ -225,7 +128,8 @@ typedef void (* ZendThrowExceptionHook )(
225128
#endif
226129
);
227130

228-
static bool isOriginalZendThrowExceptionHookSet = false;
131+
static bool elasticApmZendErrorCallbackSet = false;
132+
static bool elasticApmZendThrowExceptionHookSet = false;
229133
static ZendThrowExceptionHook originalZendThrowExceptionHook = NULL;
230134
static bool g_isLastThrownSet = false;
231135
static zval g_lastThrown;
@@ -284,7 +188,17 @@ void elasticApmZendThrowExceptionHook(
284188
#endif
285189
)
286190
{
287-
elasticApmZendThrowExceptionHookImpl( thrownObj );
191+
Tracer* const tracer = getGlobalTracer();
192+
const ConfigSnapshot* config = getTracerCurrentConfigSnapshot( tracer );
193+
if (config->captureErrors) {
194+
elasticApmZendThrowExceptionHookImpl( thrownObj );
195+
}
196+
197+
if (originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook) {
198+
ELASTIC_APM_LOG_CRITICAL( "originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook" );
199+
return;
200+
}
201+
288202

289203
if ( originalZendThrowExceptionHook != NULL )
290204
{
@@ -352,7 +266,6 @@ const char* zendErrorCallbackFileNameToCString( ZendErrorCallbackFileName fileNa
352266

353267
typedef void (* ZendErrorCallback )( ELASTIC_APM_ZEND_ERROR_CALLBACK_SIGNATURE() );
354268

355-
static bool isOriginalZendErrorCallbackSet = false;
356269
static ZendErrorCallback originalZendErrorCallback = NULL;
357270

358271
struct PhpErrorData
@@ -511,6 +424,178 @@ void elasticApmZendErrorCallbackImpl( ELASTIC_APM_ZEND_ERROR_CALLBACK_SIGNATURE(
511424
goto finally;
512425
}
513426

427+
void elasticApmZendErrorCallback( ELASTIC_APM_ZEND_ERROR_CALLBACK_SIGNATURE() )
428+
{
429+
Tracer* const tracer = getGlobalTracer();
430+
const ConfigSnapshot* config = getTracerCurrentConfigSnapshot( tracer );
431+
if (config->captureErrors) {
432+
elasticApmZendErrorCallbackImpl( ELASTIC_APM_ZEND_ERROR_CALLBACK_ARGS() );
433+
}
434+
435+
436+
if (originalZendErrorCallback == elasticApmZendErrorCallback) {
437+
ELASTIC_APM_LOG_CRITICAL( "originalZendErrorCallback == elasticApmZendErrorCallback" );
438+
return;
439+
}
440+
441+
if ( originalZendErrorCallback != NULL )
442+
{
443+
originalZendErrorCallback( ELASTIC_APM_ZEND_ERROR_CALLBACK_ARGS() );
444+
}
445+
}
446+
447+
448+
static void registerErrorAndExceptionHooks() {
449+
if (!elasticApmZendErrorCallbackSet) {
450+
originalZendErrorCallback = zend_error_cb;
451+
zend_error_cb = elasticApmZendErrorCallback;
452+
elasticApmZendErrorCallbackSet = true;
453+
ELASTIC_APM_LOG_DEBUG( "Set zend_error_cb: %p (%s elasticApmZendErrorCallback) -> %p"
454+
, originalZendErrorCallback, originalZendErrorCallback == elasticApmZendErrorCallback ? "==" : "!="
455+
, elasticApmZendErrorCallback );
456+
} else {
457+
ELASTIC_APM_LOG_WARNING( "zend_error_cb already set: %p. Original: %p, Elastic: %p", zend_error_cb, originalZendErrorCallback, elasticApmZendErrorCallback );
458+
}
459+
460+
461+
if (!elasticApmZendThrowExceptionHookSet) {
462+
originalZendThrowExceptionHook = zend_throw_exception_hook;
463+
zend_throw_exception_hook = elasticApmZendThrowExceptionHook;
464+
elasticApmZendThrowExceptionHookSet = true;
465+
ELASTIC_APM_LOG_DEBUG( "Set zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook) -> %p"
466+
, originalZendThrowExceptionHook, originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook ? "==" : "!="
467+
, elasticApmZendThrowExceptionHook );
468+
} else {
469+
ELASTIC_APM_LOG_WARNING( "zend_erzend_throw_exception_hook already set: %p. Original: %p, Elastic: %p", zend_throw_exception_hook, originalZendThrowExceptionHook, elasticApmZendThrowExceptionHook );
470+
}
471+
}
472+
473+
474+
static void unregisterErrorAndExceptionHooks() {
475+
if (elasticApmZendThrowExceptionHookSet) {
476+
ZendThrowExceptionHook zendThrowExceptionHookBeforeRestore = zend_throw_exception_hook;
477+
zend_throw_exception_hook = originalZendThrowExceptionHook;
478+
ELASTIC_APM_LOG_DEBUG( "Restored zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook: %p) -> %p"
479+
, zendThrowExceptionHookBeforeRestore, zendThrowExceptionHookBeforeRestore == elasticApmZendThrowExceptionHook ? "==" : "!="
480+
, elasticApmZendThrowExceptionHook, originalZendThrowExceptionHook );
481+
originalZendThrowExceptionHook = NULL;
482+
} else {
483+
ELASTIC_APM_LOG_DEBUG("zend_throw_exception_hook not restored: %p, elastic: %p", zend_throw_exception_hook, elasticApmZendThrowExceptionHook);
484+
}
485+
486+
if (!elasticApmZendErrorCallbackSet) {
487+
ZendErrorCallback zendErrorCallbackBeforeRestore = zend_error_cb;
488+
zend_error_cb = originalZendErrorCallback;
489+
ELASTIC_APM_LOG_DEBUG( "Restored zend_error_cb: %p (%s elasticApmZendErrorCallback: %p) -> %p"
490+
, zendErrorCallbackBeforeRestore, zendErrorCallbackBeforeRestore == elasticApmZendErrorCallback ? "==" : "!="
491+
, elasticApmZendErrorCallback, originalZendErrorCallback );
492+
originalZendErrorCallback = NULL;
493+
} else {
494+
ELASTIC_APM_LOG_DEBUG("zend_error_cb not restored: %p, elastic: %p", zend_error_cb, elasticApmZendErrorCallback);
495+
}
496+
497+
}
498+
499+
void elasticApmModuleInit( int moduleType, int moduleNumber )
500+
{
501+
registerOsSignalHandler();
502+
503+
ELASTIC_APM_LOG_DIRECT_DEBUG( "%s entered: moduleType: %d, moduleNumber: %d, parent PID: %d", __FUNCTION__, moduleType, moduleNumber, (int)(getParentProcessId()) );
504+
505+
ResultCode resultCode;
506+
Tracer* const tracer = getGlobalTracer();
507+
const ConfigSnapshot* config = NULL;
508+
509+
ELASTIC_APM_CALL_IF_FAILED_GOTO( constructTracer( tracer ) );
510+
511+
if ( ! tracer->isInited )
512+
{
513+
ELASTIC_APM_LOG_DEBUG( "Extension is not initialized" );
514+
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
515+
}
516+
517+
registerElasticApmIniEntries( moduleType, moduleNumber, &tracer->iniEntriesRegistrationState );
518+
519+
ELASTIC_APM_CALL_IF_FAILED_GOTO( ensureLoggerInitialConfigIsLatest( tracer ) );
520+
ELASTIC_APM_CALL_IF_FAILED_GOTO( ensureAllComponentsHaveLatestConfig( tracer ) );
521+
522+
logSupportabilityInfo( logLevel_debug );
523+
524+
config = getTracerCurrentConfigSnapshot( tracer );
525+
526+
if ( ! config->enabled )
527+
{
528+
resultCode = resultSuccess;
529+
ELASTIC_APM_LOG_DEBUG( "Extension is disabled" );
530+
goto finally;
531+
}
532+
533+
registerCallbacksToLogFork();
534+
registerAtExitLogging();
535+
registerErrorAndExceptionHooks();
536+
537+
CURLcode curlCode = curl_global_init( CURL_GLOBAL_ALL );
538+
if ( curlCode != CURLE_OK )
539+
{
540+
resultCode = resultFailure;
541+
ELASTIC_APM_LOG_ERROR( "curl_global_init failed: %s (%d)", curl_easy_strerror( curlCode ), (int)curlCode );
542+
goto finally;
543+
}
544+
tracer->curlInited = true;
545+
546+
resultCode = resultSuccess;
547+
finally:
548+
549+
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT();
550+
// We ignore errors because we want the monitored application to continue working
551+
// even if APM encountered an issue that prevent it from working
552+
return;
553+
554+
failure:
555+
moveTracerToFailedState( tracer );
556+
goto finally;
557+
}
558+
559+
void elasticApmModuleShutdown( int moduleType, int moduleNumber )
560+
{
561+
ResultCode resultCode;
562+
563+
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "moduleType: %d, moduleNumber: %d", moduleType, moduleNumber );
564+
565+
Tracer* const tracer = getGlobalTracer();
566+
const ConfigSnapshot* const config = getTracerCurrentConfigSnapshot( tracer );
567+
568+
if ( ! config->enabled )
569+
{
570+
resultCode = resultSuccess;
571+
ELASTIC_APM_LOG_DEBUG_FUNCTION_EXIT_MSG( "Because extension is not enabled" );
572+
goto finally;
573+
}
574+
575+
unregisterErrorAndExceptionHooks();
576+
577+
backgroundBackendCommOnModuleShutdown( config );
578+
579+
if ( tracer->curlInited )
580+
{
581+
curl_global_cleanup();
582+
tracer->curlInited = false;
583+
}
584+
585+
unregisterElasticApmIniEntries( moduleType, moduleNumber, &tracer->iniEntriesRegistrationState );
586+
587+
resultCode = resultSuccess;
588+
589+
finally:
590+
destructTracer( tracer );
591+
592+
// We ignore errors because we want the monitored application to continue working
593+
// even if APM encountered an issue that prevent it from working
594+
ELASTIC_APM_UNUSED( resultCode );
595+
596+
ELASTIC_APM_LOG_DIRECT_DEBUG( "%s exiting...", __FUNCTION__ );
597+
}
598+
514599
void elasticApmGetLastPhpError( zval* return_value )
515600
{
516601
if ( ! g_lastPhpErrorDataSet )
@@ -527,16 +612,6 @@ void elasticApmGetLastPhpError( zval* return_value )
527612
ELASTIC_APM_ZEND_ADD_ASSOC( return_value, "stackTrace", zval, &( g_lastPhpErrorData.stackTrace ) );
528613
}
529614

530-
void elasticApmZendErrorCallback( ELASTIC_APM_ZEND_ERROR_CALLBACK_SIGNATURE() )
531-
{
532-
elasticApmZendErrorCallbackImpl( ELASTIC_APM_ZEND_ERROR_CALLBACK_ARGS() );
533-
534-
if ( originalZendErrorCallback != NULL )
535-
{
536-
originalZendErrorCallback( ELASTIC_APM_ZEND_ERROR_CALLBACK_ARGS() );
537-
}
538-
}
539-
540615
void elasticApmRequestInit()
541616
{
542617
requestCounter++;
@@ -599,24 +674,7 @@ void elasticApmRequestInit()
599674

600675
// readSystemMetrics( &tracer->startSystemMetricsReading );
601676

602-
if ( config->captureErrors )
603-
{
604-
originalZendErrorCallback = zend_error_cb;
605-
isOriginalZendErrorCallbackSet = true;
606-
zend_error_cb = elasticApmZendErrorCallback;
607-
ELASTIC_APM_LOG_DEBUG( "Set zend_error_cb: %p (%s elasticApmZendErrorCallback) -> %p"
608-
, originalZendErrorCallback, originalZendErrorCallback == elasticApmZendErrorCallback ? "==" : "!="
609-
, elasticApmZendErrorCallback );
610-
611-
originalZendThrowExceptionHook = zend_throw_exception_hook;
612-
isOriginalZendThrowExceptionHookSet = true;
613-
zend_throw_exception_hook = elasticApmZendThrowExceptionHook;
614-
ELASTIC_APM_LOG_DEBUG( "Set zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook) -> %p"
615-
, originalZendThrowExceptionHook, originalZendThrowExceptionHook == elasticApmZendThrowExceptionHook ? "==" : "!="
616-
, elasticApmZendThrowExceptionHook );
617-
}
618-
else
619-
{
677+
if (!config->captureErrors) {
620678
ELASTIC_APM_LOG_DEBUG( "capture_errors (captureErrors) configuration option is set to false which means errors will NOT be captured" );
621679
}
622680

@@ -686,29 +744,6 @@ void elasticApmRequestShutdown()
686744
goto finally;
687745
}
688746

689-
690-
if ( isOriginalZendThrowExceptionHookSet )
691-
{
692-
ZendThrowExceptionHook zendThrowExceptionHookBeforeRestore = zend_throw_exception_hook;
693-
zend_throw_exception_hook = originalZendThrowExceptionHook;
694-
ELASTIC_APM_LOG_DEBUG( "Restored zend_throw_exception_hook: %p (%s elasticApmZendThrowExceptionHook: %p) -> %p"
695-
, zendThrowExceptionHookBeforeRestore, zendThrowExceptionHookBeforeRestore == elasticApmZendThrowExceptionHook ? "==" : "!="
696-
, elasticApmZendThrowExceptionHook, originalZendThrowExceptionHook );
697-
originalZendThrowExceptionHook = NULL;
698-
isOriginalZendThrowExceptionHookSet = false;
699-
}
700-
701-
if ( isOriginalZendErrorCallbackSet )
702-
{
703-
ZendErrorCallback zendErrorCallbackBeforeRestore = zend_error_cb;
704-
zend_error_cb = originalZendErrorCallback;
705-
ELASTIC_APM_LOG_DEBUG( "Restored zend_error_cb: %p (%s elasticApmZendErrorCallback: %p) -> %p"
706-
, zendErrorCallbackBeforeRestore, zendErrorCallbackBeforeRestore == elasticApmZendErrorCallback ? "==" : "!="
707-
, elasticApmZendErrorCallback, originalZendErrorCallback );
708-
originalZendErrorCallback = NULL;
709-
isOriginalZendErrorCallbackSet = false;
710-
}
711-
712747
// We should shutdown PHP part first because sendMetrics() uses metadata sent by PHP part on shutdown
713748
shutdownTracerPhpPart( config );
714749

0 commit comments

Comments
 (0)