Skip to content

Commit

Permalink
Disable agent when opcache is in reset pending state (#988) (#1000)
Browse files Browse the repository at this point in the history
* Disable agent when opcache is in reset pending state (#988)
* Better handling of tracer state
  • Loading branch information
intuibase authored Jun 21, 2023
1 parent 68cad53 commit d41a80f
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 20 deletions.
8 changes: 8 additions & 0 deletions src/ext/lifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ void elasticApmRequestInit()
{
requestCounter++;

tracerPhpPartOnRequestInitSetInitialTracerState();

TimePoint requestInitStartTime;
getCurrentTime( &requestInitStartTime );

Expand Down Expand Up @@ -652,6 +654,12 @@ void elasticApmRequestInit()
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}

if (detectOpcacheRestartPending()) {
ELASTIC_APM_LOG_ERROR("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" );
Expand Down
59 changes: 39 additions & 20 deletions src/ext/tracer_PHP_part.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,23 @@ String tracerPhpPartStateToString( TracerPhpPartState value )

static TracerPhpPartState g_tracerPhpPartState = numberOfTracerPhpPartState;

void switchTracerPhpPartStateToFailed( String reason, String dbgCalledFromFunc )
bool canInvokeTracerPhpPart() {
return g_tracerPhpPartState == tracerPhpPartState_after_bootstrap;
}

// returns true if state was changed or false it was same before
bool switchTracerPhpPartStateToFailed( String reason, String dbgCalledFromFunc )
{
if ( g_tracerPhpPartState == tracerPhpPartState_failed )
{
return;
return false;
}

ELASTIC_APM_LOG_ERROR( "Switching tracer PHP part state to failed; reason: %s, current state: %s, called from %s"
, reason, tracerPhpPartStateToString( g_tracerPhpPartState ), dbgCalledFromFunc );

g_tracerPhpPartState = tracerPhpPartState_failed;
return true;
}

ResultCode bootstrapTracerPhpPart( const ConfigSnapshot* config, const TimePoint* requestInitStartTime )
Expand Down Expand Up @@ -201,10 +207,12 @@ bool tracerPhpPartInternalFuncCallPreHook( uint32_t interceptRegistrationId, zen
ZVAL_UNDEF( &interceptRegistrationIdAsZval );
zval phpPartArgs[ g_maxInterceptedCallArgsCount + 2 ];

if ( g_tracerPhpPartState != tracerPhpPartState_after_bootstrap )
{
switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
if (!canInvokeTracerPhpPart()) {
if (switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ )) {
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
} else {
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
}

// The first argument to PHP part's interceptedCallPreHook() is $interceptRegistrationId
Expand Down Expand Up @@ -260,10 +268,12 @@ void tracerPhpPartInternalFuncCallPostHook( uint32_t dbgInterceptRegistrationId,
ResultCode resultCode;
zval phpPartArgs[ 2 ];

if ( g_tracerPhpPartState != tracerPhpPartState_after_bootstrap )
{
switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
if (!canInvokeTracerPhpPart()) {
if (switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ )) {
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
} else {
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
}

// The first argument to PHP part's interceptedCallPostHook() is $hasExitedByException (bool)
Expand Down Expand Up @@ -301,10 +311,12 @@ void tracerPhpPartInterceptedCallEmptyMethod()
zval phpPartDummyArgs[ 1 ];
ZVAL_UNDEF( &( phpPartDummyArgs[ 0 ] ) );

if ( g_tracerPhpPartState != tracerPhpPartState_after_bootstrap )
{
switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
if (!canInvokeTracerPhpPart()) {
if (switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ )) {
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
} else {
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
}

ELASTIC_APM_CALL_IF_FAILED_GOTO(
Expand Down Expand Up @@ -344,18 +356,22 @@ void tracerPhpPartLogArguments( LogLevel logLevel, uint32_t argsCount, zval args

void tracerPhpPartForwardCall( StringView phpFuncName, zend_execute_data* execute_data, /* out */ zval* retVal, String dbgCalledFrom )
{
ResultCode resultCode;
ResultCode resultCode = resultFailure;
ZVAL_NULL(retVal);
uint32_t callArgsCount;
zval callArgs[ g_maxInterceptedCallArgsCount ];

ELASTIC_APM_LOG_TRACE_FUNCTION_ENTRY_MSG( "phpFuncName: %s, dbgCalledFrom: %s", phpFuncName.begin, dbgCalledFrom );

if ( g_tracerPhpPartState != tracerPhpPartState_after_bootstrap )
{
switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
if (!canInvokeTracerPhpPart()) {
if (switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ )) {
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
} else {
ELASTIC_APM_SET_RESULT_CODE_TO_SUCCESS_AND_GOTO_FINALLY();
}
}


getArgsFromZendExecuteData( execute_data, g_maxInterceptedCallArgsCount, &( callArgs[ 0 ] ), &callArgsCount );
tracerPhpPartLogArguments( logLevel_trace, callArgsCount, callArgs );

Expand Down Expand Up @@ -385,9 +401,12 @@ void tracerPhpPartAstInstrumentationDirectCall( zend_execute_data* execute_data
tracerPhpPartForwardCall( ELASTIC_APM_STRING_LITERAL_TO_VIEW( ELASTIC_APM_PHP_PART_AST_INSTRUMENTATION_DIRECT_CALL_FUNC ), execute_data, /* out */ &unusedRetVal, __FUNCTION__ );
}

void tracerPhpPartOnRequestInitSetInitialTracerState() {
g_tracerPhpPartState = tracerPhpPartState_before_bootstrap;
}

ResultCode tracerPhpPartOnRequestInit( const ConfigSnapshot* config, const TimePoint* requestInitStartTime )
{
g_tracerPhpPartState = tracerPhpPartState_before_bootstrap;
return bootstrapTracerPhpPart( config, requestInitStartTime );
}

Expand Down
1 change: 1 addition & 0 deletions src/ext/tracer_PHP_part.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ void tracerPhpPartInterceptedCallEmptyMethod();

void tracerPhpPartAstInstrumentationCallPreHook( zend_execute_data* execute_data, zval* return_value );
void tracerPhpPartAstInstrumentationDirectCall( zend_execute_data* execute_data );
void tracerPhpPartOnRequestInitSetInitialTracerState();
58 changes: 58 additions & 0 deletions src/ext/util_for_PHP.c
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,64 @@ bool isPhpRunningAsCliScript()
return strcmp( sapi_module.name, "cli" ) == 0;
}

int call_internal_function(zval *object, const char *functionName, zval parameters[], int32_t parametersCount, zval *returnValue) {
zval funcName;
ZVAL_STRING(&funcName, functionName);

int result = resultFailure;
zend_try {
#if PHP_VERSION_ID >= 80000
result = _call_user_function_impl(object, &funcName, returnValue, parametersCount, parameters, NULL);
#else
result = _call_user_function_ex(object, &funcName, returnValue, parametersCount, parameters, 0);
#endif
} zend_catch {
ELASTIC_APM_LOG_ERROR("Call of '%s' failed", functionName);
} zend_end_try();

zval_ptr_dtor(&funcName);
return result;
}


bool detectOpcacheRestartPending() {
bool opcacheEnabled = isPhpRunningAsCliScript() ? INI_BOOL("opcache.enable_cli") : INI_BOOL("opcache.enable");
if (!opcacheEnabled) {
return false;
}
if (EG(function_table) && !zend_hash_str_find_ptr(EG(function_table), ZEND_STRL("opcache_get_status"))) {
return false;
}

zval rv;
ZVAL_NULL(&rv);
zval parameters[1];
ZVAL_BOOL(&parameters[0], false);

int result = call_internal_function(NULL, "opcache_get_status", parameters, 1, &rv);
if (result == resultFailure) {
zval_ptr_dtor(&rv);
return false;
}

if (Z_TYPE(rv) != IS_ARRAY) {
zval_ptr_dtor(&rv);
return false;
}

zval *restartPending = zend_hash_str_find(Z_ARRVAL(rv), ZEND_STRL("restart_pending"));
if (restartPending && Z_TYPE_P(restartPending) == IS_TRUE) {
zval_ptr_dtor(&rv);
return true;
} else if (!restartPending || Z_TYPE_P(restartPending) != IS_FALSE) {
ELASTIC_APM_LOG_ERROR("opcache_get_status returned unexpected data ptr: %p t:%d", restartPending, restartPending ? Z_TYPE_P(restartPending) : -1);
}

zval_ptr_dtor(&rv);
return false;
}


bool detectOpcachePreload() {
if (PHP_VERSION_ID < 70400) {
return false;
Expand Down
1 change: 1 addition & 0 deletions src/ext/util_for_PHP.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ void getArgsFromZendExecuteData( zend_execute_data *execute_data, size_t dstArra

bool isPhpRunningAsCliScript();
bool detectOpcachePreload();
bool detectOpcacheRestartPending();
void enableAccessToServerGlobal();

#define ELASTIC_APM_ZEND_ADD_ASSOC( map, key, valueType, value ) ELASTIC_APM_PP_CONCAT( ELASTIC_APM_PP_CONCAT( add_assoc_, valueType ), _ex)( (map), (key), sizeof( key ) - 1, (value) )
Expand Down

0 comments on commit d41a80f

Please sign in to comment.