Ie161e0f was done in a hurry, and so didn't do things in the best ways.
This introduces a new "CachedBagOStuff" that transparently handles all
the logic that had been copy-pasted all over in Ie161e0f.
The differences between CachedBagOStuff and MultiWriteBagOStuff are:
* CachedBagOStuff supports only one "backend".
* There's a flag for writes to only go to the in-memory cache.
* The in-memory cache is always updated.
* Locks go to the backend cache (with MultiWriteBagOStuff, it would wind
up going to the HashBagOStuff used for the in-memory cache).
Change-Id: Iea494729bd2e8c6c5ab8facf4c241232e31e8215
'CacheHelper' => __DIR__ . '/includes/cache/CacheHelper.php',
'CacheTime' => __DIR__ . '/includes/parser/CacheTime.php',
'CachedAction' => __DIR__ . '/includes/actions/CachedAction.php',
+ 'CachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/CachedBagOStuff.php',
'CachingSiteStore' => __DIR__ . '/includes/site/CachingSiteStore.php',
'CapsCleanup' => __DIR__ . '/maintenance/cleanupCaps.php',
'Category' => __DIR__ . '/includes/Category.php',
const READ_VERIFIED = 2; // promise that caller can tell when keys are stale
/** Bitfield constants for set()/merge() */
const WRITE_SYNC = 1; // synchronously write to all locations for replicated stores
+ const WRITE_CACHE_ONLY = 2; // Only change state of the in-memory cache
public function __construct( array $params = array() ) {
if ( isset( $params['logger'] ) ) {
--- /dev/null
+<?php
+/**
+ * Wrapper around a BagOStuff that caches data in memory
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+use Psr\Log\LoggerInterface;
+
+/**
+ * Wrapper around a BagOStuff that caches data in memory
+ *
+ * The differences between CachedBagOStuff and MultiWriteBagOStuff are:
+ * * CachedBagOStuff supports only one "backend".
+ * * There's a flag for writes to only go to the in-memory cache.
+ * * The in-memory cache is always updated.
+ * * Locks go to the backend cache (with MultiWriteBagOStuff, it would wind
+ * up going to the HashBagOStuff used for the in-memory cache).
+ *
+ * @ingroup Cache
+ */
+class CachedBagOStuff extends HashBagOStuff {
+ /** @var BagOStuff */
+ protected $backend;
+
+ /**
+ * @param BagOStuff $backend Permanent backend to use
+ * @param array $params Parameters for HashBagOStuff
+ */
+ function __construct( BagOStuff $backend, $params = array() ) {
+ $this->backend = $backend;
+ parent::__construct( $params );
+ }
+
+ protected function doGet( $key, $flags = 0 ) {
+ $ret = parent::doGet( $key, $flags );
+ if ( $ret === false ) {
+ $ret = $this->backend->doGet( $key, $flags );
+ if ( $ret !== false ) {
+ $this->set( $key, $ret, 0, self::WRITE_CACHE_ONLY );
+ }
+ }
+ return $ret;
+ }
+
+ public function set( $key, $value, $exptime = 0, $flags = 0 ) {
+ parent::set( $key, $value, $exptime, $flags );
+ if ( !( $flags & self::WRITE_CACHE_ONLY ) ) {
+ $this->backend->set( $key, $value, $exptime, $flags & ~self::WRITE_CACHE_ONLY );
+ }
+ return true;
+ }
+
+ public function delete( $key, $flags = 0 ) {
+ unset( $this->bag[$key] );
+ if ( !( $flags & self::WRITE_CACHE_ONLY ) ) {
+ $this->backend->delete( $key );
+ }
+
+ return true;
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ parent::setLogger( $logger );
+ $this->backend->setLogger( $logger );
+ }
+
+ public function setDebug( $bool ) {
+ parent::setDebug( $bool );
+ $this->backend->setDebug( $bool );
+ }
+
+ public function lock( $key, $timeout = 6, $expiry = 6, $rclass = '' ) {
+ return $this->backend->lock( $key, $timeout, $expiry, $rclass );
+ }
+
+ public function unlock( $key ) {
+ return $this->backend->unlock( $key );
+ }
+
+ public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
+ parent::deleteObjectsExpiringBefore( $date, $progressCallback );
+ return $this->backend->deleteObjectsExpiringBefore( $date, $progressCallback );
+ }
+
+ public function getLastError() {
+ return $this->backend->getLastError();
+ }
+
+ public function clearLastError() {
+ $this->backend->clearLastError();
+ }
+
+ public function modifySimpleRelayEvent( array $event ) {
+ return $this->backend->modifySimpleRelayEvent( $event );
+ }
+
+}
namespace MediaWiki\Session;
-use BagOStuff;
+use CachedBagOStuff;
use Psr\Log\LoggerInterface;
use User;
use WebRequest;
/** @var string Used to detect subarray modifications */
private $dataHash = null;
- /** @var BagOStuff */
- private $tempStore;
- /** @var BagOStuff */
- private $permStore;
+ /** @var CachedBagOStuff */
+ private $store;
/** @var LoggerInterface */
private $logger;
/**
* @param SessionId $id Session ID object
* @param SessionInfo $info Session info to populate from
- * @param BagOStuff $tempStore In-process data store
- * @param BagOStuff $permstore Backend data store for persisted sessions
+ * @param CachedBagOStuff $store Backend data store
* @param LoggerInterface $logger
* @param int $lifetime Session data lifetime in seconds
*/
public function __construct(
- SessionId $id, SessionInfo $info, BagOStuff $tempStore, BagOStuff $permStore,
- LoggerInterface $logger, $lifetime
+ SessionId $id, SessionInfo $info, CachedBagOStuff $store, LoggerInterface $logger, $lifetime
) {
$phpSessionHandling = \RequestContext::getMain()->getConfig()->get( 'PHPSessionHandling' );
$this->usePhpSessionHandling = $phpSessionHandling !== 'disable';
$this->id = $id;
$this->user = $info->getUserInfo() ? $info->getUserInfo()->getUser() : new User;
- $this->tempStore = $tempStore;
- $this->permStore = $permStore;
+ $this->store = $store;
$this->logger = $logger;
$this->lifetime = $lifetime;
$this->provider = $info->getProvider();
$this->forceHTTPS = $info->forceHTTPS();
$this->providerMetadata = $info->getProviderMetadata();
- $key = wfMemcKey( 'MWSession', (string)$this->id );
- $blob = $tempStore->get( $key );
- if ( $blob === false ) {
- $blob = $permStore->get( $key );
- if ( $blob !== false ) {
- $tempStore->set( $key, $blob );
- }
- }
+ $blob = $store->get( wfMemcKey( 'MWSession', (string)$this->id ) );
if ( !is_array( $blob ) ||
!isset( $blob['metadata'] ) || !is_array( $blob['metadata'] ) ||
!isset( $blob['data'] ) || !is_array( $blob['data'] )
$this->autosave();
// Delete the data for the old session ID now
- $this->tempStore->delete( wfMemcKey( 'MWSession', $oldId ) );
- $this->permStore->delete( wfMemcKey( 'MWSession', $oldId ) );
+ $this->store->delete( wfMemcKey( 'MWSession', $oldId ) );
}
}
}
}
- $this->tempStore->set(
+ $this->store->set(
wfMemcKey( 'MWSession', (string)$this->id ),
array(
'data' => $this->data,
'metadata' => $metadata,
),
- $metadata['expires']
+ $metadata['expires'],
+ $this->persist ? 0 : CachedBagOStuff::WRITE_CACHE_ONLY
);
- if ( $this->persist ) {
- $this->permStore->set(
- wfMemcKey( 'MWSession', (string)$this->id ),
- array(
- 'data' => $this->data,
- 'metadata' => $metadata,
- ),
- $metadata['expires']
- );
- }
$this->metaDirty = false;
$this->dataDirty = false;
use Psr\Log\LoggerInterface;
use BagOStuff;
+use CachedBagOStuff;
use Config;
use FauxRequest;
use Language;
/** @var Config */
private $config;
- /** @var BagOStuff|null */
- private $tempStore;
-
- /** @var BagOStuff|null */
- private $permStore;
+ /** @var CachedBagOStuff|null */
+ private $store;
/** @var SessionProvider[] */
private $sessionProviders = null;
$this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'session' ) );
}
- $this->tempStore = new \HashBagOStuff;
if ( isset( $options['store'] ) ) {
if ( !$options['store'] instanceof BagOStuff ) {
throw new \InvalidArgumentException(
'$options[\'store\'] must be an instance of BagOStuff'
);
}
- $this->permStore = $options['store'];
+ $store = $options['store'];
} else {
- $this->permStore = \ObjectCache::getInstance( $this->config->get( 'SessionCacheType' ) );
- $this->permStore->setLogger( $this->logger );
+ $store = \ObjectCache::getInstance( $this->config->get( 'SessionCacheType' ) );
+ $store->setLogger( $this->logger );
}
+ $this->store = $store instanceof CachedBagOStuff ? $store : new CachedBagOStuff( $store );
register_shutdown_function( array( $this, 'shutdown' ) );
}
// Test this here to provide a better log message for the common case
// of "no such ID"
$key = wfMemcKey( 'MWSession', $id );
- $existing = $this->tempStore->get( $key );
- if ( $existing === false ) {
- $existing = $this->permStore->get( $key );
- if ( $existing !== false ) {
- $this->tempStore->set( $key, $existing );
- }
- }
- if ( is_array( $existing ) ) {
+ if ( is_array( $this->store->get( $key ) ) ) {
$info = new SessionInfo( SessionInfo::MIN_PRIORITY, array( 'id' => $id, 'idIsSafe' => true ) );
if ( $this->loadSessionInfoFromStore( $info, $request ) ) {
$session = $this->getSessionFromInfo( $info, $request );
}
$key = wfMemcKey( 'MWSession', $id );
- $existing = $this->tempStore->get( $key );
- if ( $existing === false ) {
- $existing = $this->permStore->get( $key );
- if ( $existing !== false ) {
- $this->tempStore->set( $key, $existing );
- }
- }
- if ( is_array( $existing ) ) {
+ if ( is_array( $this->store->get( $key ) ) ) {
throw new \InvalidArgumentException( 'Session ID already exists' );
}
}
*/
private function loadSessionInfoFromStore( SessionInfo &$info, WebRequest $request ) {
$key = wfMemcKey( 'MWSession', $info->getId() );
- $blob = $this->tempStore->get( $key );
- if ( $blob === false ) {
- $blob = $this->permStore->get( $key );
- if ( $blob !== false ) {
- $this->tempStore->set( $key, $blob );
- }
- }
+ $blob = $this->store->get( $key );
$newParams = array();
// Sanity check: blob must be an array, if it's saved at all
if ( !is_array( $blob ) ) {
$this->logger->warning( "Session $info: Bad data" );
- $this->tempStore->delete( $key );
- $this->permStore->delete( $key );
+ $this->store->delete( $key );
return false;
}
!isset( $blob['metadata'] ) || !is_array( $blob['metadata'] )
) {
$this->logger->warning( "Session $info: Bad data structure" );
- $this->tempStore->delete( $key );
- $this->permStore->delete( $key );
+ $this->store->delete( $key );
return false;
}
!array_key_exists( 'provider', $metadata )
) {
$this->logger->warning( "Session $info: Bad metadata" );
- $this->tempStore->delete( $key );
- $this->permStore->delete( $key );
+ $this->store->delete( $key );
return false;
}
$newParams['provider'] = $provider = $this->getProvider( $metadata['provider'] );
if ( !$provider ) {
$this->logger->warning( "Session $info: Unknown provider, " . $metadata['provider'] );
- $this->tempStore->delete( $key );
- $this->permStore->delete( $key );
+ $this->store->delete( $key );
return false;
}
} elseif ( $metadata['provider'] !== (string)$provider ) {
$backend = new SessionBackend(
$this->allSessionIds[$id],
$info,
- $this->tempStore,
- $this->permStore,
+ $this->store,
$this->logger,
$this->config->get( 'ObjectCacheSessionExpiry' )
);
do {
$id = wfBaseConvert( \MWCryptRand::generateHex( 40 ), 16, 32, 32 );
$key = wfMemcKey( 'MWSession', $id );
- } while ( isset( $this->allSessionIds[$id] ) ||
- is_array( $this->tempStore->get( $key ) ) || is_array( $this->permStore->get( $key ) )
- );
+ } while ( isset( $this->allSessionIds[$id] ) || is_array( $this->store->get( $key ) ) );
return $id;
}
* @param PHPSessionHandler $handler
*/
public function setupPHPSessionHandler( PHPSessionHandler $handler ) {
- $handler->setManager( $this, $this->permStore, $this->logger );
+ $handler->setManager( $this, $this->store, $this->logger );
}
/**
--- /dev/null
+<?php
+
+/**
+ * @group BagOStuff
+ */
+class CachedBagOStuffTest extends PHPUnit_Framework_TestCase {
+
+ public function testGetFromBackend() {
+ $backend = new HashBagOStuff;
+ $cache = new CachedBagOStuff( $backend );
+
+ $backend->set( 'foo', 'bar' );
+ $this->assertEquals( 'bar', $cache->get( 'foo' ) );
+
+ $backend->set( 'foo', 'baz' );
+ $this->assertEquals( 'bar', $cache->get( 'foo' ), 'cached' );
+ }
+
+ public function testSetAndDelete() {
+ $backend = new HashBagOStuff;
+ $cache = new CachedBagOStuff( $backend );
+
+ for ( $i = 0; $i < 10; $i++ ) {
+ $cache->set( "key$i", 1 );
+ $this->assertEquals( 1, $cache->get( "key$i" ) );
+ $this->assertEquals( 1, $backend->get( "key$i" ) );
+ $cache->delete( "key$i" );
+ $this->assertEquals( false, $cache->get( "key$i" ) );
+ $this->assertEquals( false, $backend->get( "key$i" ) );
+ }
+ }
+
+ public function testWriteCacheOnly() {
+ $backend = new HashBagOStuff;
+ $cache = new CachedBagOStuff( $backend );
+
+ $cache->set( 'foo', 'bar', 0, CachedBagOStuff::WRITE_CACHE_ONLY );
+ $this->assertEquals( 'bar', $cache->get( 'foo' ) );
+ $this->assertFalse( $backend->get( 'foo' ) );
+
+ $cache->set( 'foo', 'old' );
+ $this->assertEquals( 'old', $cache->get( 'foo' ) );
+ $this->assertEquals( 'old', $backend->get( 'foo' ) );
+
+ $cache->set( 'foo', 'new', 0, CachedBagOStuff::WRITE_CACHE_ONLY );
+ $this->assertEquals( 'new', $cache->get( 'foo' ) );
+ $this->assertEquals( 'old', $backend->get( 'foo' ) );
+
+ $cache->delete( 'foo', CachedBagOStuff::WRITE_CACHE_ONLY );
+ $this->assertEquals( 'old', $cache->get( 'foo' ) ); // Reloaded from backend
+ }
+}
$provider->setManager( SessionManager::singleton() );
$sessionId = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
- $store = new \HashBagOStuff();
+ $store = new TestBagOStuff();
$user = User::newFromName( 'UTSysop' );
$anon = new User;
'idIsSafe' => true,
) ),
$store,
- $store,
new \Psr\Log\NullLogger(),
10
);
'persisted' => true,
'idIsSafe' => true,
) ),
- new \EmptyBagOStuff(),
- new \EmptyBagOStuff(),
+ new TestBagOStuff(),
new \Psr\Log\NullLogger(),
10
);
$provider->setManager( SessionManager::singleton() );
$sessionId = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
- $store = new \HashBagOStuff();
+ $store = new TestBagOStuff();
$user = User::newFromName( 'UTSysop' );
$anon = new User;
'idIsSafe' => true,
) ),
$store,
- $store,
new \Psr\Log\NullLogger(),
10
);
'userInfo' => UserInfo::newFromUser( $user, true ),
'idIsSafe' => true,
) ),
- new \EmptyBagOStuff(),
- new \EmptyBagOStuff(),
+ new TestBagOStuff(),
new \Psr\Log\NullLogger(),
10
);
ini_set( 'session.use_cookies', 1 );
ini_set( 'session.use_trans_sid', 1 );
- $store = new \HashBagOStuff();
+ $store = new TestBagOStuff();
$logger = new \TestLogger();
$manager = new SessionManager( array(
'store' => $store,
'wgObjectCacheSessionExpiry' => 2,
) );
- $store = new \HashBagOStuff();
+ $store = new TestBagOStuff();
$logger = new \TestLogger( true, function ( $m ) {
return preg_match( '/^SessionBackend a{32} /', $m ) ? null : $m;
} );
$this->assertSame( $expect, $_SESSION );
}
+ // Test expiry
+ session_write_close();
+ ini_set( 'session.gc_divisor', 1 );
+ ini_set( 'session.gc_probability', 1 );
+ sleep( 3 );
+ session_start();
+ $this->assertSame( array(), $_SESSION );
+
// Re-fill the session, then test that session_destroy() works.
$_SESSION['AuthenticationSessionTest'] = $rand;
session_write_close();
) );
$id = new SessionId( $info->getId() );
- $backend = new SessionBackend( $id, $info, $this->store, $this->store, $logger, 10 );
+ $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
$priv = \TestingAccessWrapper::newFromObject( $backend );
$priv->persist = false;
$priv->requests = array( 100 => new \FauxRequest() );
$id = new SessionId( $info->getId() );
$logger = new \Psr\Log\NullLogger();
try {
- new SessionBackend( $id, $info, $this->store, $this->store, $logger, 10 );
+ new SessionBackend( $id, $info, $this->store, $logger, 10 );
$this->fail( 'Expected exception not thrown' );
} catch ( \InvalidArgumentException $ex ) {
$this->assertSame(
) );
$id = new SessionId( $info->getId() );
try {
- new SessionBackend( $id, $info, $this->store, $this->store, $logger, 10 );
+ new SessionBackend( $id, $info, $this->store, $logger, 10 );
$this->fail( 'Expected exception not thrown' );
} catch ( \InvalidArgumentException $ex ) {
$this->assertSame( 'Cannot create session without a provider', $ex->getMessage() );
) );
$id = new SessionId( '!' . $info->getId() );
try {
- new SessionBackend( $id, $info, $this->store, $this->store, $logger, 10 );
+ new SessionBackend( $id, $info, $this->store, $logger, 10 );
$this->fail( 'Expected exception not thrown' );
} catch ( \InvalidArgumentException $ex ) {
$this->assertSame(
'idIsSafe' => true,
) );
$id = new SessionId( $info->getId() );
- $backend = new SessionBackend( $id, $info, $this->store, $this->store, $logger, 10 );
+ $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
$this->assertSame( self::SESSIONID, $backend->getId() );
$this->assertSame( $id, $backend->getSessionId() );
$this->assertSame( $this->provider, $backend->getProvider() );
'idIsSafe' => true,
) );
$id = new SessionId( $info->getId() );
- $backend = new SessionBackend( $id, $info, $this->store, $this->store, $logger, 10 );
+ $backend = new SessionBackend( $id, $info, $this->store, $logger, 10 );
$this->assertSame( self::SESSIONID, $backend->getId() );
$this->assertSame( $id, $backend->getSessionId() );
$this->assertSame( $this->provider, $backend->getProvider() );
$this->assertInternalType( 'array', $metadata );
$this->assertArrayHasKey( '???', $metadata );
$this->assertSame( '!!!', $metadata['???'] );
+ $this->assertFalse( $this->store->getSessionFromBackend( self::SESSIONID ),
+ 'making sure it didn\'t save to backend' );
// Persistent, not dirty
$this->provider = $neverProvider;
$this->assertInternalType( 'array', $metadata );
$this->assertArrayHasKey( '???', $metadata );
$this->assertSame( '!!!', $metadata['???'] );
+ $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
+ 'making sure it did save to backend' );
$this->provider = $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
$this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
$this->assertInternalType( 'array', $metadata );
$this->assertArrayHasKey( '???', $metadata );
$this->assertSame( '!!!', $metadata['???'] );
+ $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
+ 'making sure it did save to backend' );
$this->provider = $this->getMock( 'DummySessionProvider', array( 'persistSession' ) );
$this->provider->expects( $this->atLeastOnce() )->method( 'persistSession' );
$this->assertInternalType( 'array', $metadata );
$this->assertArrayHasKey( '???', $metadata );
$this->assertSame( '!!!', $metadata['???'] );
+ $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
+ 'making sure it did save to backend' );
// Not marked dirty, but dirty data
$this->provider = $neverProvider;
$this->assertInternalType( 'array', $metadata );
$this->assertArrayHasKey( '???', $metadata );
$this->assertSame( '!!!', $metadata['???'] );
+ $this->assertNotSame( false, $this->store->getSessionFromBackend( self::SESSIONID ),
+ 'making sure it did save to backend' );
// Bad hook
$this->provider = null;
$manager = \TestingAccessWrapper::newFromObject( $this->getManager() );
$this->assertSame( $this->config, $manager->config );
$this->assertSame( $this->logger, $manager->logger );
- $this->assertSame( $this->store, $manager->permStore );
+ $this->assertSame( $this->store, $manager->store );
$manager = \TestingAccessWrapper::newFromObject( new SessionManager() );
$this->assertSame( \RequestContext::getMain()->getConfig(), $manager->config );
$manager = \TestingAccessWrapper::newFromObject( new SessionManager( array(
'config' => $this->config,
) ) );
- $this->assertSame( \ObjectCache::$instances['testSessionStore'], $manager->permStore );
+ $this->assertSame( \ObjectCache::$instances['testSessionStore'], $manager->store );
foreach ( array(
'config' => '$options[\'config\'] must be an instance of Config',
public function testGetSessionById() {
$manager = $this->getManager();
-
- // Disable the in-process cache so our $this->store->setSession() takes effect.
- \TestingAccessWrapper::newFromObject( $manager )->tempStore = new \EmptyBagOStuff;
-
try {
$manager->getSessionById( 'bad' );
$this->fail( 'Expected exception not thrown' );
$that = $this;
- \ObjectCache::$instances[__METHOD__] = new \HashBagOStuff();
+ \ObjectCache::$instances[__METHOD__] = new TestBagOStuff();
$this->setMwGlobals( array( 'wgMainCacheType' => __METHOD__ ) );
$this->stashMwGlobals( array( 'wgGroupPermissions' ) );
$manager->setLogger( $logger );
$request = new \FauxRequest();
- // Disable the in-process cache so our $this->store->setSession() takes effect.
- \TestingAccessWrapper::newFromObject( $manager )->tempStore = new \EmptyBagOStuff;
-
// TestingAccessWrapper can't handle methods with reference arguments, sigh.
$rClass = new \ReflectionClass( $manager );
$rMethod = $rClass->getMethod( 'loadSessionInfoFromStore' );
/**
* BagOStuff with utility functions for MediaWiki\\Session\\* testing
*/
-class TestBagOStuff extends \HashBagOStuff {
+class TestBagOStuff extends \CachedBagOStuff {
+
+ public function __construct() {
+ parent::__construct( new \HashBagOStuff );
+ }
/**
* @param string $id Session ID
return $this->get( wfMemcKey( 'MWSession', $id ) );
}
+ /**
+ * @param string $id Session ID
+ * @return mixed
+ */
+ public function getSessionFromBackend( $id ) {
+ return $this->backend->get( wfMemcKey( 'MWSession', $id ) );
+ }
+
/**
* @param string $id Session ID
*/