/** * @param CoreException $e * @param ExceptionContext $ec * @return ExceptionStrategy * @throws FailoverException */ public function handleException(CoreException $e, ExceptionContext $ec = null) { // we only want to handle ConnectionErrors if (!$e instanceof ConnectionError) { return ExceptionStrategy::RETHROW(); } $this->log->warning(sprintf("Handling exception for connection %s: %s, trying to reconnect", $this->curConn->getUniqueId(), $e->getMessage())); if ($this->inMultiMode) { // usually we would throw an exception here, because data was lost, so the application should decide what to do. // ITE however wants us to just continue in this case as well, so we try to reset the multi mode and continue, // as if this were a regular error $this->log->warning(sprintf("connection error while multi mode was active, data was likely lost. msg: %s", $e->getMessage())); try { // we cannot use $this->curConn here as its a TCWrapper and would catch the exception directly, invoking this method // again and leading to a recursive loop of death $this->connections[$this->curConnKey]->getClient()->discard(); } catch (Exception $e) { // we just drop the exception } } // disconnect first to reset the internal state in the instance, we set $force to true here, // in order to remove persistent connections as well $this->curConn->disconnect(true); // sleep for 50 ms to give the network a chance to restore itself usleep(50000); // try to reconnect once if (!$this->connectInstance($this->curConn)) { $this->log->warning("Reconnecting failed, deactivating connection"); // not possible to reconnect, deactivate and continue $this->connections->deactivateConnection($this->curConnKey); $this->curConnKey = -1; $this->curConn = null; } // at this point either the old connection is active again, or we have to look for a new one if ($this->curConn === null) { $this->log->warning(sprintf("Trying to find new connection, configured connections: %s", $this->connections->getUniqueId())); // we need to find a functioning connection try { $this->activateNextConnection(); } catch (FailoverException $foe) { // no more healthy connections, we catch the exception so we can link it to the original one throw new FailoverException("while handling a connection problem in the FailoverWrapper: " . $e->getMessage(), FailoverException::ERROR_NO_HEALTHY_CONNECTIONS, $e); } } $this->log->warning(sprintf("New connection %s active, reissuing command %s", $this->curConn->getUniqueId(), $ec->getFunction())); // at this point we (hopefully) have a functioning connection so we retry the command // TODO: this recursive call may lead to a very nasty deadlock, if a connection keeps breaking right after connecting // this is what the UNSTABLE status was used for. maybe instead of using $this, we should call directly on the connection object $retVal = call_user_func_array(array($this, $ec->getFunction()), $ec->getArguments()); $strategy = ExceptionStrategy::RETURN_VALUE(); $strategy->returnValue = $retVal; return $strategy; }