Skip to content

Commit 8dc5c07

Browse files
committed
Merge branch 'lavarou/feat/frankenphp-worker-p1' into frankenphp-beta-1
2 parents 59bcd92 + e1322e1 commit 8dc5c07

6 files changed

Lines changed: 157 additions & 13 deletions

File tree

agent/config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ if test "$PHP_NEWRELIC" = "yes"; then
221221
php_call.c php_curl.c php_curl_md.c php_datastore.c php_environment.c \
222222
php_error.c php_execute.c php_explain.c php_explain_mysqli.c \
223223
php_explain_pdo_mysql.c php_extension.c php_file_get_contents.c \
224-
php_globals.c php_hash.c php_header.c php_httprequest_send.c \
224+
php_frankenphp.c php_globals.c php_hash.c php_header.c php_httprequest_send.c \
225225
php_internal_instrument.c php_memcached.c php_minit.c php_mshutdown.c php_mysql.c \
226226
php_mysqli.c php_newrelic.c php_nrini.c php_observer.c php_output.c php_pdo.c \
227227
php_pdo_mysql.c php_pdo_pgsql.c php_pgsql.c php_psr7.c php_redis.c \

agent/php_frankenphp.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2026 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include "php_frankenphp.h"
7+
#include "php_agent.h"
8+
#include "php_user_instrument.h"
9+
#include "php_wrapper.h"
10+
11+
#ifdef ZTS
12+
13+
static void nr_php_frankenphp_request_handler_fcall_begin(
14+
zend_execute_data* execute_data NRUNUSED) {
15+
nrl_verbosedebug(NRL_INSTRUMENT,
16+
"frankenphp_request_handler_fcall_begin started");
17+
// emulate rinit
18+
nr_php_txn_begin(0, 0);
19+
nrl_verbosedebug(NRL_INSTRUMENT,
20+
"frankenphp_request_handler_fcall_begin done");
21+
}
22+
23+
static void nr_php_frankenphp_request_handler_fcall_end(
24+
zend_execute_data* execute_data NRUNUSED,
25+
zval* return_value NRUNUSED) {
26+
nrl_verbosedebug(NRL_INSTRUMENT,
27+
"frankenphp_request_handler_fcall_end started");
28+
// emulate rshutdown
29+
nr_php_txn_end(0, 0);
30+
nrl_verbosedebug(NRL_INSTRUMENT, "frankenphp_request_handler_fcall_end done");
31+
}
32+
33+
// Wrap the request handler with before and after hooks to emulate rinit and
34+
// rshutdown
35+
void nr_php_frankenphp_handle_request(INTERNAL_FUNCTION_PARAMETERS) {
36+
zval* function = NULL;
37+
zend_function* zf;
38+
nruserfn_t* wr = NULL;
39+
40+
// Always end current transaction started when worker was started
41+
nr_txn_set_path("frankenphp_handle_request", NRPRG(txn), "frankenphp/worker",
42+
NR_PATH_TYPE_CUSTOM, NR_OK_TO_OVERWRITE);
43+
nr_txn_set_as_background_job(NRPRG(txn), "frankenphp worker");
44+
nr_php_txn_end(0, 0);
45+
46+
zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "z",
47+
&function);
48+
zf = nr_php_zval_to_function(function);
49+
50+
wr = nr_php_wrap_callable(zf, NULL);
51+
if (NULL == wr) {
52+
nrl_verbosedebug(NRL_INSTRUMENT,
53+
"Failed to create wraprec for frankenphp request handler");
54+
return;
55+
}
56+
wr->fcall_handlers.begin = nr_php_frankenphp_request_handler_fcall_begin;
57+
wr->fcall_handlers.end = nr_php_frankenphp_request_handler_fcall_end;
58+
}
59+
#endif /* ZTS */

agent/php_frankenphp.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2026 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifndef PHP_FRANKENPHP_H
7+
#define PHP_FRANKENPHP_H
8+
9+
#include "php_includes.h"
10+
11+
#ifdef ZTS
12+
13+
extern void nr_php_frankenphp_handle_request(INTERNAL_FUNCTION_PARAMETERS);
14+
15+
#endif
16+
17+
#endif

agent/php_internal_instrument.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "php_explain.h"
1313
#include "php_explain_mysqli.h"
1414
#include "php_file_get_contents.h"
15+
#include "php_frankenphp.h"
1516
#include "php_globals.h"
1617
#include "php_hash.h"
1718
#include "php_httprequest_send.h"
@@ -113,6 +114,28 @@ static void nr_php_instrument_delegate(nrinternalfn_t* wraprec,
113114
(wraprec->inner_wrapper)(INTERNAL_FUNCTION_PARAM_PASSTHRU, wraprec);
114115
}
115116

117+
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO
118+
/*
119+
* This delegate is used for the wrappers, which need to execute its inner
120+
* wrapper on every call, even when not recording. This can be used for starting
121+
* the transaction and thus needs to execute its inner wrapper to start
122+
* recording.
123+
*/
124+
static void nr_php_instrument_delegate_always(nrinternalfn_t* wraprec,
125+
INTERNAL_FUNCTION_PARAMETERS) {
126+
if ((NULL == wraprec) || (NULL == wraprec->oldhandler)
127+
|| (NULL == wraprec->inner_wrapper)) {
128+
/*
129+
* This conditional should never happen: The wraprec and its fields
130+
* should be properly populated during construction. Perhaps this should
131+
* be changed into some sort of assertion.
132+
*/
133+
return;
134+
}
135+
(wraprec->inner_wrapper)(INTERNAL_FUNCTION_PARAM_PASSTHRU, wraprec);
136+
}
137+
#endif
138+
116139
#define NR_OUTER_WRAPPER_NAME(RAW) _nr_outer_wrapper_function_##RAW
117140
#define NR_OUTER_GLOBAL_NAME(RAW) _nr_outer_wrapper_global_##RAW
118141
#define NR_INNER_WRAPPER_NAME(RAW) _nr_inner_wrapper_function_##RAW
@@ -142,6 +165,13 @@ static void nr_php_instrument_delegate(nrinternalfn_t* wraprec,
142165
nr_php_instrument_delegate(NR_OUTER_GLOBAL_NAME(RAW), \
143166
INTERNAL_FUNCTION_PARAM_PASSTHRU); \
144167
}
168+
#define NR_OUTER_WRAPPER_DELEGATE_ALWAYS(RAW) \
169+
static nrinternalfn_t* NR_OUTER_GLOBAL_NAME(RAW) = NULL; \
170+
static void ZEND_FASTCALL NR_OUTER_WRAPPER_NAME(RAW)( \
171+
INTERNAL_FUNCTION_PARAMETERS) { \
172+
nr_php_instrument_delegate_always(NR_OUTER_GLOBAL_NAME(RAW), \
173+
INTERNAL_FUNCTION_PARAM_PASSTHRU); \
174+
}
145175
#endif /* PHP < 7.3 */
146176

147177
/*
@@ -3089,6 +3119,18 @@ NR_INNER_WRAPPER(exception_common) {
30893119
}
30903120
}
30913121

3122+
#ifdef ZTS
3123+
NR_INNER_WRAPPER(frankenphp_handle_request) {
3124+
nrl_verbosedebug(NRL_INIT, "frankenphp_handle_request started");
3125+
3126+
nr_php_frankenphp_handle_request(INTERNAL_FUNCTION_PARAM_PASSTHRU);
3127+
3128+
// Let frankenphp take over
3129+
nr_wrapper->oldhandler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
3130+
nrl_verbosedebug(NRL_INIT, "frankenphp_handle_request done");
3131+
}
3132+
#endif
3133+
30923134
NR_OUTER_WRAPPER(mysql_select_db)
30933135
NR_OUTER_WRAPPER(mysql_selectdb)
30943136
NR_OUTER_WRAPPER(mysql_close)
@@ -3538,6 +3580,10 @@ NR_OUTER_WRAPPER(dl)
35383580
NR_OUTER_WRAPPER(set_exception_handler)
35393581
NR_OUTER_WRAPPER(restore_exception_handler)
35403582

3583+
#ifdef ZTS
3584+
NR_OUTER_WRAPPER_DELEGATE_ALWAYS(frankenphp_handle_request)
3585+
#endif /* ZTS */
3586+
35413587
nrinternalfn_t* nr_wrapped_internal_functions = NULL;
35423588

35433589
static nrinternalfn_t* nr_get_wraprec(void) {
@@ -4391,6 +4437,10 @@ void nr_php_generate_internal_wrap_records(void) {
43914437
exception_common, 0, 0)
43924438
NR_INTERNAL_WRAPREC("restore_exception_handler", restore_exception_handler,
43934439
exception_common, 0, 0)
4440+
#ifdef ZTS
4441+
NR_INTERNAL_WRAPREC("frankenphp_handle_request", frankenphp_handle_request,
4442+
frankenphp_handle_request, 0, 0)
4443+
#endif
43944444
}
43954445

43964446
/*

agent/php_observer.c

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
static zend_observer_fcall_handlers nr_php_fcall_register_handlers(
7676
zend_execute_data* execute_data) {
7777
zend_observer_fcall_handlers handlers = {NULL, NULL};
78+
nruserfn_t* wr = NULL;
7879
if (NULL == execute_data) {
7980
return handlers;
8081
}
@@ -83,7 +84,11 @@ static zend_observer_fcall_handlers nr_php_fcall_register_handlers(
8384
return handlers;
8485
}
8586

86-
if (0 == nr_php_recording()) {
87+
if (0 == nr_php_recording()
88+
#ifdef ZTS
89+
&& !nr_streq(sapi_module.name, "frankenphp")
90+
#endif
91+
) {
8792
return handlers;
8893
}
8994

@@ -110,23 +115,33 @@ static zend_observer_fcall_handlers nr_php_fcall_register_handlers(
110115
return handlers;
111116
}
112117

113-
if (!ZEND_OP_ARRAY_EXTENSION(
114-
NR_OP_ARRAY, NR_PHP_PROCESS_GLOBALS(op_array_extension_handle))) {
118+
// clang-format off
119+
wr = ZEND_OP_ARRAY_EXTENSION(NR_OP_ARRAY, NR_PHP_PROCESS_GLOBALS(op_array_extension_handle));
120+
if (NULL == wr) {
115121
zend_string* func_name = NR_OP_ARRAY->function_name;
116-
zend_string* scope_name
117-
= OP_ARRAY_IS_A_METHOD(NR_OP_ARRAY) ? NR_OP_ARRAY->scope->name : NULL;
118-
nruserfn_t* wr
119-
= nr_php_user_instrument_wraprec_hashmap_get(func_name, scope_name);
120-
// store the wraprec in the op_array extension for the duration of the
121-
// request for later lookup
122-
ZEND_OP_ARRAY_EXTENSION(NR_OP_ARRAY,
123-
NR_PHP_PROCESS_GLOBALS(op_array_extension_handle))
124-
= wr;
122+
zend_string* scope_name = OP_ARRAY_IS_A_METHOD(NR_OP_ARRAY) ? NR_OP_ARRAY->scope->name : NULL;
123+
wr = nr_php_user_instrument_wraprec_hashmap_get(func_name, scope_name);
124+
// store the wraprec in the op_array extension for the duration of the request for later lookup
125+
ZEND_OP_ARRAY_EXTENSION(NR_OP_ARRAY, NR_PHP_PROCESS_GLOBALS(op_array_extension_handle)) = wr;
126+
} else {
127+
if (nrl_should_print(NRL_VERBOSEDEBUG, NRL_INSTRUMENT)) {
128+
nrl_verbosedebug(NRL_INSTRUMENT, "%s - wraprec already installed", __func__);
129+
}
125130
}
126131

132+
// use default handlers for all user functions
127133
handlers.begin = nr_php_observer_fcall_begin;
128134
handlers.end = nr_php_observer_fcall_end;
135+
136+
// override default handlers with wraprec's custom handlers
137+
if (NULL != wr) {
138+
if (NULL != wr->fcall_handlers.begin)
139+
handlers.begin = wr->fcall_handlers.begin;
140+
if (NULL != wr->fcall_handlers.end)
141+
handlers.end = wr->fcall_handlers.end;
142+
}
129143
return handlers;
144+
// clang-format on
130145
}
131146

132147
#if ZEND_MODULE_API_NO > ZEND_8_0_X_API_NO /* PHP8.1+ */

agent/php_user_instrument.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ typedef struct _nruserfn_t {
110110
#if ZEND_MODULE_API_NO >= ZEND_7_4_X_API_NO
111111
char* wordpress_plugin_theme;
112112
#endif
113+
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO
114+
zend_observer_fcall_handlers fcall_handlers;
115+
#endif
113116
} nruserfn_t;
114117

115118
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO

0 commit comments

Comments
 (0)