retries = $retries !== null ? (int) $retries : 3; $this->retryFunction = $retryFunction; // @todo revisit this approach // @codeCoverageIgnoreStart $this->delayFunction = function ($delay) { usleep($delay); }; // @codeCoverageIgnoreEnd } /** * Executes the retry process. * * @param callable $function * @param array $arguments [optional] * @return mixed * @throws \Exception The last exception caught while retrying. */ public function execute(callable $function, array $arguments = []) { $delayFunction = $this->delayFunction; $retryAttempt = 0; $exception = null; while (true) { try { return call_user_func_array($function, $arguments); } catch (\Exception $exception) { if ($this->retryFunction) { if (!call_user_func($this->retryFunction, $exception)) { throw $exception; } } if ($retryAttempt >= $this->retries) { break; } $delayFunction($this->calculateDelay($retryAttempt)); $retryAttempt++; } } throw $exception; } /** * @param callable $delayFunction * @return void */ public function setDelayFunction(callable $delayFunction) { $this->delayFunction = $delayFunction; } /** * Calculates exponential delay. * * @param int $attempt The attempt number used to calculate the delay. * @return int */ public static function calculateDelay($attempt) { return min(mt_rand(0, 1000000) + pow(2, $attempt) * 1000000, self::MAX_DELAY_MICROSECONDS); } }