friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Socket;
4
5use React\EventLoop\Loop;
6use React\EventLoop\LoopInterface;
7use React\Promise\Promise;
8
9final class TimeoutConnector implements ConnectorInterface
10{
11 private $connector;
12 private $timeout;
13 private $loop;
14
15 /**
16 * @param ConnectorInterface $connector
17 * @param float $timeout
18 * @param ?LoopInterface $loop
19 */
20 public function __construct(ConnectorInterface $connector, $timeout, $loop = null)
21 {
22 if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
23 throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
24 }
25
26 $this->connector = $connector;
27 $this->timeout = $timeout;
28 $this->loop = $loop ?: Loop::get();
29 }
30
31 public function connect($uri)
32 {
33 $promise = $this->connector->connect($uri);
34
35 $loop = $this->loop;
36 $time = $this->timeout;
37 return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $uri) {
38 $timer = null;
39 $promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
40 if ($timer) {
41 $loop->cancelTimer($timer);
42 }
43 $timer = false;
44 $resolve($v);
45 }, function ($v) use (&$timer, $loop, $reject) {
46 if ($timer) {
47 $loop->cancelTimer($timer);
48 }
49 $timer = false;
50 $reject($v);
51 });
52
53 // promise already resolved => no need to start timer
54 if ($timer === false) {
55 return;
56 }
57
58 // start timeout timer which will cancel the pending promise
59 $timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $uri) {
60 $reject(new \RuntimeException(
61 'Connection to ' . $uri . ' timed out after ' . $time . ' seconds (ETIMEDOUT)',
62 \defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110
63 ));
64
65 // Cancel pending connection to clean up any underlying resources and references.
66 // Avoid garbage references in call stack by passing pending promise by reference.
67 assert(\method_exists($promise, 'cancel'));
68 $promise->cancel();
69 $promise = null;
70 });
71 }, function () use (&$promise) {
72 // Cancelling this promise will cancel the pending connection, thus triggering the rejection logic above.
73 // Avoid garbage references in call stack by passing pending promise by reference.
74 assert(\method_exists($promise, 'cancel'));
75 $promise->cancel();
76 $promise = null;
77 });
78 }
79}