RetryExecutor.php 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. <?php
  2. namespace React\Dns\Query;
  3. use React\Promise\Deferred;
  4. use React\Promise\PromiseInterface;
  5. final class RetryExecutor implements ExecutorInterface
  6. {
  7. private $executor;
  8. private $retries;
  9. public function __construct(ExecutorInterface $executor, $retries = 2)
  10. {
  11. $this->executor = $executor;
  12. $this->retries = $retries;
  13. }
  14. public function query(Query $query)
  15. {
  16. return $this->tryQuery($query, $this->retries);
  17. }
  18. public function tryQuery(Query $query, $retries)
  19. {
  20. $deferred = new Deferred(function () use (&$promise) {
  21. if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
  22. $promise->cancel();
  23. }
  24. });
  25. $success = function ($value) use ($deferred, &$errorback) {
  26. $errorback = null;
  27. $deferred->resolve($value);
  28. };
  29. $executor = $this->executor;
  30. $errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) {
  31. if (!$e instanceof TimeoutException) {
  32. $errorback = null;
  33. $deferred->reject($e);
  34. } elseif ($retries <= 0) {
  35. $errorback = null;
  36. $deferred->reject($e = new \RuntimeException(
  37. 'DNS query for ' . $query->describe() . ' failed: too many retries',
  38. 0,
  39. $e
  40. ));
  41. // avoid garbage references by replacing all closures in call stack.
  42. // what a lovely piece of code!
  43. $r = new \ReflectionProperty('Exception', 'trace');
  44. $r->setAccessible(true);
  45. $trace = $r->getValue($e);
  46. // Exception trace arguments are not available on some PHP 7.4 installs
  47. // @codeCoverageIgnoreStart
  48. foreach ($trace as $ti => $one) {
  49. if (isset($one['args'])) {
  50. foreach ($one['args'] as $ai => $arg) {
  51. if ($arg instanceof \Closure) {
  52. $trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
  53. }
  54. }
  55. }
  56. }
  57. // @codeCoverageIgnoreEnd
  58. $r->setValue($e, $trace);
  59. } else {
  60. --$retries;
  61. $promise = $executor->query($query)->then(
  62. $success,
  63. $errorback
  64. );
  65. }
  66. };
  67. $promise = $this->executor->query($query)->then(
  68. $success,
  69. $errorback
  70. );
  71. return $deferred->promise();
  72. }
  73. }