Problem/Motivation

We sometimes see this error message in our server logs:

PHP message: PHP Fatal error: Call to a member function getTimestamp() on null in /var/www/current/web/core/modules/page_cache/src/StackMiddleware/PageCache.php on line 257

Looking at the code in question, it is clear that it doesn't take into account that getExpires() may return null, as seen from Symfony's documentation

Proposed resolution

See @msgph's patch in #3 that handles this.

Original issue

https://travis-ci.org/mohankumargupta/drupal8travisinstall/builds/81727062

error:

PHP Fatal error: Call to a member function getTimestamp() on a non-object in /home/travis/build/mohankumargupta/drupal8travisinstall/web/core/modules/page_cache/src/StackMiddleware/PageCache.php on line 226

Help!

Comments

mohangupta created an issue. See original summary.

badrange’s picture

Version: 8.0.0-beta15 » 8.0.x-dev

We also see this error, now with Drupal Core 8.0.5:

FastCGI sent in stderr: "PHP message: PHP Fatal error: Call to a member function getTimestamp() on null in /var/www/current/web/core/modules/page_cache/src/StackMiddleware/PageCache.php on line 257" while reading response header from upstream, client: 127.0.0.1

msgph’s picture

StatusFileSize
new833 bytes

Possible fix for this

badrange’s picture

Title: getTimestamp error » Fatal error: Call to a member function getTimestamp()
Issue summary: View changes
dagmar’s picture

Status: Active » Needs review

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jcnventura’s picture

Component: other » cache system
Status: Needs review » Needs work
Issue tags: +cache, +Needs tests

@msgph: looks good, but it would be good to add 1 line of comment saying that getExpires returns Null when the expires header is not set.

Also, it needs tests...

msgph’s picture

StatusFileSize
new991 bytes
jcnventura’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 8: 2573807_8.patch, failed testing.

prateekjain’s picture

Assigned: Unassigned » prateekjain
prateekjain’s picture

Assigned: prateekjain » Unassigned
msgph’s picture

Status: Needs work » Needs review
jcnventura’s picture

Status: Needs review » Needs work

Needs tests.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

killua99’s picture

Yes, this is clearly broken my server too.

The patch need reroll for 8.2.x

heikki’s picture

Status: Needs work » Needs review
StatusFileSize
new822 bytes

I ran into the same error.
I was using "domain" module and got the error only on secondary domain, so this might be caused by that, but I don't have time to look into it.
Also the error only appeared for anonymous users.

Anyway, here is the 8.2.x reroll for the patch.

20th’s picture

Issue tags: -Needs tests
StatusFileSize
new4.65 KB
new3.64 KB
new1.22 KB

This still needs tests, I guess.

Two ternary operators in a row make code a bit hard to understand, so I have rearranged the condition a little bit. But now, after pocking in code and testing, I'm wondering if there is a logic error in this code?

The condition in

  if ($expire === Cache::PERMANENT || $expire > $request_time) {

will almost always be TRUE. The only case when it becomes FALSE and cache of the page is not set is when the response is a "client error" and the value of the cache_ttl_4xx setting is set to 0.

Intuitively, I would think that if the Expires header of a page is set to a past date, it means that the page is already expired and should not be cached. But this current behavior may have something to do with the internals of the cache system that I do not fully understand.

For context, in its original form this check was added in #2257709: Remove the interdependence between the internal page cache and management of the Cache-Control header for external caches.

The last submitted patch, 18: 2573807-18.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 18: 2573807-18-onlytests.patch, failed testing.

20th’s picture

Status: Needs work » Needs review
StatusFileSize
new4.6 KB
new4.64 KB
new3.59 KB
new3.63 KB

The last submitted patch, 21: 2573807-19-8.2.x.patch, failed testing.

The last submitted patch, 21: 2573807-19-test-only-8.2.x.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 21: 2573807-19-test-only-8.3.x.patch, failed testing.

20th’s picture

Status: Needs work » Needs review
StatusFileSize
new4.75 KB
new4.56 KB
new3.73 KB
new3.55 KB

The last submitted patch, 25: 2573807-25-test-only-8.2.x.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 25: 2573807-25-test-only-8.3.x.patch, failed testing.

The last submitted patch, 25: 2573807-25-8.3.x.patch, failed testing.

20th’s picture

Status: Needs work » Needs review

Test-only patches failed as expected.

Code in 8.2.x and 8.3.x branches has diverged and requires two different patches (which I have not noticed at first).

killua99’s picture

The path that 20th provide is more elegant.

Using it. No errors at the moment.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mglaman’s picture

Ran into this when returning a RedirectResponse that did not have the URL in the metadata and had page expiration set to a day, with `page_cache` enabled.

dagmar’s picture

Status: Needs review » Needs work
Issue tags: -getTimestamp, -cache +Need tests

Nice to see the test. However I think we should have a least a kernel tests that trigger this error in a real request.

wim leers’s picture

Component: cache system » page_cache.module
mfb’s picture

Version: 8.3.x-dev » 8.4.x-dev

This bug is still relevant on Drupal 8.4.x

In SecureLogin module I have to work around by calling $response->setExpires(); on a cacheable redirect.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

sherakama’s picture

StatusFileSize
new4.56 KB

Re-roll of #25 for Drupal 8.3.6

mglaman’s picture

Status: Needs work » Needs review
Issue tags: -Need tests

Running tests for #37, there are tests and removing that tag.

EDIT: Also, we're experiencing this in 8.3 as well, still.

aries’s picture

A test scenario that I'd add to the unit test:

new CacheableJsonResponse($stations, 200, ['Cache-Control' => 'max-age=300, public']);

this throws the an error when the $header is populated with Expires.

sherakama’s picture

StatusFileSize
new4.54 KB

Re-roll for 8.3.7.

gmaxwelled’s picture

nicrodgers’s picture

Patch in #40 appllies to 8.4.0 and fixes the problem for me, thanks!

freelock’s picture

Status: Needs review » Reviewed & tested by the community

This issue seems to happen in 8.4.0 with Redirect module, on paths that have been redirected. (It worked fine in 8.3.x), as mentioned in #2915430. Patch from 40 applies with an offset, and fixes the issue.

sam152’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new2.32 KB
new3.33 KB

I also ran into this upgrading to 8.4 with the redirect module.

I agree with #33, the current tests reach into the internal implementation of Drupal\page_cache\StackMiddleware\PageCache which we shouldn't really do and it also doesn't prove what kind of real response can trigger this code path.

I've done some analysis and found that the offending code is in \Drupal\Core\EventSubscriber\FinishResponseSubscriber:

    $is_cacheable = ($this->requestPolicy->check($request) === RequestPolicyInterface::ALLOW) && ($this->responsePolicy->check($response, $request) !== ResponsePolicyInterface::DENY);

    // Add headers necessary to specify whether the response should be cached by
    // proxies and/or the browser.
    if ($is_cacheable && $this->config->get('cache.page.max_age') > 0) {
      if (!$this->isCacheControlCustomized($response)) {
        // Only add the default Cache-Control header if the controller did not
        // specify one on the response.
        $this->setResponseCacheable($response, $request);
      }
    }
    else {
      // If either the policy forbids caching or the sites configuration does
      // not allow to add a max-age directive, then enforce a Cache-Control
      // header declaring the response as not cacheable.
      $this->setResponseNotCacheable($response, $request);
    }

The two methods responsible for setting the "Expire" header are setResponseCacheable and setResponseNotCacheable. So the trick to generating a problematic response is:

  • Make it cacheable.
  • Make it isCacheControlCustomized.

Under those circumstances, both method calls are skipped and the response is sent with no Expire header.

sam152’s picture

The last submitted patch, 44: 2573807-44_TEST-ONLY.patch, failed testing. View results

kingdutch’s picture

Status: Needs review » Reviewed & tested by the community

The patch looks good and changes only the minimal amount of code to handle the problem. It only affects the behaviour in the error case and leaves other scenarios untouched.

It introduces no new coding standards issues. One could argue that getCacheableWithCustomCacheControl needs a return type but there are other one line methods in the same file that don't have this so I'm using the precedent there to determine it's unneeded.

With the above and the fact that the patch successfully fixes the issue on one of our production sites I'm marking this as RTBC.

wim leers’s picture

I only have two super silly nits, which aren't even worth un-RTBC'ing this for.

  1. +++ b/core/modules/system/tests/modules/system_test/src/Controller/SystemTestController.php
    @@ -375,4 +375,11 @@ public function getTestHeader(Request $request) {
    +  public function getCacheableWithCustomCacheControl() {
    

    Nit: s/CacheableWith/CacheableResponseWith/

  2. +++ b/core/modules/system/tests/modules/system_test/system_test.routing.yml
    @@ -196,3 +196,11 @@ system_test.echo_utf8:
    +    _title: 'Cacheable response custom cache control'
    

    Nit: s/response custom/response with custom/

sam152’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new1.57 KB
new2.34 KB
new3.35 KB

Happy to fix the feedback! Thanks for the review.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

👌

The last submitted patch, 49: 2573807-49_TEST-ONLY.patch, failed testing. View results

alexpott’s picture

Creditting @Wim Leers and @jcnventura because their reviews affected the patch directly. Thanks to everyone else who reported that the patch successfully fixed issues in production sites.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 505f3bf8d4 to 8.5.x and 8d5dd3334e to 8.4.x. Thanks!

As a bug fix with no BC implications backported to 8.4.x

  • alexpott committed 505f3bf on 8.5.x
    Issue #2573807 by 20th, Sam152, msgph, sherakama, heikki, jcnventura,...

  • alexpott committed 8d5dd33 on 8.4.x
    Issue #2573807 by 20th, Sam152, msgph, sherakama, heikki, jcnventura,...
alan d.’s picture

Issue tags: +8.4.0 update

We just discovered this happening on our site too after updated from 8.3.x to 8.4.0 for logged out users only.

Appears to be a new issue related to the 8.4.0 release so tagging as such.

The commit 8d5dd33 from comment #55 applied cleanly to the tagged release 8.4.0 and fixed the issue :)

tinny’s picture

This happened on our site after upgrading to 8.4.0 and using Paragraphs module where anonymous users didn't have permission to view the Paragraph.

After enabling the 'view' permission and clearing cache it worked.

afireintheattic’s picture

After upgrading to 8.4.0 we started noticing that redirects were failing for anonymous users. Applied the patch from #49 and everything is back to normal!

nvaken’s picture

Same here, fatal errors on redirect pages. Using 8d5dd33 fixed our troubles.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

larowlan’s picture

Version: 8.5.x-dev » 8.4.x-dev

Updating version

coredumperror’s picture

#49 also fixed this problem for me!