-
Notifications
You must be signed in to change notification settings - Fork 842
Improve Post Stats cache handling for invalid or error data #46211
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
Changes from 6 commits
e0ceb39
8877038
a736465
ffe8c0c
3c3624b
4ba2b0e
68d3fe3
3bbe021
5b9fc39
bd5a7b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: changed | ||
|
|
||
| Improve Post Stats cache handling for invalid or error data |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -503,34 +503,57 @@ protected function fetch_post_stats( $args, $post_id ) { | |||||||||||||||||||||||||||||||||||||
| if ( $stats_cache ) { | ||||||||||||||||||||||||||||||||||||||
| $data = reset( $stats_cache ); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||
| ! is_array( $data ) | ||||||||||||||||||||||||||||||||||||||
| || empty( $data ) | ||||||||||||||||||||||||||||||||||||||
| || is_wp_error( $data ) | ||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||
| return $data; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| // Check if we have a valid cache structure with a time key. | ||||||||||||||||||||||||||||||||||||||
| if ( is_array( $data ) && ! empty( $data ) ) { | ||||||||||||||||||||||||||||||||||||||
| $time = key( $data ); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // If we have a numeric time, check if cache is still valid. | ||||||||||||||||||||||||||||||||||||||
| if ( is_numeric( $time ) ) { | ||||||||||||||||||||||||||||||||||||||
| /** This filter is already documented in projects/packages/stats/src/class-wpcom-stats.php */ | ||||||||||||||||||||||||||||||||||||||
| $expiration = apply_filters( | ||||||||||||||||||||||||||||||||||||||
| 'jetpack_fetch_stats_cache_expiration', | ||||||||||||||||||||||||||||||||||||||
| self::STATS_CACHE_EXPIRATION_IN_MINUTES * MINUTE_IN_SECONDS | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $time = key( $data ); | ||||||||||||||||||||||||||||||||||||||
| $views = $data[ $time ] ?? null; | ||||||||||||||||||||||||||||||||||||||
| // If within cache period, return cached data regardless of validity. | ||||||||||||||||||||||||||||||||||||||
| if ( ( time() - $time ) < $expiration ) { | ||||||||||||||||||||||||||||||||||||||
| $cached_value = $data[ $time ]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Bail if data is malformed. | ||||||||||||||||||||||||||||||||||||||
| if ( ! is_numeric( $time ) || ! is_array( $views ) ) { | ||||||||||||||||||||||||||||||||||||||
| return $data; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| // If it's a WP_Error, return it directly. | ||||||||||||||||||||||||||||||||||||||
| if ( is_wp_error( $cached_value ) ) { | ||||||||||||||||||||||||||||||||||||||
| return $cached_value; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** This filter is already documented in projects/packages/stats/src/class-wpcom-stats.php */ | ||||||||||||||||||||||||||||||||||||||
| $expiration = apply_filters( | ||||||||||||||||||||||||||||||||||||||
| 'jetpack_fetch_stats_cache_expiration', | ||||||||||||||||||||||||||||||||||||||
| self::STATS_CACHE_EXPIRATION_IN_MINUTES * MINUTE_IN_SECONDS | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| // If it's an array, merge with cached_at timestamp. | ||||||||||||||||||||||||||||||||||||||
| if ( is_array( $cached_value ) ) { | ||||||||||||||||||||||||||||||||||||||
| return array_merge( array( 'cached_at' => $time ), $cached_value ); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
| // If it's a WP_Error, return it directly. | |
| if ( is_wp_error( $cached_value ) ) { | |
| return $cached_value; | |
| } | |
| /** This filter is already documented in projects/packages/stats/src/class-wpcom-stats.php */ | |
| $expiration = apply_filters( | |
| 'jetpack_fetch_stats_cache_expiration', | |
| self::STATS_CACHE_EXPIRATION_IN_MINUTES * MINUTE_IN_SECONDS | |
| ); | |
| // If it's an array, merge with cached_at timestamp. | |
| if ( is_array( $cached_value ) ) { | |
| return array_merge( array( 'cached_at' => $time ), $cached_value ); | |
| } | |
| // If it's an array or WP_Error, add cached time and return to user. | |
| if ( is_array( $cached_value ) || is_wp_error( $cached_value ) ) { | |
| return array_merge( array( 'cached_at' => $time ), (array)$cached_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.
Updated in 68d3fe3.
Outdated
Copilot
AI
Dec 9, 2025
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.
Lines 532-533 will return any non-array, non-WP_Error cached value as-is, which could perpetuate invalid cached data (e.g., null, string, boolean) for the entire cache period. This contradicts the PR's goal of "detecting and recovering from invalid cache entries."
Consider either:
- Only returning the cached value if it's an array or WP_Error, falling through to refresh otherwise
- Adding explicit validation that cached_value is an expected type
Example:
// For any other unexpected type, treat as malformed cache.
// Fall through to refresh.Then remove the return statement and let execution continue to line 540.
| // For any other type, return as-is. | |
| return $cached_value; | |
| // For any other unexpected type, treat as malformed cache. | |
| // Fall through to refresh. |
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.
Agree with Copilot here - other cases should all fall to malformed types?
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.
other cases should all fall to malformed types?
Looks reasonable to me, like only leave expected WP_Error thrown from cache.
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.
Updated in 68d3fe3.
kangzj marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Dec 9, 2025
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 new refresh_post_stats_cache() method and the modified cache validation logic in fetch_post_stats() lack test coverage. Given that this package has comprehensive test coverage (as evidenced by WPCOM_Stats_Test.php with 863 lines of tests), the following scenarios should be tested:
- Invalid/malformed cache entries trigger a refresh
- WP_Error cached entries are handled correctly within and beyond expiration
- The new
refresh_post_stats_cache()method properly caches both success and error responses - Cache expiration logic works correctly with the new validation
This is critical since the PR specifically addresses a bug where "post stats cache could get stuck on invalid data or a WP_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.
@copilot open a new pull request to apply changes based on this feedback
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 comment "return cached data regardless of validity" is misleading. The code immediately validates the cached value by checking if it's a WP_Error or array (lines 522-530). Consider updating the comment to: "If within cache period, return cached data after type validation."