I've got a situation where I have a bunch of scripts trying to connect to a very busy database. These scripts typically lock records to do work on them by updating the record_lock_microtime and record_lock_name columns of the record in question and, once they are done trying to do their work, they will come back to try and unlock the record (or remove the record if the work is complete). The problem I'm having is that my database server is occasionally too busy and will drop a connection and this currently results in a fatal exception being thrown and this in turn means that those job records that have been locked will never be unlocked. If my method that connects via PDO fails, I'd like for it to sleep for some period of time and try again. Apparently the PDO Constructor will throw an exception when the database connection attempt fails and I do have a record of these exceptions in my log files.
The problem I'm having is understanding how to structure my initializePDO method. I expect I'll just need to put this try/catch section inside a loop until it successfully connects or something...
private function initializePDO() {
try {
$this->db = new PDO(DB_DSN, DB_USER, DB_PASS);
if (!$this->db) {
$error_msg = 'PDO create failed for slot #' . $this->slot . '. $this->db: ' . "\n" . print_r($this->db, TRUE);
MTLog::getInstance()->error($error_msg);
throw new Exception($error_msg);
} else {
MTLog::getInstance()->debug2('PDO create success in slot #' . $this->slot);
}
} catch (Exception $e) {
// instead of re-throwing exception here, maybe sleep a bit and try again
throw New Exception('initializePDO() threw an exception while trying to create a PDO connection to the db: ' . $e->getMessage());
}
}
A couple of things are making this tricky. First, I can't really think of a good way to replicate the circumstance of a busy server so I can get the exact exception thrown (I'm not sure if it's just a PDOException or something else). Second, I'm wondering if I give a hoot about the SQLState values. I can't seem to locate any page in the manual which might detail what the different SQLSTATE values are. Here are a couple of error entries that appear when an exception gets thrown by my current code.
2013-06-20 12:13:34 [20649] ERROR : EXCEPTION in deleteImage() main block, slot=24: initializePDO() threw an exception while trying to create a PDO connection to the db: SQLSTATE[08004] [1040] Too many connections
2013-06-20 12:13:34 [20654] ERROR : EXCEPTION in deleteImage() main block, slot=2: initializePDO() threw an exception while trying to create a PDO connection to the db: SQLSTATE[HY000] [1040] Too many connections
Does anyone see any potential problems (e.g., infinite loops) with this modified version?
/**
* Initializes a PDO Connection to the database. If the PDO constructor throws
* an exception, this function will keep trying to connect MAX_DB_CONNECT_ATTEMPTS times
* and, if it fails, will sleep pow(DB_CONNECT_SLEEP_BASE, $attempts) seconds before trying again.
* @throws Exception
*/
private function initializePDO() {
$attempts = 0;
do {
try {
$this->db = new PDO(EREP_DB_DSN, EREP_DB_USER, EREP_DB_PASS);
if (!$this->db) {
// i've yet to see this code execute
$error_msg = 'PDO create failed for slot #' . $this->slot . '. $this->db: ' . "\n" . print_r($this->db, TRUE);
MTLog::getInstance()->error($error_msg);
throw new Exception($error_msg);
} else {
MTLog::getInstance()->debug2('PDO create success in slot #' . $this->slot);
}
} catch (Exception $e) {
// the connection attempt throw an exception!
$this->db = NULL;
$attempts++;
if ($attempts > self::MAX_DB_CONNECT_ATTEMPTS) {
// too msny attempts! throw an exception and stop trying to connect
throw New Exception('initializePDO() threw an exception while trying to create a PDO connection to the db: ' . $e->getMessage());
}
$sleep_time = pow(self::DB_CONNECT_SLEEP_BASE, $attempts);
MTLog::getInstance()->debug("DB connection attempt #" . $attempts . " failed in slot #" . $this->slot . ". Sleeping for " . $sleep_time . " seconds.");
sleep($sleep_time);
}
} while (is_null($this->db));
} // initializePDO()