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