-
Notifications
You must be signed in to change notification settings - Fork 101
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
Disregard transient cache in perflab_query_plugin_info() when a plugin is absent #1694
base: trunk
Are you sure you want to change the base?
Conversation
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. |
__( 'Plugin not found in WordPress.org API response.', 'performance-lab' ) | ||
); | ||
// Cache the fact that the plugin was not found. | ||
$plugins[ $current_plugin_slug ] = 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.
Instead of storing false
here, what about storing the WP_Error
instance? As it stands right now, there are two conditions resulting in false
being logged, which conflates these two error scenarios which we tried to get rid of in #1651 to assist with debugging.
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.
Overall this makes sense to me. We probably shouldn't store the WP_Error
instance though, but just its data, since storing PHP class instances is a bit unpredictable with what would happen in different object cache implementations.
@@ -101,9 +102,14 @@ function perflab_query_plugin_info( string $plugin_slug ) { | |||
} | |||
} | |||
|
|||
if ( ! isset( $plugins[ $plugin_slug ] ) ) { | |||
// Cache the fact that the plugin was not found. | |||
$plugins[ $plugin_slug ] = 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.
See above about storing a WP_Error
instance instead.
if ( false === $plugins[ $plugin_slug ] ) { | ||
// Plugin was requested before and not found. | ||
return new WP_Error( | ||
'plugin_not_found', | ||
__( 'Plugin not found in cached API response.', 'performance-lab' ) | ||
); | ||
} |
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.
If $plugins[ $plugin_slug ]
is either an array
or a WP_Error
, then this can just return that.
if ( false === $plugins[ $plugin_slug ] ) { | |
// Plugin was requested before and not found. | |
return new WP_Error( | |
'plugin_not_found', | |
__( 'Plugin not found in cached API response.', 'performance-lab' ) | |
); | |
} |
// Cache the fact that the plugin was not found. | ||
$plugins[ $plugin_slug ] = false; | ||
} | ||
|
||
set_transient( $transient_key, $plugins, HOUR_IN_SECONDS ); |
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.
Here I think it would be good to check if there was an error condition, and if so, set the expiration to 1 minute instead of HOUR_IN_SECONDS
.
There are still these error scenarios that isn't getting cached: performance/plugins/performance-lab/includes/admin/plugins.php Lines 58 to 72 in 2a344c9
|
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 @b1ink0! This looks good, just one thing that needs to be fixed I think.
|
||
set_transient( $transient_key, $plugins, MINUTE_IN_SECONDS ); | ||
} else { | ||
set_transient( $transient_key, $plugins, HOUR_IN_SECONDS ); | ||
} |
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 differentiation does not happen in the correct clause. We should use a shorter cache duration in case of any error, not just in the case of this specific error.
I think it would be most straightforward to implement this via a boolean flag like $has_errors
that is set if anywhere in the (uncached) logic an error is set. Then the code here could be changed like:
set_transient( $transient_key, $plugins, MINUTE_IN_SECONDS ); | |
} else { | |
set_transient( $transient_key, $plugins, HOUR_IN_SECONDS ); | |
} | |
} | |
if ( $has_errors ) { | |
set_transient( $transient_key, $plugins, MINUTE_IN_SECONDS ); | |
} else { | |
set_transient( $transient_key, $plugins, HOUR_IN_SECONDS ); | |
} |
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.
@b1ink0 There are two points of follow-up feedback, though some of them have overlap with #1691. But since this is broken too and affected by the same logic, maybe we should work towards a solution that addresses both?
cc @westonruter
if ( is_wp_error( $response ) ) { | ||
return new WP_Error( | ||
'api_error', | ||
sprintf( | ||
/* translators: %s: API error message */ | ||
__( 'Failed to retrieve plugins data from WordPress.org API: %s', 'performance-lab' ), | ||
$response->get_error_message() | ||
) | ||
$plugins[ $plugin_slug ] = array( | ||
'error' => array( | ||
'code' => 'api_error', | ||
'message' => sprintf( | ||
/* translators: %s: API error message */ | ||
__( 'Failed to retrieve plugins data from WordPress.org API: %s', 'performance-lab' ), | ||
$response->get_error_message() | ||
), | ||
), | ||
); | ||
|
||
$has_errors = true; | ||
} | ||
|
||
// Check if the response contains plugins. | ||
if ( ! ( is_object( $response ) && property_exists( $response, 'plugins' ) ) ) { | ||
return new WP_Error( 'no_plugins', __( 'No plugins found in the API response.', 'performance-lab' ) ); | ||
if ( ! $has_errors && ! ( is_object( $response ) && property_exists( $response, 'plugins' ) ) ) { | ||
$plugins[ $plugin_slug ] = array( | ||
'error' => array( | ||
'code' => 'no_plugins', | ||
'message' => __( 'No plugins found in the API response.', 'performance-lab' ), | ||
), | ||
); | ||
|
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 think for both of these scenarios we need to set the error for every single plugin of ours. In other words, iterate through the list from perflab_get_standalone_plugins()
and set the same error for every plugin's slug.
Otherwise, this can cause a problem where the API failing would cause the request to be issued for every single plugin, which would defeat the purpose of the transient cache and the combination of those plugin requests into a single API request (see #1542). This would effectively fix #1691, and since this PR is touching the exact same logic, I wonder whether we should go straight to a solution that addresses both issues.
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.
To make things clear as its getting confusing please answer these below question or my understanding of the solution needed.
- If error is encounterd at line
57
and76
we should add the error to the allperflab_get_standalone_plugins
plugins so that if any one of the plugin is requested it should return error?
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.
Correct. That's because a failing API response is by definition an error that affects all plugins.
if ( ! isset( $plugins[ $plugin_slug ] ) ) { | ||
// Cache the fact that the plugin was not found. | ||
$plugins[ $plugin_slug ] = array( | ||
'error' => array( | ||
'code' => 'plugin_not_found', | ||
'message' => __( 'Plugin not found in API response.', 'performance-lab' ), | ||
), | ||
); | ||
|
||
// Enqueue the required plugins slug by adding it to the queue. | ||
if ( isset( $plugin_data['requires_plugins'] ) && is_array( $plugin_data['requires_plugins'] ) ) { | ||
$plugin_queue = array_merge( $plugin_queue, $plugin_data['requires_plugins'] ); | ||
$has_errors = true; | ||
} |
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.
Now that I look at this again, what is this particular check needed for? Wouldn't this already be catered for by the check above in line 101?
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 check is needed because if the $plugin_slug
is not in the perflab_get_standalone_plugins()
then this $plugin_slug
will not be processed inside the loop.
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.
In that case, I think we should make this check at the very beginning of the function, even before looking at the transient cache. This function should never be used for a plugin that is not among our plugins I think, so we could already check that before all the main logic in this function.
We wouldn't want that someone calling the function with e.g. jetpack
would trigger this API request and logic to run for every single page load just because jetpack
would (rightfully) never be included in the response.
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.
If we only check the $plugin_slug
inside perflab_get_standalone_plugins()
it will also cause error for the dependency plugins. And if we want to dynamically check the dependency then we need to request the API.
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 does the function need to be called for dependency plugins?
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.
The perflab_render_plugin_card()
function is calling the perflab_get_plugin_availability()
function
and inside the perflab_get_plugin_availability()
on here it is calling the perflab_query_plugin_info()
for the dependency plugin.
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.
Ah, that's a good point. But in that case, I think at least this error should use a different message? I would try to clarify it as: If the plugin is not found at this point, we know it's not one of our plugins, since it's not in the API response, and the API response should always included all our plugins plus its dependencies.
'message' => __( 'Plugin not found in API response.', 'performance-lab' ), | ||
), | ||
); | ||
continue; |
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.
Due to the recent commit ff1b440 this could cause incorrect behavour if $plugin_slug
is not found in the all_performance_plugins
it will add error for that $plugin_slug
as we are now using the $has_errors
flag which will not be set to true
in this case. The below logic will fail.
Solution could be to check it using the isset( $plugins[ $plugin_slug ]['error'] )
condition but it will defect the usage of the $has_errors
flag.
if ( $has_errors ) {
set_transient( $transient_key, $plugins, MINUTE_IN_SECONDS );
return new WP_Error(
$plugins[ $plugin_slug ]['error']['code'],
$plugins[ $plugin_slug ]['error']['message']
);
}
Reason for the $has_errors
flag not setting: As the errors could occur for other plugin ( not $plugin_slug
) currently code will cache the error but will return the requested $plugin_slug
information.
Question: Should we return error even if the error is encountered for the other plugin ( not $plugin_slug
) ?
cc: @felixarntz
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 my reply in #1694 (comment) - I think this problem would become irrelevant if we checked that the plugin slug is among our performance plugins in the beginning of the function?
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.
Revisiting this: Why should we not set the $has_errors
flag to true
here? I think we should, given this is an error 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.
Reason for the $has_errors flag not setting: As the errors could occur for other plugin ( not $plugin_slug ) currently code will cache the error but will return the requested $plugin_slug information.
Question: Should we return error even if the error is encountered for the other plugin ( not $plugin_slug ) ?
This above is reason. And question related to your question.
Summary
Fixes #1617
Relevant technical choices
When a new feature plugin is published, it may not yet exist in the transient. Therefore, whenever one of the expected feature plugin is absent, we should act as if the transient was not set in the first place so that we can obtain the latest plugin info right away.
Logic for this to work:
false
.false
raise the error.This logic will make sure that the multiple request are not made for the plugin which is absent.