The Last-Modified HTTP header, used to check if a cached page has been modified since it was cached, is wrongly set under certain circumstances:
Conditions:
- $wgUseSquid = true
- A page has been touched later than $wgCacheEpoch and more than $wgSquidMaxage seconds ago.
In OutputPage::checkLastModified due to T46570 it was added:
if ( $config->get( 'UseSquid' ) ) { // T46570: the core page itself may not change, but resources might $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) ); }
This adds another time to later use the most recent time for the Last-Modified. The problem is: it calculates a date that moves every second. The date is always $wgSquidMaxage seconds before "now".
The default value of $wgSquidMaxage is 18000 seconds (5 hours). If a page is last modified more than 5 hours ago, the $modifiedTimes['sepoch'] timestamp will likely be the most recent one, and the Last-Modified: header will be constantly moving every second, making each request with an If-Last-Modified header return the full page again instead of the cached one.
How should it work:. To prevent a cache stampede, it's reasonable to distribute page expirations across a range of time instead of use a fixed time for each page. But the calculation shouldn't be a function that returns a time that moves each second. Ideally it should move every $wgSquidMaxage seconds
Possible solution:
Calculate how many "ages" have passed since last page modification timestamp, and make this date increment every "age":
$timeLastMod = wfTimestamp( TS_UNIX, $modifiedTimes['page'] ); $timeSinceLastMod = time() - $timeLastMod; $ages = floor( $timeSinceLastMod / $wgSquidMaxage ); $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $timeLastMod + $wgSquidMaxage * $ages );
Assuming $wgSquidMaxage is 30 days, if the page was last modified on August 1th, this timestamp will be incremented to September 1th, October 1th... the most recent date before today.