-
Notifications
You must be signed in to change notification settings - Fork 108
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
Ensure the entire template is passed to the output buffer callback for Optimization Detective to process #1317
Conversation
01f7be3
to
b3ce59e
Compare
cea1286
to
13e068d
Compare
Update test-optimization.php
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
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.
@westonruter This is probably good, I'd just like to understand it better. :)
If this is merged, should we also update https://github.com/WordPress/performance/blob/trunk/plugins/performance-lab/includes/server-timing/class-perflab-server-timing.php#L272?
*/ | ||
return (string) apply_filters( 'od_template_output_buffer', $output ); | ||
static function ( string $output, ?int $phase ): string { | ||
if ( ( $phase & PHP_OUTPUT_HANDLER_FINAL ) > 0 ) { |
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.
A few questions so that I understand this:
- This is only relevant for when someone calls
ob_clean()
? Or for other things as well? - What's the purpose of
$phase
and what's the purpose ofPHP_OUTPUT_HANDLER_FINAL
in this? Why are both checks needed?
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 is only relevant for when someone calls
ob_clean()
? Or for other things as well?
That's a good point. There's also the consideration of ob_flush()
. Currently if this gets called then the resulting buffer passed into the filter is not complete. I've improved this in f218dc8 by making it so that the buffer is cleanable but not flushable.
- What's the purpose of
$phase
and what's the purpose ofPHP_OUTPUT_HANDLER_FINAL
in this? Why are both checks needed?
The $phase
provides information about why the handler was invoked. Note that bitwise operations are being used here (&
not &&
), so it is checking if the $phase
contains the final 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.
@westonruter Thanks, I had indeed missed the bitwise &, so this is only one check, makes sense.
Reading your code comments around the custom flag handling, I'm trying to understand: Does this mean that calling ob_flush()
anywhere while this output buffer is active will trigger an error? If so, I think we need to review whether/how that function is commonly used by plugins, mostly caching plugins of course. While it seems necessary for our functionality to be reliable, it also seems risky in terms of cross-plugin compatibility, like you're already indicating in your TODO.
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.
It won't cause an error, no. It just results in ob_flush()
returning false, which is included in the unit tests.
I'm addressing the TODO in another PR as it's not really related to output buffering.
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.
Ok, but what about breakage by flushing not being possible in a plugin where that might be intended? That's where I think we need to take a look at the ecosystem to see if that's a real or just a theoretical problem.
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.
That would be a situation where a plugin is attempting to flush the output buffer for a buffer it didn't open, which would be poor form. I did look over the instances of ob_flush()
in WPDirectory and I didn't see any obvious examples where ob_flush()
was being used in frontend contexts without also having called ob_start()
: so they'd only be flushing their own buffer. So I think we're fine here. We'll need to do more testing with actual sites to discover if there is a compatibility problem.
Actually, I don't think so because for server timing we're not manipulating the buffer in any way. We're simply using output buffering to delay sending the headers. |
$flags = PHP_OUTPUT_HANDLER_CLEANABLE; | ||
|
||
// When running unit tests the output buffer must also be removable in order to obtain the buffered output. | ||
if ( php_sapi_name() === 'cli' ) { | ||
// TODO: Do any caching plugins need the output buffer to be removable? This is unlikely, as they would pass an output buffer callback to ob_start() instead of calling ob_get_clean() at shutdown. | ||
$flags |= PHP_OUTPUT_HANDLER_REMOVABLE; | ||
} |
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 actually does need to be removable because WordPress core calls wp_ob_end_flush_all()
at shutdown
, and the lack of PHP_OUTPUT_HANDLER_REMOVABLE
results in:
Notice: ob_end_flush(): Failed to send buffer of Closure::__invoke (1) in /var/www/html/wp-includes/functions.php on line 5427
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.
Addressed in 0b4f2cd
@nextend I did some more testing and it seems there is something else this is not accounting for: whether there are any calls to
The only remaining question I have is whether ob_start( 'process_output_buffer' ); And: add_action( 'template_redirect', static function () {
ob_start();
add_action(
'shutdown',
function () {
echo process_output_buffer( ob_get_clean() );
}
);
} ); (This seems brittle given that other output buffers may have been opened. So additional logic would be needed to check if the And clearly the former is simpler! Also, if the output buffer were to be started without
To work around that, the remove_action( 'shutdown', 'wp_ob_end_flush_all', 1 ); I believe this may be obsolete since it was introduced to fix compatibility with PHP 5.2, but I'm wary of the implications for removing this. In particular, the
In core, this function just does function wp_cache_close() {
global $wp_object_cache;
return $wp_object_cache->close();
} These plugins in particular to something like the above:
So indeed it seems like we cannot use a non-removable output buffer without risking the object cache being closed when a plugin may want to leverage the |
assert( ( $phase & ( PHP_OUTPUT_HANDLER_FLUSH ) ) === 0 ); | ||
assert( ( $phase & ( PHP_OUTPUT_HANDLER_FINAL ) ) !== 0 ); |
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.
Not sure about using assert()
as I don't think there's a precedent in WP or this plugin for that. Can we use one of the more common ways to aid debugging in WordPress?
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.
These are sanity checks that should not ever return false given the flags.
There are instances of assert()
being used in core, although in bundled libraries: https://github.com/search?q=repo%3AWordPress%2Fwordpress-develop+%2Fassert%5C%28%2F+path%3A%2Fsrc%2F&type=code
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.
Right, but not in WordPress itself, and I don't know how relevant the presence of these flags is in said code, e.g. whether the code paths are even reached.
If they should never return false
, then I'd say we should either go with what's more established for debugging in WordPress, or simply omit them.
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.
OK. Removed in 364fa43.
@@ -139,7 +167,7 @@ function od_is_response_html_content_type(): bool { | |||
* @return string Filtered template output buffer. | |||
*/ | |||
function od_optimize_template_output_buffer( string $buffer ): string { | |||
if ( ! od_is_response_html_content_type() ) { | |||
if ( ! od_is_response_html_content_type() ) { // TODO: This should check to see if there is an HTML tag. |
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.
Is this TODO
intended for this PR or another issue?
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, I'm opening a sub-PR for this. It's unrelated to output buffering.
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.
See #1442
Could the way that Server-Timing code is currently written lead to duplicate output of the header? |
Oh, yes, you're right. |
Avoid sending Server-Timing header when buffer is being cleaned
…sponses Ensure only HTML documents are processed by Optimization Detective
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.
Thanks @westonruter!
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.
swell
This is based on @nextend's recommendation in WordPress/gutenberg#61212 (comment) feedback which is drafted in WordPress/gutenberg#62770. If a template calls
ob_clean()
at present, the output buffer being thrown away will be sent into the output buffer callback for needless processing even though it won't be served.This PR fixes addresses this issue by making sure the output buffer phase isAnother issue is that calls toPHP_OUTPUT_HANDLER_FINAL
.ob_flush()
will result in template fragments being sent into the output buffer callback. To address these issues:PHP_OUTPUT_HANDLER_FLUSHABLE
removed. This results in calls toob_flush()
not having any effect.ob_clean()
, in which case the callback short-circuits and does not pass the buffer into the filter for processing.For more details, see #1317 (comment).