Skip to content
This repository has been archived by the owner on Jan 31, 2020. It is now read-only.

Use current time() instead of Request Time for the expiration TTL #85

Closed
wants to merge 4 commits into from
Closed

Use current time() instead of Request Time for the expiration TTL #85

wants to merge 4 commits into from

Conversation

MatthiasKuehneEllerhold
Copy link
Contributor

@MatthiasKuehneEllerhold MatthiasKuehneEllerhold commented Aug 17, 2017

The calculation of the expiration timestamp is problematic if you have long-running requests (like user uploads). For long running requests the $_SERVER['REQUEST_TIME'] is far less than the current timestamp. For example uploading 500 MiB can take up to 4 hours in some of our use cases. We're using setExpirationSeconds() as a way to enforce a server-side inactivity timeout. Lets assume we're setting the expiration to 2 hours ($container->setExpirationSeconds(7200);) it will set the expiration timestamp to 2 hours from the START of the request ($_SERVER['REQUEST_TIME']) and not 2 hours from NOW (time()).

Let me show this on an real-life example:

  • We call $container->setExpirationSeconds(7200) on every request.
  • At timestamp 0 we're starting an upload that takes 4 hours (until 14400).
  • We constantly (every 100 seconds) ping our server during the upload to avoid getting the inactivity logout.
    • This resets the expiration timestamp to each pings REQUEST_TIME + 2 hours.
    • Browsing the website during this time is possible without an inactivity logout.
  • Timestamp 14300 (100 seconds before the end of the data upload): The expiration timestamp is set via the ping to 21500 (14300 + 7200).
  • Timestamp 14400: the data upload is done.
    • It finds an intact session (because REQUEST_TIME is used as comparison and 0 < 21500).
    • It sets the new expiration timestamp to 7200 (REQUEST_TIME + 7200) (down from 21500).
  • Timestamp 14401: The next requests sees the expiration timestamp of 7200 (which is 2 hours in the past) and resets all data.

In order to fix this the container needs to use the current time() instead of the REQUEST_TIME for setting the new expiration TTL.

I hope I could make this (rather specific) problem clear.

@Ocramius
Copy link
Member

@MatthiasKuehneEllerhold the problem is clear, but a test (even using sleep(), without having to mock the heck out of the internal functions) is required for patches to be considered.

@MatthiasKuehneEllerhold
Copy link
Contributor Author

@Ocramius I've fixed the current unit-tests and added a new one. The problem with this kind of tests is that during the execution the second can switch between the calls. So we'd need to make them robust enough against that but they shouldn't take too long either.
My test case should fail on the old implementation and work on the new one.

@MatthiasKuehneEllerhold
Copy link
Contributor Author

Travis fails on PHP 7.1 on this unit-test: ZendTest\Session\SaveHandler\MongoDBTest::testReadDestroysExpiredSession. I dont think this is related to my changes, especially since all other jobs (even the other 7.1 ones) work flawlessly.

@MatthiasKuehneEllerhold
Copy link
Contributor Author

@Ocramius Any feedback?

@Ocramius
Copy link
Member

Ocramius commented Aug 22, 2017 via email

$this->assertEquals($currentTimestamp + 3600, $metadata['EXPIRE']);
}

public function testSettingExpirationSecondsUsesCurrentTime()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this test doesn't prove the fix.

To test, I reverted the change you provided (s/time()/$_SERVER['REQUEST_TIME']/), and ran just this test; it passed. I tried resetting the sleep and expiration seconds values as well, but they always passed. I also tried putting the assignment prior to the setExpirationSeconds() method, and that did not work, either.

While I do not doubt that the fix is correct, we need an accurate way to test it that emulates what happens in a normal server-side web request so that we can ensure the fix is correct and will not break with future updates.

@MatthiasKuehneEllerhold
Copy link
Contributor Author

Sorry for missing this! You were correct, the test proved nothing because the REQUEST_TIME never changed. I've fixed it and the REQUEST_TIME will now be temporarily overwritten to simulate a second request coming it.


// Simulate a second request, backup the request time and overwrite it with current time()
$requestTimeBackup = $_SERVER['REQUEST_TIME'];
$requestTimeFloatBackup = $_SERVER['REQUEST_TIME_FLOAT'] ?? null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library still supports PHP 5.6, so we'll need to do an isset() ? ... : null here. I can do that on merge, though.

} catch (\Throwable $e) {
// Restore the original values
$_SERVER['REQUEST_TIME'] = $requestTimeBackup;
$_SERVER['REQUEST_TIME_FLOAT'] = $requestTimeFloatBackup;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to do this; PHPUnit restores superglobals between test executions by default, IIRC.

I'll try removing it locally when I merge.

@weierophinney
Copy link
Member

I've verified the fix locally at this time by restoring the original code and running the new tests! Merging momentarily!

weierophinney added a commit that referenced this pull request Nov 29, 2017
weierophinney added a commit that referenced this pull request Nov 29, 2017
@MatthiasKuehneEllerhold
Copy link
Contributor Author

Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants