Problem

  1. In order to move forward in review-able baby steps, #2228341: Objectify session management functions + remove session.inc converted the former static $started variable from session.inc as-is into a SessionManager class property.

  2. This property is not static in Symfony, and it should not have to be.

  3. Tests + various DrupalKernel rebuilds will likely disagree though. :P

Proposed solution

  1. Remove the static variable entirely.

    #201122: Drupal should support disabling anonymous sessions originally introduced it in a custom wrapper around session_start().

    #477944: Fix and streamline page cache and session handling introduced lazy-sessions and changed the condition to additionally check for session_id() !== '', but forgot to remove the static variable.

Comments

sun’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new998 bytes

Let's see what happens.

Status: Needs review » Needs work

The last submitted patch, 1: session.started.1.patch, failed testing.

sun’s picture

Status: Needs work » Needs review
Related issues: +#1831184: DatabaseExceptionWrapper completely hides *unexpected* errors
StatusFileSize
new7.04 KB
new6.35 KB

While testbot only shows 1 failing test case, that failure/error effectively means all web tests - but only when executed through the Simpletest UI.

  1. The non-interactive installer throws PHP notices: "Ignoring call to session_start(), session is started already."
  2. The session of the UI test runner leaks into all web tests.
  3. We currently "disable" session saving in TestBase::prepareEnvironment().
  4. The session_manager service is persisted. However, the service persistence facility only applies to DrupalKernel::updateModules() — it does not apply to complete replacements of DrupalKernel.
  5. After the SessionManager has been re-instantiated from scratch for the installer kernel, install_bootstrap_full() tries to initialize() the session — but it is initialized already.

Attached patch attempts to

  1. Properly write + close the current session in TestBase::prepareEnvironment().
  2. Restore it in TestBase::restoreEnvironment().

That apparently works... until the restore, which blows up with this exception:

Uncaught exception thrown in session handler.

Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[HY000]: General error: failed to instantiate user-supplied statement class: SELECT 1 AS expression
FROM 
{sessions} sessions
WHERE ( (sid = :db_condition_placeholder_0) AND (ssid = :db_condition_placeholder_1) ); Array
(
[:db_condition_placeholder_0] => jGHUKbKnm2LIgdt5tp4zmLxcFPLLpz93kNwd1wm53XQ
[:db_condition_placeholder_1] => 
)
in Drupal\Core\Database\Connection->query() (line 569 of \core\lib\Drupal\Core\Database\Connection.php).
Drupal\Core\Database\Connection->query('...', Array, Array)
Drupal\Core\Database\Query\Select->execute()
Drupal\Core\Database\Query\Merge->execute()
Drupal\Core\Session\SessionHandler->write('lFpSVzn8fEmt1B995GB3NpJAWcHR9918iI45OckM2EA', 'batches|a:1:{i:854;b:1;}')
session_write_close()
Drupal\Core\Session\SessionManager->save()
Drupal\Core\Authentication\Provider\Cookie->cleanup(Object)
Drupal\Core\Authentication\AuthenticationManager->cleanup(Object)
Drupal\Core\EventSubscriber\AuthenticationSubscriber->onRespond(Object, 'kernel.response', Object)
call_user_func(Array, Object, 'kernel.response', Object)
Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'kernel.response', Object)
Symfony\Component\EventDispatcher\EventDispatcher->dispatch('kernel.response', Object)
Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.response', Object)
Symfony\Component\HttpKernel\HttpKernel->filterResponse(Object, Object, 1)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1)
Drupal\Core\HttpKernel->handle(Object, 1, 1)
Drupal\Core\DrupalKernel->handle(Object)
drupal_handle_request()
#
PDOException: SQLSTATE[HY000]: General error: failed to instantiate user-supplied statement class in Drupal\Core\Session\SessionHandler->write() (line 201 of \core\lib\Drupal\Core\Session\SessionHandler.php).Drupal\Core\Session\SessionHandler->write('lFpSVzn8fEmt1B995GB3NpJAWcHR9918iI45OckM2EA', 'batches|a:1:{i:854;b:1;}')
session_write_close()
Drupal\Core\Session\SessionManager->save()
Drupal\Core\Authentication\Provider\Cookie->cleanup(Object)
...
...
#
PDOException: SQLSTATE[HY000]: General error: user-supplied statement does not accept constructor arguments in Drupal\Core\Session\SessionHandler->write() (line 201 of \core\lib\Drupal\Core\Session\SessionHandler.php).Drupal\Core\Session\SessionHandler->write('lFpSVzn8fEmt1B995GB3NpJAWcHR9918iI45OckM2EA', 'batches|a:1:{i:854;b:1;}')
session_write_close()
Drupal\Core\Session\SessionManager->save()
Drupal\Core\Authentication\Provider\Cookie->cleanup(Object)
...
...
#
{"status":true,"percentage":"100","message":"","label":"Processed test 1 of 1 - \u003Cem class=\u0022placeholder\u0022\u003EWebTestBase environment\u003C\/em\u003E.\u003Cdiv class=\u0022simpletest-fail\u0022\u003EOverall results: 2 passes, 0 fails, 2 exceptions\u003C\/div\u003E\u003Cdiv class=\u0022item-list\u0022\u003E\u003Cul\u003E\u003Cli\u003E\u003Cdiv class=\u0022simpletest-fail\u0022\u003EWebTestBase environment: 2 passes, 0 fails, 2 exceptions\u003C\/div\u003E\u003C\/li\u003E\u003C\/ul\u003E\u003C\/div\u003E"}

As you can see in this patch, I already tried various things to figure out the cause for that exception, but to no avail.

Status: Needs review » Needs work

The last submitted patch, 3: session.started.3.patch, failed testing.

znerol’s picture

Status: Needs work » Needs review
StatusFileSize
new1.06 KB

Do we need the static property at all? Let's try to remove it completely.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Awesome idea! :-)

Let's do this. We can look into the problem discovered in #3 in a separate issue (session of UI test runner leaking into all web tests).

sun’s picture

@znerol additionally dug out the original issue that introduced this static variable — however, it does not contain any discussion nor an explanation for why the static variable was added.

Even before PHP 5.4, session_status() === PHP_SESSION_ACTIVE is identical to this:

session_id() !== ''

See also http://php.net/manual/en/function.session-status.php#113468

Therefore, it is not clear why that static variable ever existed to begin with. It looks like it was unnecessary all of the time.

znerol’s picture

I think it the history is like follows:

  1. #201122-59: Drupal should support disabling anonymous sessions
    /**
    + * Propagate $_SESSION and set session cookie if not already set. This function
    + * should be called before writing to $_SESSION, usually via
    + * drupal_set_session().
    + *
    + * @param $start
    + *   If FALSE, the session is not actually started. This is only used by
    + *   drupal_session_is_started().
    + * @return
    + *   TRUE if session has already been started, or FALSE if it has not.
    + */
    +function drupal_session_start($start = TRUE) {
    +  static $started = FALSE;
    +  if ($start && !$started) {
    +    $started = TRUE;
    +    session_start();
    +  }
    +  return $started;
    +}
    +
    +/**
    + * Return whether a session has been started and the $_SESSION variable is
    + * available.
    + */
    +function drupal_session_is_started() {
    +  return drupal_session_start(FALSE);
    +}
    

    This might indicate that the authors were not aware that it was possible to determine the session status using session_id() === ''; (see session_status).

  2. #477944-12: Fix and streamline page cache and session handling
    +function drupal_session_started($set = NULL) {
    +  static $session_started = FALSE;
    +  if (isset($set)) {
    +    $session_started = $set;
    +  }
    +  return $session_started && session_id();
     }
    

    This patch introduced the session_id() check but failed to remove the static variable.

sun’s picture

Great! — That makes some more sense now.

Definitely fine to simply remove it then. :-)

sun’s picture

Title: Make SessionManager::$started not static » Remove unnecessary SessionManager::$started static variable
Category: Task » Bug report
Issue summary: View changes

Adjusting issue title + summary accordingly.

After my debugging investigations in #3, I actually consider this a bug now, since session_write_close() ends a session, but due to the static variable, and because that variable is never reset to FALSE, Drupal still believes that there is an active session.

There is no code in core/HEAD that checks whether a session is active after writing+closing it (typically at the end of a request), but SessionManager::isStarted() definitely returns a wrong status in that case right now.

sun’s picture

Title: Remove unnecessary SessionManager::$started static variable » SessionManager::isStarted() returns TRUE even after session_write_close() has been called

Sorry, one more issue title adjustment — I think this new one properly describes the hidden bug.

ParisLiakos’s picture

+1000 to commit this

This static variable is the reason i wasted many hours of my life in original symfony/session patch, and #10 nicely explains it, thanks!

arg

sun’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 3a7d54d and pushed to 8.x. Thanks!

  • Commit 3a7d54d on 8.x by alexpott:
    Issue #2239181 by sun, znerol: SessionManager::isStarted() returns TRUE...

Status: Fixed » Closed (fixed)

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