Esempio n. 1
0
 /**
  * Return an array of metadatas for the given cache id
  *
  * The array must include these keys :
  * - expire : the expire timestamp
  * - tags : a string array of tags
  * - mtime : timestamp of last modification time
  *
  * @param string $id cache id
  * @return array array of metadatas (false if the cache id is not found)
  */
 public function getMetadatas($id)
 {
     list($tags, $mtime, $inf) = $this->_redis->hMGet(self::PREFIX_KEY . $id, array(self::FIELD_TAGS, self::FIELD_MTIME, self::FIELD_INF));
     if (!$mtime) {
         return FALSE;
     }
     $tags = explode(',', $this->_decodeData($tags));
     $expire = $inf === '1' ? FALSE : time() + $this->_redis->ttl(self::PREFIX_KEY . $id);
     return array('expire' => $expire, 'tags' => $tags, 'mtime' => $mtime);
 }
Esempio n. 2
0
 public function testHashes()
 {
     $this->assertEquals(1, $this->credis->hSet('hash', 'field1', 'foo'));
     $this->assertEquals(0, $this->credis->hSet('hash', 'field1', 'foo'));
     $this->assertEquals('foo', $this->credis->hGet('hash', 'field1'));
     $this->assertEquals(NULL, $this->credis->hGet('hash', 'x'));
     $this->assertTrue($this->credis->hMSet('hash', array('field2' => 'Hello', 'field3' => 'World')));
     $this->assertEquals(array('foo', 'Hello', FALSE), $this->credis->hMGet('hash', array('field1', 'field2', 'nilfield')));
     $this->assertEquals(array(), $this->credis->hGetAll('nohash'));
     $this->assertEquals(array('field1' => 'foo', 'field2' => 'Hello', 'field3' => 'World'), $this->credis->hGetAll('hash'));
 }
Esempio n. 3
0
 public function testHashes()
 {
     $this->assertEquals(1, $this->credis->hSet('hash', 'field1', 'foo'));
     $this->assertEquals(0, $this->credis->hSet('hash', 'field1', 'foo'));
     $this->assertEquals('foo', $this->credis->hGet('hash', 'field1'));
     $this->assertEquals(NULL, $this->credis->hGet('hash', 'x'));
     $this->assertTrue($this->credis->hMSet('hash', array('field2' => 'Hello', 'field3' => 'World')));
     $this->assertEquals(array('foo', 'Hello', FALSE), $this->credis->hMGet('hash', array('field1', 'field2', 'nilfield')));
     $this->assertEquals(array(), $this->credis->hGetAll('nohash'));
     $this->assertEquals(array('field1' => 'foo', 'field2' => 'Hello', 'field3' => 'World'), $this->credis->hGetAll('hash'));
     // Test long hash values
     $longString = str_repeat(md5('asd'), 4096);
     // 128k (redis.h REDIS_INLINE_MAX_SIZE = 64k)
     $this->assertEquals(1, $this->credis->hMSet('long_hash', array('count' => 1, 'data' => $longString)), 'Set long hash value');
     $this->assertEquals($longString, $this->credis->hGet('long_hash', 'data'), 'Get long hash value');
 }
Esempio n. 4
0
 /**
  * Fetch session data
  *
  * @param string $sessionId
  * @return string
  */
 public function read($sessionId)
 {
     $this->profilerStart(__METHOD__);
     // Get lock on session. Increment the "lock" field and if the new value is 1, we have the lock.
     $sessionId = self::SESSION_PREFIX . $sessionId;
     $tries = $waiting = $lock = 0;
     $lockPid = $oldLockPid = NULL;
     // Restart waiting for lock when current lock holder changes
     $detectZombies = FALSE;
     $breakAfter = $this->_getBreakAfter();
     if ($this->_logLevel >= \Zend_Log::WARN) {
         $timeStart = microtime(true);
     }
     if ($this->_logLevel >= \Zend_Log::DEBUG) {
         $this->_log(sprintf("Attempting to take lock on ID %s", $sessionId));
     }
     $this->_redis->select($this->_dbNum);
     while ($this->_useLocking) {
         // Increment lock value for this session and retrieve the new value
         $oldLock = $lock;
         $lock = $this->_redis->hIncrBy($sessionId, 'lock', 1);
         // Get the pid of the process that has the lock
         if ($lock != 1 && $tries + 1 >= $breakAfter) {
             $lockPid = $this->_redis->hGet($sessionId, 'pid');
         }
         // If we got the lock, update with our pid and reset lock and expiration
         if ($lock == 1 || $tries >= $breakAfter && $oldLockPid == $lockPid) {
             $this->_hasLock = TRUE;
             break;
         } else {
             if (!$waiting) {
                 $i = 0;
                 do {
                     $waiting = $this->_redis->hIncrBy($sessionId, 'wait', 1);
                 } while (++$i < $this->_maxConcurrency && $waiting < 1);
             } else {
                 // Detect broken sessions (e.g. caused by fatal errors)
                 if ($detectZombies) {
                     $detectZombies = FALSE;
                     if ($lock > $oldLock && $lock + 1 < $oldLock + $waiting) {
                         // Reset session to fresh state
                         if ($this->_logLevel >= \Zend_Log::INFO) {
                             $this->_log(sprintf("Detected zombie waiter after %.5f seconds for ID %s (%d waiting)\n  %s (%s - %s)", microtime(true) - $timeStart, $sessionId, $waiting, Mage::app()->getRequest()->getRequestUri(), Mage::app()->getRequest()->getClientIp(), Mage::app()->getRequest()->getHeader('User-Agent')), \Zend_Log::INFO);
                         }
                         $waiting = $this->_redis->hIncrBy($sessionId, 'wait', -1);
                         continue;
                     }
                 }
                 // Limit concurrent lock waiters to prevent server resource hogging
                 if ($waiting >= $this->_maxConcurrency) {
                     // Overloaded sessions get 503 errors
                     $this->_redis->hIncrBy($sessionId, 'wait', -1);
                     $this->_sessionWritten = TRUE;
                     // Prevent session from getting written
                     $writes = $this->_redis->hGet($sessionId, 'writes');
                     if ($this->_logLevel >= \Zend_Log::WARN) {
                         $this->_log(sprintf("Session concurrency exceeded for ID %s; displaying HTTP 503 (%s waiting, %s total requests)\n  %s (%s - %s)", $sessionId, $waiting, $writes, Mage::app()->getRequest()->getRequestUri(), Mage::app()->getRequest()->getClientIp(), Mage::app()->getRequest()->getHeader('User-Agent')), \Zend_Log::WARN);
                     }
                     $this->_logLevel = -1;
                     // Disable further logging
                     require_once Mage::getBaseDir() . DS . 'errors' . DS . '503.php';
                     exit;
                 }
             }
         }
         $tries++;
         $oldLockPid = $lockPid;
         $sleepTime = self::SLEEP_TIME;
         // Detect dead lock waiters
         if ($tries % self::DETECT_ZOMBIES == 1) {
             $detectZombies = TRUE;
             $sleepTime += 10000;
             // sleep + 0.01 seconds
         }
         // Detect dead lock holder every 10 seconds (only works on same node as lock holder)
         if ($tries % self::DETECT_ZOMBIES == 0) {
             $this->profilerStart(__METHOD__ . '-detect-zombies');
             if ($this->_logLevel >= \Zend_Log::DEBUG) {
                 $this->_log(sprintf("Checking for zombies after %.5f seconds of waiting...", microtime(true) - $timeStart));
             }
             $pid = $this->_redis->hGet($sessionId, 'pid');
             if ($pid && !$this->_pidExists($pid)) {
                 // Allow a live process to get the lock
                 $this->_redis->hSet($sessionId, 'lock', 0);
                 if ($this->_logLevel >= \Zend_Log::INFO) {
                     $this->_log(sprintf("Detected zombie process (%s) for %s (%s waiting)\n  %s (%s - %s)", $pid, $sessionId, $waiting, Mage::app()->getRequest()->getRequestUri(), Mage::app()->getRequest()->getClientIp(), Mage::app()->getRequest()->getHeader('User-Agent')), \Zend_Log::INFO);
                 }
                 $this->profilerStop(__METHOD__ . '-detect-zombies');
                 continue;
             }
             $this->profilerStop(__METHOD__ . '-detect-zombies');
         }
         // Timeout
         if ($tries >= $breakAfter + $this->_failAfter) {
             $this->_hasLock = FALSE;
             if ($this->_logLevel >= \Zend_Log::NOTICE) {
                 $this->_log(sprintf("Giving up on read lock for ID %s after %.5f seconds (%d attempts)", $sessionId, microtime(true) - $timeStart, $tries), \Zend_Log::NOTICE);
             }
             break;
         } else {
             if ($this->_logLevel >= \Zend_Log::DEBUG) {
                 $this->_log(sprintf("Waiting %.2f seconds for lock on ID %s (%d tries, lock pid is %s, %.5f seconds elapsed)", $sleepTime / 1000000, $sessionId, $tries, $lockPid, microtime(true) - $timeStart));
             }
             $this->profilerStart(__METHOD__ . '-wait');
             usleep($sleepTime);
             $this->profilerStop(__METHOD__ . '-wait');
         }
     }
     self::$failedLockAttempts = $tries;
     // Session can be read even if it was not locked by this pid!
     if ($this->_logLevel >= \Zend_Log::DEBUG) {
         $timeStart = microtime(true);
     }
     list($sessionData, $sessionWrites) = $this->_redis->hMGet($sessionId, array('data', 'writes'));
     $this->profilerStop(__METHOD__);
     if ($this->_logLevel >= \Zend_Log::DEBUG) {
         $this->_log(sprintf("Data read for ID %s in %.5f seconds", $sessionId, microtime(true) - $timeStart));
     }
     $this->_sessionWrites = (int) $sessionWrites;
     // This process is no longer waiting for a lock
     if ($tries > 0) {
         $this->_redis->hIncrBy($sessionId, 'wait', -1);
     }
     // This process has the lock, save the pid
     if ($this->_hasLock) {
         $setData = array('pid' => $this->_getPid(), 'lock' => 1);
         // Save request data in session so if a lock is broken we can know which page it was for debugging
         if ($this->_logLevel >= \Zend_Log::INFO) {
             if (empty($_SERVER['REQUEST_METHOD'])) {
                 $setData['req'] = $_SERVER['SCRIPT_NAME'];
             } else {
                 $setData['req'] = "{$_SERVER['REQUEST_METHOD']} {$_SERVER['SERVER_NAME']}{$_SERVER['REQUEST_URI']}";
             }
             if ($lock != 1) {
                 $this->_log(sprintf("Successfully broke lock for ID %s after %.5f seconds (%d attempts). Lock: %d\nLast request of broken lock: %s", $sessionId, microtime(true) - $timeStart, $tries, $lock, $this->_redis->hGet($sessionId, 'req')), \Zend_Log::INFO);
             }
         }
     }
     // Set session data and expiration
     $this->_redis->pipeline();
     if (!empty($setData)) {
         $this->_redis->hMSet($sessionId, $setData);
     }
     $this->_redis->expire($sessionId, min($this->getLifeTime(), $this->_maxLifetime));
     $this->_redis->exec();
     // Reset flag in case of multiple session read/write operations
     $this->_sessionWritten = FALSE;
     return $sessionData ? $this->_decodeData($sessionData) : '';
 }
 /**
  * Fetch session data
  *
  * @param string $sessionId
  * @return string
  * @throws ConcurrentConnectionsExceededException
  */
 public function read($sessionId)
 {
     // Get lock on session. Increment the "lock" field and if the new value is 1, we have the lock.
     $sessionId = self::SESSION_PREFIX . $sessionId;
     $tries = $waiting = $lock = 0;
     $lockPid = $oldLockPid = null;
     // Restart waiting for lock when current lock holder changes
     $detectZombies = false;
     $breakAfter = $this->_getBreakAfter();
     $timeStart = microtime(true);
     $this->_log(sprintf("Attempting to take lock on ID %s", $sessionId));
     $this->_redis->select($this->_dbNum);
     while ($this->_useLocking) {
         // Increment lock value for this session and retrieve the new value
         $oldLock = $lock;
         $lock = $this->_redis->hIncrBy($sessionId, 'lock', 1);
         // Get the pid of the process that has the lock
         if ($lock != 1 && $tries + 1 >= $breakAfter) {
             $lockPid = $this->_redis->hGet($sessionId, 'pid');
         }
         // If we got the lock, update with our pid and reset lock and expiration
         if ($lock == 1 || $tries >= $breakAfter && $oldLockPid == $lockPid) {
             $this->_hasLock = true;
             break;
         } else {
             if (!$waiting) {
                 $i = 0;
                 do {
                     $waiting = $this->_redis->hIncrBy($sessionId, 'wait', 1);
                 } while (++$i < $this->_maxConcurrency && $waiting < 1);
             } else {
                 // Detect broken sessions (e.g. caused by fatal errors)
                 if ($detectZombies) {
                     $detectZombies = false;
                     // Lock shouldn't be less than old lock (another process broke the lock)
                     if ($lock > $oldLock && $lock + 1 < $oldLock + $waiting) {
                         // Reset session to fresh state
                         $this->_log(sprintf("Detected zombie waiter after %.5f seconds for ID %s (%d waiting)", microtime(true) - $timeStart, $sessionId, $waiting), LoggerInterface::INFO);
                         $waiting = $this->_redis->hIncrBy($sessionId, 'wait', -1);
                         continue;
                     }
                 }
                 // Limit concurrent lock waiters to prevent server resource hogging
                 if ($waiting >= $this->_maxConcurrency) {
                     // Overloaded sessions get 503 errors
                     $this->_redis->hIncrBy($sessionId, 'wait', -1);
                     $this->_sessionWritten = true;
                     // Prevent session from getting written
                     $writes = $this->_redis->hGet($sessionId, 'writes');
                     $this->_log(sprintf('Session concurrency exceeded for ID %s; displaying HTTP 503 (%s waiting, %s total ' . 'requests)', $sessionId, $waiting, $writes), LoggerInterface::WARNING);
                     throw new ConcurrentConnectionsExceededException();
                 }
             }
         }
         $tries++;
         $oldLockPid = $lockPid;
         $sleepTime = self::SLEEP_TIME;
         // Detect dead lock waiters
         if ($tries % self::DETECT_ZOMBIES == 1) {
             $detectZombies = true;
             $sleepTime += 10000;
             // sleep + 0.01 seconds
         }
         // Detect dead lock holder every 10 seconds (only works on same node as lock holder)
         if ($tries % self::DETECT_ZOMBIES == 0) {
             $this->_log(sprintf("Checking for zombies after %.5f seconds of waiting...", microtime(true) - $timeStart));
             $pid = $this->_redis->hGet($sessionId, 'pid');
             if ($pid && !$this->_pidExists($pid)) {
                 // Allow a live process to get the lock
                 $this->_redis->hSet($sessionId, 'lock', 0);
                 $this->_log(sprintf("Detected zombie process (%s) for %s (%s waiting)", $pid, $sessionId, $waiting), LoggerInterface::INFO);
                 continue;
             }
         }
         // Timeout
         if ($tries >= $breakAfter + $this->_failAfter) {
             $this->_hasLock = false;
             $this->_log(sprintf('Giving up on read lock for ID %s after %.5f seconds (%d attempts)', $sessionId, microtime(true) - $timeStart, $tries), LoggerInterface::NOTICE);
             break;
         } else {
             $this->_log(sprintf("Waiting %.2f seconds for lock on ID %s (%d tries, lock pid is %s, %.5f seconds elapsed)", $sleepTime / 1000000, $sessionId, $tries, $lockPid, microtime(true) - $timeStart));
             usleep($sleepTime);
         }
     }
     $this->failedLockAttempts = $tries;
     // Session can be read even if it was not locked by this pid!
     $timeStart2 = microtime(true);
     list($sessionData, $sessionWrites) = $this->_redis->hMGet($sessionId, array('data', 'writes'));
     $this->_log(sprintf("Data read for ID %s in %.5f seconds", $sessionId, microtime(true) - $timeStart2));
     $this->_sessionWrites = (int) $sessionWrites;
     // This process is no longer waiting for a lock
     if ($tries > 0) {
         $this->_redis->hIncrBy($sessionId, 'wait', -1);
     }
     // This process has the lock, save the pid
     if ($this->_hasLock) {
         $setData = array('pid' => $this->_getPid(), 'lock' => 1);
         // Save request data in session so if a lock is broken we can know which page it was for debugging
         if (empty($_SERVER['REQUEST_METHOD'])) {
             $setData['req'] = $_SERVER['SCRIPT_NAME'];
         } else {
             $setData['req'] = "{$_SERVER['REQUEST_METHOD']} {$_SERVER['SERVER_NAME']}{$_SERVER['REQUEST_URI']}";
         }
         if ($lock != 1) {
             $this->_log(sprintf("Successfully broke lock for ID %s after %.5f seconds (%d attempts). Lock: %d\nLast request of '\n                            . 'broken lock: %s", $sessionId, microtime(true) - $timeStart, $tries, $lock, $this->_redis->hGet($sessionId, 'req')), LoggerInterface::INFO);
         }
     }
     // Set session data and expiration
     $this->_redis->pipeline();
     if (!empty($setData)) {
         $this->_redis->hMSet($sessionId, $setData);
     }
     $this->_redis->expire($sessionId, 3600 * 6);
     // Expiration will be set to correct value when session is written
     $this->_redis->exec();
     // Reset flag in case of multiple session read/write operations
     $this->_sessionWritten = false;
     return $sessionData ? (string) $this->_decodeData($sessionData) : '';
 }