-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Refactor CLI SAPI php_cli_server_client struct to use zend_string #8522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
162db64
to
3bd53e3
Compare
What is the advantage of this change? What I can see now is that one has to use Or do you plan to submit followups so that functions which take the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind these changes. Although I personally think we should avoid too many TODOs and fix those cases before merging (because famously TODOs that hit production never actually get fixed).
sapi/cli/php_cli_server.c
Outdated
@@ -623,9 +620,10 @@ static int sapi_cli_server_register_entry_cb(char **entry, int num_args, va_list | |||
} | |||
spprintf(&real_key, 0, "%s_%s", "HTTP", key); | |||
if (strcmp(key, "CONTENT_TYPE") == 0 || strcmp(key, "CONTENT_LENGTH") == 0) { | |||
sapi_cli_server_register_variable(track_vars_array, key, *entry); | |||
// TODO make a version specialized for zend_string? | |||
sapi_cli_server_register_variable(track_vars_array, key, ZSTR_VAL(*entry)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could at least save a strlen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I think ideally I'll get everything else which uses this function to be a zend_string which would indeed save a strlen
.
sapi/cli/php_cli_server.c
Outdated
case HEADER_VALUE: { | ||
/* Previous element was part of header value, append content to it */ | ||
size_t old_length = ZSTR_LEN(client->current_header_value); | ||
// TODO Release old value? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the implementation of zend_string_extend
, it returns a realloc
ated string if the string was non-interned and the refcount was === 1, and a new string otherwise. That's honestly not great design because there's no way to know if the returned string is copied or reallocated and if the old string needs to be released or not without basically performing the same check.
In this particular case, it looks like it is never interned and the refcount is always === 1, so the old string doesn't need to be released. Maybe it would make sense to add a ZEND_ASSERT
to be sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add an assertion, as I was wondering why I wasn't hitting any memory leaks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you also run with ASAN
? Memory leaks are only reported for non-persistent memory tracked by ZMM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I did will do that when I get back.
But I'm also struggling to understand why the memory needs to be allocated persistently in the first place. As I haven't figured out how that memory is freed in the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the implementation of
zend_string_extend
, it returns arealloc
ated string if the string was non-interned and the refcount was === 1, and a new string otherwise
This follows a copy on write semantic, I don't think we need to care about the original string after a zend_string_extend()
: The other owners will release it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the memory needs to be allocated persistently in the first place
If it's used before or after
zend_execute_scripts
, the non-persistent allocator may not be started, or maybe shutdown.
That's good to know, so everything needs to be persistently allocated then in the SAPI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I may be wrong here. The allocator is fully available as soon as sapi_cli_server_startup() and is reset in php_cli_server_request_shutdown(). So it's possible to allocate non-persistent things outside of zend_execute_script as long as their lifetime don't extend after php_cli_server_request_shutdown().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it does seem that FreeBSD is more picky about this stuff than Linux. But lets see if me not duplicating a string breaks it again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, I do think every string here needs to be persistently allocated.
I think I will add some _ex
variant of some APIs to pass a persistent flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, I do think every string here needs to be persistently allocated.
I confirm. I dug more in the cli server, and what I missed originally is that it parses multiple requests concurrently. It results that parsing and script execution (and allocator shutdown) are interleaved.
So the plan is indeed to do follow up PRs to convert more pairs of Ideally, all SAPIs would use zend_strings so that we can change the "higher generic lever" SAPI API to zend_strings to then add and utilize some interned strings such as HTTP methods name, or common header fields, as this is recurrent and slightly suboptimal to always recreate this string when it's a core part of a response. Hope this clarifies why I started working on this. |
07cf0a2
to
0ce4dab
Compare
I think this is now ready, and hopefully addresses all the coments. |
sapi/cli/php_cli_server.c
Outdated
case HEADER_VALUE: { | ||
/* Previous element was part of header value, append content to it */ | ||
size_t old_length = ZSTR_LEN(client->current_header_value); | ||
// TODO Release old value? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, I do think every string here needs to be persistently allocated.
I confirm. I dug more in the cli server, and what I missed originally is that it parses multiple requests concurrently. It results that parsing and script execution (and allocator shutdown) are interleaved.
sapi/cli/php_cli_server.c
Outdated
ZEND_FALLTHROUGH; | ||
case HEADER_NONE: | ||
/* Create new header field */ | ||
client->current_header_name = zend_string_init(at, length, /* persistent */ false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one needs to be persistent too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
otherwise it's freed too early in this case:
$fd = stream_socket_client("tcp://localhost:8082");
fwrite($fd, "GET /index.php HTTP/1.0\r\nHost: hello");
$fd2 = stream_socket_client("tcp://localhost:8082");
fwrite($fd2, "GET /index.php HTTP/1.0\r\nConnection: close\r\n\r\n");
stream_copy_to_stream($fd2, STDOUT);
fwrite($fd, "\r\n\r\n");
stream_copy_to_stream($fd, STDOUT);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't manage to reproduce this locally. Hopefully I fixed the issue.
Thanks to Arnaud for figuring this out
@cmb69 I don't really understand why Windows is failing, could you have a look into it? |
@Girgias, looks like the culprit are the changes to php_cli_server.inc, especially not closing stderr (apparently, on Windows some spurious log output is sent to stderr, after the server has started, and that lets the test fail). Might be best to stick with the previous redirection of stderr to a pipe, and to close that pipe after the listen address has been identified. |
@cmb69 the changes cause php_cli_server.inc to print the server's stderr when |
@arnaud-lb, ah, right! The problem is php-src/ext/standard/proc_open.c Line 324 in 926407f
|
No description provided.