Skip to content

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

Merged
merged 25 commits into from
May 15, 2022

Conversation

Girgias
Copy link
Member

@Girgias Girgias commented May 10, 2022

No description provided.

@Girgias Girgias force-pushed the cli-sapi-2 branch 3 times, most recently from 162db64 to 3bd53e3 Compare May 11, 2022 22:00
@Girgias Girgias marked this pull request as ready for review May 11, 2022 22:00
@Girgias Girgias changed the title Refactor CLI SAPI to use zend_string Refactor CLI SAPI php_cli_server_client struct to use zend_string May 11, 2022
@kocsismate
Copy link
Member

What is the advantage of this change? What I can see now is that one has to use ZSTR_VAL() and ZSTR_LEN() all over the place instead of directly passing the necessary values.

Or do you plan to submit followups so that functions which take the char * parameters are also changed to accept zend_strings (i.e. php_base64_decode())?

Copy link
Member

@iluuu1994 iluuu1994 left a 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).

@@ -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));
Copy link
Member

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

Copy link
Member Author

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.

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?
Copy link
Member

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 reallocated 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.

Copy link
Member Author

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

Copy link
Member

@iluuu1994 iluuu1994 May 12, 2022

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.

Copy link
Member Author

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.

Copy link
Member

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 reallocated 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.

Copy link
Member Author

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?

Copy link
Member

@arnaud-lb arnaud-lb May 13, 2022

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().

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member

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.

@Girgias
Copy link
Member Author

Girgias commented May 12, 2022

So the plan is indeed to do follow up PRs to convert more pairs of char* and length fields/args to use zend_string*. Mainly because we have usually better APIs with zend_strings, and we have some internal API which only take a zend_string.
Moreover, passing zend_string will prevent some strlen() recomputation (although one could argue just adding a size_t param is the better way to go).

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.

@Girgias Girgias force-pushed the cli-sapi-2 branch 3 times, most recently from 07cf0a2 to 0ce4dab Compare May 13, 2022 22:08
@Girgias Girgias requested a review from iluuu1994 May 14, 2022 11:50
@Girgias
Copy link
Member Author

Girgias commented May 14, 2022

I think this is now ready, and hopefully addresses all the coments.

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?
Copy link
Member

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.

ZEND_FALLTHROUGH;
case HEADER_NONE:
/* Create new header field */
client->current_header_name = zend_string_init(at, length, /* persistent */ false);
Copy link
Member

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

Copy link
Member

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);

Copy link
Member Author

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.

@Girgias
Copy link
Member Author

Girgias commented May 15, 2022

@cmb69 I don't really understand why Windows is failing, could you have a look into it?

@cmb69
Copy link
Member

cmb69 commented May 15, 2022

@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.

@arnaud-lb
Copy link
Member

@cmb69 the changes cause php_cli_server.inc to print the server's stderr when proc_get_status()['exitstatus'] is not 0. Is 255 a normal status code after a proc_terminate() on windows ?

@cmb69
Copy link
Member

cmb69 commented May 15, 2022

@arnaud-lb, ah, right! The problem is

RETURN_BOOL(TerminateProcess(proc->childHandle, 255));

@Girgias Girgias merged commit 9b19d90 into php:master May 15, 2022
@Girgias Girgias deleted the cli-sapi-2 branch May 15, 2022 20:11
nikic added a commit that referenced this pull request May 21, 2022
…ring (#8522)"

This reverts commit 9b19d90.

This has broken the ZEND_RC_DEBUG build.
Girgias added a commit to Girgias/php-src that referenced this pull request May 22, 2022
Girgias added a commit to Girgias/php-src that referenced this pull request May 22, 2022
Girgias added a commit to Girgias/php-src that referenced this pull request May 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants