TimeoutConnector.php 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. <?php
  2. namespace React\Socket;
  3. use React\EventLoop\Loop;
  4. use React\EventLoop\LoopInterface;
  5. use React\Promise\Promise;
  6. final class TimeoutConnector implements ConnectorInterface
  7. {
  8. private $connector;
  9. private $timeout;
  10. private $loop;
  11. public function __construct(ConnectorInterface $connector, $timeout, LoopInterface $loop = null)
  12. {
  13. $this->connector = $connector;
  14. $this->timeout = $timeout;
  15. $this->loop = $loop ?: Loop::get();
  16. }
  17. public function connect($uri)
  18. {
  19. $promise = $this->connector->connect($uri);
  20. $loop = $this->loop;
  21. $time = $this->timeout;
  22. return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $uri) {
  23. $timer = null;
  24. $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
  25. if ($timer) {
  26. $loop->cancelTimer($timer);
  27. }
  28. $timer = false;
  29. $resolve($v);
  30. }, function ($v) use (&$timer, $loop, $reject) {
  31. if ($timer) {
  32. $loop->cancelTimer($timer);
  33. }
  34. $timer = false;
  35. $reject($v);
  36. });
  37. // promise already resolved => no need to start timer
  38. if ($timer === false) {
  39. return;
  40. }
  41. // start timeout timer which will cancel the pending promise
  42. $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $uri) {
  43. $reject(new \RuntimeException(
  44. 'Connection to ' . $uri . ' timed out after ' . $time . ' seconds (ETIMEDOUT)',
  45. \defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110
  46. ));
  47. // Cancel pending connection to clean up any underlying resources and references.
  48. // Avoid garbage references in call stack by passing pending promise by reference.
  49. assert(\method_exists($promise, 'cancel'));
  50. $promise->cancel();
  51. $promise = null;
  52. });
  53. }, function () use (&$promise) {
  54. // Cancelling this promise will cancel the pending connection, thus triggering the rejection logic above.
  55. // Avoid garbage references in call stack by passing pending promise by reference.
  56. assert(\method_exists($promise, 'cancel'));
  57. $promise->cancel();
  58. $promise = null;
  59. });
  60. }
  61. }