截流自动化的商城平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Backoff.php 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <?php
  2. namespace STS\Backoff;
  3. use Exception;
  4. use InvalidArgumentException;
  5. use STS\Backoff\Strategies\ConstantStrategy;
  6. use STS\Backoff\Strategies\ExponentialStrategy;
  7. use STS\Backoff\Strategies\LinearStrategy;
  8. use STS\Backoff\Strategies\PolynomialStrategy;
  9. /**
  10. * Class Retry
  11. * @package STS\Backoff
  12. */
  13. class Backoff
  14. {
  15. /**
  16. * @var string
  17. */
  18. public static $defaultStrategy = "polynomial";
  19. /**
  20. * @var int
  21. */
  22. public static $defaultMaxAttempts = 5;
  23. /**
  24. * @var bool
  25. */
  26. public static $defaultJitterEnabled = false;
  27. /**
  28. * This callable should take an 'attempt' integer, and return a wait time in milliseconds
  29. *
  30. * @var callable
  31. */
  32. protected $strategy;
  33. /**
  34. * @var array
  35. */
  36. protected $strategies = [
  37. 'constant' => ConstantStrategy::class,
  38. 'linear' => LinearStrategy::class,
  39. 'polynomial' => PolynomialStrategy::class,
  40. 'exponential' => ExponentialStrategy::class
  41. ];
  42. /**
  43. * @var int
  44. */
  45. protected $maxAttempts;
  46. /**
  47. * The max wait time you want to allow, regardless of what the strategy says
  48. *
  49. * @var int|null In milliseconds
  50. */
  51. protected $waitCap;
  52. /**
  53. * @var bool
  54. */
  55. protected $useJitter = false;
  56. /**
  57. * @var array
  58. */
  59. protected $exceptions = [];
  60. /**
  61. * This will decide whether to retry or not.
  62. * @var callable
  63. */
  64. protected $decider;
  65. /**
  66. * This receive any exceptions we encounter.
  67. * @var callable
  68. */
  69. protected $errorHandler;
  70. /**
  71. * @param int $maxAttempts
  72. * @param mixed $strategy
  73. * @param int $waitCap
  74. * @param bool $useJitter
  75. * @param callable $decider
  76. */
  77. public function __construct(
  78. $maxAttempts = null,
  79. $strategy = null,
  80. $waitCap = null,
  81. $useJitter = null,
  82. $decider = null
  83. ) {
  84. $this->setMaxAttempts($maxAttempts ?: self::$defaultMaxAttempts);
  85. $this->setStrategy($strategy ?: self::$defaultStrategy);
  86. $this->setJitter($useJitter ?: self::$defaultJitterEnabled);
  87. $this->setWaitCap($waitCap);
  88. $this->setDecider($decider ?: $this->getDefaultDecider());
  89. }
  90. /**
  91. * @param integer $attempts
  92. */
  93. public function setMaxAttempts($attempts)
  94. {
  95. $this->maxAttempts = $attempts;
  96. return $this;
  97. }
  98. /**
  99. * @return integer
  100. */
  101. public function getMaxAttempts()
  102. {
  103. return $this->maxAttempts;
  104. }
  105. /**
  106. * @param int|null $cap
  107. *
  108. * @return $this
  109. */
  110. public function setWaitCap($cap)
  111. {
  112. $this->waitCap = $cap;
  113. return $this;
  114. }
  115. /**
  116. * @return int|null
  117. */
  118. public function getWaitCap()
  119. {
  120. return $this->waitCap;
  121. }
  122. /**
  123. * @param bool $useJitter
  124. *
  125. * @return $this
  126. */
  127. public function setJitter($useJitter)
  128. {
  129. $this->useJitter = $useJitter;
  130. return $this;
  131. }
  132. /**
  133. *
  134. */
  135. public function enableJitter()
  136. {
  137. $this->setJitter(true);
  138. return $this;
  139. }
  140. /**
  141. *
  142. */
  143. public function disableJitter()
  144. {
  145. $this->setJitter(false);
  146. return $this;
  147. }
  148. public function jitterEnabled()
  149. {
  150. return $this->useJitter;
  151. }
  152. /**
  153. * @return callable
  154. */
  155. public function getStrategy()
  156. {
  157. return $this->strategy;
  158. }
  159. /**
  160. * @param mixed $strategy
  161. *
  162. * @return $this
  163. */
  164. public function setStrategy($strategy)
  165. {
  166. $this->strategy = $this->buildStrategy($strategy);
  167. return $this;
  168. }
  169. /**
  170. * Builds a callable strategy.
  171. *
  172. * @param mixed $strategy Can be a string that matches a key in $strategies, an instance of AbstractStrategy
  173. * (or any other instance that has an __invoke method), a callback function, or
  174. * an integer (which we interpret to mean you want a ConstantStrategy)
  175. *
  176. * @return callable
  177. */
  178. protected function buildStrategy($strategy)
  179. {
  180. if (is_string($strategy) && array_key_exists($strategy, $this->strategies)) {
  181. return new $this->strategies[$strategy];
  182. }
  183. if (is_callable($strategy)) {
  184. return $strategy;
  185. }
  186. if (is_int($strategy)) {
  187. return new ConstantStrategy($strategy);
  188. }
  189. throw new InvalidArgumentException("Invalid strategy: " . $strategy);
  190. }
  191. /**
  192. * @param callable $callback
  193. *
  194. * @return mixed
  195. * @throws Exception
  196. */
  197. public function run($callback)
  198. {
  199. $attempt = 0;
  200. $try = true;
  201. while ($try) {
  202. $result = null;
  203. $exception = null;
  204. $this->wait($attempt);
  205. try {
  206. $result = call_user_func($callback);
  207. } catch (\Throwable $e) {
  208. if ($e instanceof \Error) {
  209. $e = new Exception($e->getMessage(), $e->getCode(), $e);
  210. }
  211. $this->exceptions[] = $e;
  212. $exception = $e;
  213. } catch (Exception $e) {
  214. $this->exceptions[] = $e;
  215. $exception = $e;
  216. }
  217. $try = call_user_func($this->decider, ++$attempt, $this->getMaxAttempts(), $result, $exception);
  218. if($try && isset($this->errorHandler)) {
  219. call_user_func($this->errorHandler, $exception, $attempt, $this->getMaxAttempts());
  220. }
  221. }
  222. return $result;
  223. }
  224. /**
  225. * Sets the decider callback
  226. * @param callable $callback
  227. * @return $this
  228. */
  229. public function setDecider($callback)
  230. {
  231. $this->decider = $callback;
  232. return $this;
  233. }
  234. /**
  235. * Sets the error handler callback
  236. * @param callable $callback
  237. * @return $this
  238. */
  239. public function setErrorHandler($callback)
  240. {
  241. $this->errorHandler = $callback;
  242. return $this;
  243. }
  244. /**
  245. * Gets a default decider that simply check exceptions and maxattempts
  246. * @return \Closure
  247. */
  248. protected function getDefaultDecider()
  249. {
  250. return function ($retry, $maxAttempts, $result = null, $exception = null) {
  251. if($retry >= $maxAttempts && ! is_null($exception)) {
  252. throw $exception;
  253. }
  254. return $retry < $maxAttempts && !is_null($exception);
  255. };
  256. }
  257. /**
  258. * @param int $attempt
  259. */
  260. public function wait($attempt)
  261. {
  262. if ($attempt == 0) {
  263. return;
  264. }
  265. usleep($this->getWaitTime($attempt) * 1000);
  266. }
  267. /**
  268. * @param int $attempt
  269. *
  270. * @return int
  271. */
  272. public function getWaitTime($attempt)
  273. {
  274. $waitTime = call_user_func($this->getStrategy(), $attempt);
  275. return $this->jitter($this->cap($waitTime));
  276. }
  277. /**
  278. * @param int $waitTime
  279. *
  280. * @return mixed
  281. */
  282. protected function cap($waitTime)
  283. {
  284. return is_int($this->getWaitCap())
  285. ? min($this->getWaitCap(), $waitTime)
  286. : $waitTime;
  287. }
  288. /**
  289. * @param int $waitTime
  290. *
  291. * @return int
  292. */
  293. protected function jitter($waitTime)
  294. {
  295. return $this->jitterEnabled()
  296. ? mt_rand(0, $waitTime)
  297. : $waitTime;
  298. }
  299. }