diff --git a/lib/Doctrine/MongoDB/Collection.php b/lib/Doctrine/MongoDB/Collection.php index 6d39e8b5..0625cabe 100644 --- a/lib/Doctrine/MongoDB/Collection.php +++ b/lib/Doctrine/MongoDB/Collection.php @@ -66,6 +66,13 @@ class Collection */ protected $cmd; + /** + * Number of times to retry queries. + * + * @var mixed + */ + protected $numRetries; + /** * Create a new MongoCollection instance that wraps a PHP MongoCollection instance * for a given ClassMetadata instance. @@ -74,13 +81,15 @@ class Collection * @param Database $database The Database instance. * @param EventManager $evm The EventManager instance. * @param string $cmd Mongo cmd character. + * @param mixed $numRetries Number of times to retry queries. */ - public function __construct(\MongoCollection $mongoCollection, Database $database, EventManager $evm, $cmd) + public function __construct(\MongoCollection $mongoCollection, Database $database, EventManager $evm, $cmd, $numRetries = false) { $this->mongoCollection = $mongoCollection; $this->database = $database; $this->eventManager = $evm; $this->cmd = $cmd; + $this->numRetries = $numRetries; } /** @proxy */ @@ -194,7 +203,7 @@ protected function doFind(array $query, array $fields) protected function wrapCursor(\MongoCursor $cursor, $query, $fields) { - return new Cursor($cursor); + return new Cursor($cursor, $this->numRetries); } /** @override */ @@ -215,7 +224,10 @@ public function findOne(array $query = array(), array $fields = array()) protected function doFindOne(array $query, array $fields) { - return $this->mongoCollection->findOne($query, $fields); + $mongoCollection = $this->mongoCollection; + return $this->retry(function() use ($mongoCollection, $query, $fields) { + return $mongoCollection->findOne($query, $fields); + }); } public function findAndRemove(array $query, array $options = array()) @@ -242,12 +254,20 @@ protected function doFindAndRemove(array $query, array $options = array()) $command = array_merge($command, $options); $document = null; - $result = $this->database->command($command); + + $database = $this->database; + $result = $this->retry(function() use ($database, $command) { + return $database->command($command); + }); + if (isset($result['value'])) { $document = $result['value']; if ($this->mongoCollection instanceof \MongoGridFS) { // Remove the file data from the chunks collection - $this->mongoCollection->chunks->remove(array('files_id' => $document['_id']), $options); + $mongoCollection = $this->mongoCollection; + $this->retry(function() use ($mongoCollection, $document, $options) { + return $mongoCollection->chunks->remove(array('files_id' => $document['_id']), $options); + }); } } return $document; @@ -275,7 +295,11 @@ protected function doFindAndUpdate(array $query, array $newObj, array $options) $command['query'] = $query; $command['update'] = $newObj; $command = array_merge($command, $options); - $result = $this->database->command($command); + + $database = $this->database; + $result = $this->retry(function() use ($database, $command) { + return $database->command($command); + }); return isset($result['value']) ? $result['value'] : null; } @@ -301,7 +325,11 @@ protected function doNear(array $near, array $query, array $options) $command['near'] = $near; $command['query'] = $query; $command = array_merge($command, $options); - $result = $this->database->command($command); + + $database = $this->database; + $result = $this->retry(function() use ($database, $command) { + return $database->command($command); + }); return new ArrayIterator(isset($result['results']) ? $result['results'] : array()); } @@ -327,7 +355,11 @@ protected function doDistinct($field, array $query, array $options) $command['key'] = $field; $command['query'] = $query; $command = array_merge($command, $options); - $result = $this->database->command($command); + + $database = $this->database; + $result = $this->retry(function() use ($database, $command) { + return $database->command($command); + }); return new ArrayIterator(isset($result['values']) ? $result['values'] : array()); } @@ -362,7 +394,10 @@ protected function doMapReduce($map, $reduce, array $out, array $query, array $o $command['out'] = $out; $command = array_merge($command, $options); - $result = $this->database->command($command); + $database = $this->database; + $result = $this->retry(function() use ($database, $command) { + return $database->command($command); + }); if (!$result['ok']) { throw new \RuntimeException($result['errmsg']); @@ -372,13 +407,18 @@ protected function doMapReduce($map, $reduce, array $out, array $query, array $o return new ArrayIterator($result['results']); } - return $this->database->selectCollection($result['result'])->find(); + return $this->retry(function() use ($database, $command, $result) { + return $database->selectCollection($result['result'])->find(); + }); } /** @proxy */ public function count(array $query = array(), $limit = 0, $skip = 0) { - return $this->mongoCollection->count($query, $limit, $skip); + $mongoCollection = $this->mongoCollection; + return $this->retry(function() use ($mongoCollection, $query, $limit, $skip) { + return $mongoCollection->count($query, $limit, $skip); + }); } /** @proxy */ @@ -465,7 +505,10 @@ public function getDBRef(array $reference) protected function doGetDBRef(array $reference) { - return $this->mongoCollection->getDBRef($reference); + $mongoCollection = $this->mongoCollection; + return $this->retry(function() use ($mongoCollection, $reference) { + return $mongoCollection->getDBRef($reference); + }); } /** @proxy */ @@ -486,7 +529,10 @@ public function group($keys, array $initial, $reduce, array $options = array()) protected function doGroup($keys, array $initial, $reduce, array $options) { - $result = $this->mongoCollection->group($keys, $initial, $reduce, $options); + $mongoCollection = $this->mongoCollection; + $result = $this->retry(function() use ($mongoCollection, $keys, $initial, $reduce, $options) { + return $mongoCollection->group($keys, $initial, $reduce, $options); + }); return new ArrayIterator($result); } @@ -508,11 +554,20 @@ public function insert(array &$a, array $options = array()) protected function doInsert(array &$a, array $options) { $document = $a; - $result = $this->mongoCollection->insert($document, $options); - if ($result && isset($document['_id'])) { - $a['_id'] = $document['_id']; + + $mongoCollection = $this->mongoCollection; + $result = $this->retry(function() use ($mongoCollection, $document, $options) { + $return = $mongoCollection->insert($document, $options); + return array( + 'return' => $return, + 'document' => $document + ); + }); + + if ($result && isset($result['document']['_id'])) { + $a['_id'] = $result['document']['_id']; } - return $result; + return $result['return']; } /** @proxy */ @@ -554,13 +609,19 @@ public function save(array &$a, array $options = array()) protected function doSave(array &$a, array $options) { - return $this->mongoCollection->save($a, $options); + $mongoCollection = $this->mongoCollection; + return $this->retry(function() use ($mongoCollection, &$a, $options) { + return $mongoCollection->save($a, $options); + }); } /** @proxy */ public function validate($scanData = false) { - return $this->mongoCollection->validate($scanData); + $mongoCollection = $this->mongoCollection; + return $this->retry(function() use ($mongoCollection, $scanData) { + return $mongoCollection->validate($scanData); + }); } /** @proxy */ @@ -568,4 +629,23 @@ public function __toString() { return $this->mongoCollection->__toString(); } -} + + protected function retry(\Closure $retry) + { + if ($this->numRetries !== null && $this->numRetries !== false) { + for ($i = 1; $i <= $this->numRetries; $i++) { + try { + return $retry(); + break; + } catch (\MongoException $e) { + if ($i === $this->numRetries) { + throw $e; + } + sleep(1); + } + } + } else { + return $retry(); + } + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Configuration.php b/lib/Doctrine/MongoDB/Configuration.php index d22b8fd2..be8ac9e8 100644 --- a/lib/Doctrine/MongoDB/Configuration.php +++ b/lib/Doctrine/MongoDB/Configuration.php @@ -34,7 +34,11 @@ class Configuration * * @var array $attributes */ - protected $attributes = array('mongoCmd' => '$'); + protected $attributes = array( + 'mongoCmd' => '$', + 'retryConnect' => 3, + 'retryQuery' => 3 + ); /** * Set the logger callable. @@ -74,4 +78,44 @@ public function setMongoCmd($cmd) { $this->attributes['mongoCmd'] = $cmd; } + + /** + * Get number of times to retry connect when errors occur. + * + * @return mixed True/False or number of times to retry. + */ + public function getRetryConnect() + { + return $this->attributes['retryConnect']; + } + + /** + * Set number of times to retry connect when errors occur. + * + * @param string $retryConnect + */ + public function setRetryConnect($retryConnect) + { + $this->attributes['retryConnect'] = $retryConnect; + } + + /** + * Get number of times to retry queries when + * + * @return mixed True/False or number of times to retry queries. + */ + public function getRetryQuery() + { + return $this->attributes['retryQuery']; + } + + /** + * Set true/false whether or not to retry connect upon failure or number of times to retry. + * + * @param mixed $retryQuery True/false or number of times to retry queries. + */ + public function setRetryQuery($retryQuery) + { + $this->attributes['retryQuery'] = $retryQuery; + } } \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Connection.php b/lib/Doctrine/MongoDB/Connection.php index 916b1dd2..ba875a97 100644 --- a/lib/Doctrine/MongoDB/Connection.php +++ b/lib/Doctrine/MongoDB/Connection.php @@ -91,10 +91,21 @@ public function initialize() $this->eventManager->dispatchEvent(Events::preConnect, new EventArgs($this)); } - if ($this->server) { - $this->mongo = new \Mongo($this->server, $this->options); + $numRetries = $this->config->getRetryConnect(); + if ($numRetries !== null && $numRetries !== false) { + for ($i = 1; $i <= $numRetries; $i++) { + try { + $this->initializeMongo(); + break; + } catch (\MongoConnectionException $e) { + if ($i === $numRetries) { + throw $e; + } + sleep(1); + } + } } else { - $this->mongo = new \Mongo(); + $this->initializeMongo(); } if ($this->eventManager->hasListeners(Events::postConnect)) { @@ -261,16 +272,29 @@ public function selectDatabase($name) */ protected function wrapDatabase(\MongoDB $database) { + $numRetries = $this->config->getRetryQuery(); if (null !== $this->config->getLoggerCallable()) { return new LoggableDatabase( - $database, $this->eventManager, $this->cmd, $this->config->getLoggerCallable() + $database, $this->eventManager, $this->cmd, $numRetries, $this->config->getLoggerCallable() ); } return new Database( - $database, $this->eventManager, $this->cmd + $database, $this->eventManager, $this->cmd, $numRetries ); } + /** + * Initialize new Mongo instance. + */ + protected function initializeMongo() + { + if ($this->server) { + $this->mongo = new \Mongo($this->server, $this->options); + } else { + $this->mongo = new \Mongo(); + } + } + /** @proxy */ public function __toString() { diff --git a/lib/Doctrine/MongoDB/Cursor.php b/lib/Doctrine/MongoDB/Cursor.php index 203b855b..b07ee16a 100644 --- a/lib/Doctrine/MongoDB/Cursor.php +++ b/lib/Doctrine/MongoDB/Cursor.php @@ -32,14 +32,18 @@ class Cursor implements Iterator /** The PHP MongoCursor being wrapped */ protected $mongoCursor; + /** Number of times to try operations on the cursor */ + protected $numRetries; + /** * Create a new MongoCursor which wraps around a given PHP MongoCursor. * * @param MongoCursor $mongoCursor The cursor being wrapped. */ - public function __construct(\MongoCursor $mongoCursor) + public function __construct(\MongoCursor $mongoCursor, $numRetries = false) { $this->mongoCursor = $mongoCursor; + $this->numRetries = $numRetries; } /** @@ -55,7 +59,10 @@ public function getMongoCursor() /** @proxy */ public function current() { - $current = $this->mongoCursor->current(); + $mongoCursor = $this->mongoCursor; + $current = $this->retry(function() use ($mongoCursor) { + return $mongoCursor->current(); + }); if ($current instanceof \MongoGridFSFile) { $document = $current->file; $document['file'] = new GridFSFile($current); @@ -79,7 +86,10 @@ public function dead() /** @proxy */ public function explain() { - return $this->mongoCursor->explain(); + $mongoCursor = $this->mongoCursor; + return $this->retry(function() use ($mongoCursor) { + return $mongoCursor->explain(); + }); } /** @proxy */ @@ -92,7 +102,10 @@ public function fields(array $f) /** @proxy */ public function getNext() { - $next = $this->mongoCursor->getNext(); + $mongoCursor = $this->mongoCursor; + $next = $this->retry(function() use ($mongoCursor) { + return $mongoCursor->getNext(); + }); if ($next instanceof \MongoGridFSFile) { $document = $next->file; $document['file'] = new GridFSFile($next); @@ -104,7 +117,10 @@ public function getNext() /** @proxy */ public function hasNext() { - return $this->mongoCursor->hasNext(); + $mongoCursor = $this->mongoCursor; + return $this->retry(function() use ($mongoCursor) { + return $mongoCursor->hasNext(); + }); } /** @proxy */ @@ -130,13 +146,19 @@ public function info() /** @proxy */ public function rewind() { - return $this->mongoCursor->rewind(); + $mongoCursor = $this->mongoCursor; + return $this->retry(function() use ($mongoCursor) { + return $mongoCursor->rewind(); + }); } /** @proxy */ public function next() { - return $this->mongoCursor->next(); + $mongoCursor = $this->mongoCursor; + return $this->retry(function() use ($mongoCursor) { + return $mongoCursor->next(); + }); } /** @proxy */ @@ -148,7 +170,10 @@ public function reset() /** @proxy */ public function count($foundOnly = false) { - return $this->mongoCursor->count($foundOnly); + $mongoCursor = $this->mongoCursor; + return $this->retry(function() use ($mongoCursor, $foundOnly) { + return $mongoCursor->count($foundOnly); + }); } /** @proxy */ @@ -247,4 +272,23 @@ public function getSingleResult() $this->reset(); return $result ? $result : null; } -} + + protected function retry(\Closure $retry) + { + if ($this->numRetries !== null && $this->numRetries !== false) { + for ($i = 1; $i <= $this->numRetries; $i++) { + try { + return $retry(); + break; + } catch (\MongoException $e) { + if ($i === $this->numRetries) { + throw $e; + } + sleep(1); + } + } + } else { + return $retry(); + } + } +} \ No newline at end of file diff --git a/lib/Doctrine/MongoDB/Database.php b/lib/Doctrine/MongoDB/Database.php index faed132e..a6e8b53c 100644 --- a/lib/Doctrine/MongoDB/Database.php +++ b/lib/Doctrine/MongoDB/Database.php @@ -50,18 +50,27 @@ class Database */ protected $cmd; + /** + * Number of times to retry queries. + * + * @var mixed + */ + protected $numRetries; + /** * Create a new MongoDB instance which wraps a PHP MongoDB instance. * * @param MongoDB $mongoDB The MongoDB instance to wrap. * @param EventManager $evm The EventManager instance. * @param string $cmd The MongoDB cmd character. + * @param mixed $numRetries Number of times to retry queries. */ - public function __construct(\MongoDB $mongoDB, EventManager $evm, $cmd) + public function __construct(\MongoDB $mongoDB, EventManager $evm, $cmd, $numRetries) { $this->mongoDB = $mongoDB; $this->eventManager = $evm; $this->cmd = $cmd; + $this->numRetries = $numRetries; } /** @@ -250,7 +259,7 @@ public function selectCollection($name) protected function wrapCollection(\MongoCollection $collection) { return new Collection( - $collection, $this, $this->eventManager, $this->cmd + $collection, $this, $this->eventManager, $this->cmd, $this->numRetries ); } diff --git a/lib/Doctrine/MongoDB/LoggableDatabase.php b/lib/Doctrine/MongoDB/LoggableDatabase.php index fec51675..926ad8a1 100755 --- a/lib/Doctrine/MongoDB/LoggableDatabase.php +++ b/lib/Doctrine/MongoDB/LoggableDatabase.php @@ -45,14 +45,15 @@ class LoggableDatabase extends Database implements Loggable * @param MongoDB $mongoDB The MongoDB instance to wrap. * @param EventManager $evm The EventManager instance. * @param string $cmd The MongoDB cmd character. + * @param mixed $numRetries Number of times to retry queries. * @param Closure $loggerCallable Logger callback function. */ - public function __construct(\MongoDB $mongoDB, EventManager $evm, $cmd, $loggerCallable) + public function __construct(\MongoDB $mongoDB, EventManager $evm, $cmd, $numRetries, $loggerCallable) { if ( ! is_callable($loggerCallable)) { throw new \InvalidArgumentException('$loggerCallable must be a valid callback'); } - parent::__construct($mongoDB, $evm, $cmd); + parent::__construct($mongoDB, $evm, $cmd, $numRetries); $this->loggerCallable = $loggerCallable; }