14
14
*/
15
15
final class HappyEyeBallsConnectionBuilder
16
16
{
17
- const CONNECT_INTERVAL = 0.1 ;
18
- const RESOLVE_WAIT = 0.5 ;
17
+ /**
18
+ * As long as we haven't connected yet keep popping an IP address of the connect queue until one of them
19
+ * succeeds or they all fail. We will wait 100ms between connection attempts as per RFC.
20
+ *
21
+ * @link https://tools.ietf.org/html/rfc8305#section-5
22
+ */
23
+ const CONNECTION_ATTEMPT_DELAY = 0.1 ;
24
+
25
+ /**
26
+ * Delay `A` lookup by 50ms sending out connection to IPv4 addresses when IPv6 records haven't
27
+ * resolved yet as per RFC.
28
+ *
29
+ * @link https://tools.ietf.org/html/rfc8305#section-3
30
+ */
31
+ const RESOLUTION_DELAY = 0.05 ;
19
32
20
33
public $ loop ;
21
34
public $ connector ;
@@ -29,7 +42,7 @@ final class HappyEyeBallsConnectionBuilder
29
42
public $ resolverPromises = array ();
30
43
public $ connectionPromises = array ();
31
44
public $ connectQueue = array ();
32
- public $ timer ;
45
+ public $ nextAttemptTimer ;
33
46
public $ parts ;
34
47
public $ ipsCount = 0 ;
35
48
public $ failureCount = 0 ;
@@ -48,66 +61,52 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector,
48
61
49
62
public function connect ()
50
63
{
64
+ $ timer = null ;
51
65
$ that = $ this ;
52
- return new Promise \Promise (function ($ resolve , $ reject ) use ($ that ) {
66
+ return new Promise \Promise (function ($ resolve , $ reject ) use ($ that, & $ timer ) {
53
67
$ lookupResolve = function ($ type ) use ($ that , $ resolve , $ reject ) {
54
68
return function (array $ ips ) use ($ that , $ type , $ resolve , $ reject ) {
55
69
unset($ that ->resolverPromises [$ type ]);
56
70
$ that ->resolved [$ type ] = true ;
57
71
58
72
$ that ->mixIpsIntoConnectQueue ($ ips );
59
73
60
- if ($ that ->timer instanceof TimerInterface) {
74
+ if ($ that ->nextAttemptTimer instanceof TimerInterface) {
61
75
return ;
62
76
}
63
77
64
78
$ that ->check ($ resolve , $ reject );
65
79
};
66
80
};
67
81
68
- $ ipv4Deferred = null ;
69
- $ timer = null ;
70
- $ that ->resolverPromises [Message::TYPE_AAAA ] = $ that ->resolve (Message::TYPE_AAAA , $ reject )->then ($ lookupResolve (Message::TYPE_AAAA ))->then (function () use (&$ ipv4Deferred ) {
71
- if ($ ipv4Deferred instanceof Promise \Deferred) {
72
- $ ipv4Deferred ->resolve ();
73
- }
74
- });
75
- $ that ->resolverPromises [Message::TYPE_A ] = $ that ->resolve (Message::TYPE_A , $ reject )->then (function ($ ips ) use ($ that , &$ ipv4Deferred , &$ timer ) {
82
+ $ that ->resolverPromises [Message::TYPE_AAAA ] = $ that ->resolve (Message::TYPE_AAAA , $ reject )->then ($ lookupResolve (Message::TYPE_AAAA ));
83
+ $ that ->resolverPromises [Message::TYPE_A ] = $ that ->resolve (Message::TYPE_A , $ reject )->then (function ($ ips ) use ($ that , &$ timer ) {
84
+ // happy path: IPv6 has resolved already, continue with IPv4 addresses
76
85
if ($ that ->resolved [Message::TYPE_AAAA ] === true ) {
77
- return Promise \resolve ( $ ips) ;
86
+ return $ ips ;
78
87
}
79
88
80
- /**
81
- * Delay A lookup by 50ms sending out connection to IPv4 addresses when IPv6 records haven't
82
- * resolved yet as per RFC.
83
- *
84
- * @link https://tools.ietf.org/html/rfc8305#section-3
85
- */
86
- $ ipv4Deferred = new Promise \Deferred ();
89
+ // Otherwise delay processing IPv4 lookup until short timer passes or IPv6 resolves in the meantime
87
90
$ deferred = new Promise \Deferred ();
88
-
89
- $ timer = $ that ->loop ->addTimer ($ that ::RESOLVE_WAIT , function () use ($ deferred , $ ips ) {
90
- $ ipv4Deferred = null ;
91
+ $ timer = $ that ->loop ->addTimer ($ that ::RESOLUTION_DELAY , function () use ($ deferred , $ ips ) {
91
92
$ deferred ->resolve ($ ips );
92
93
});
93
94
94
- $ ipv4Deferred -> promise () ->then (function () use ($ that , & $ timer , $ deferred , $ ips ) {
95
+ $ that -> resolverPromises [Message:: TYPE_AAAA ] ->then (function () use ($ that , $ timer , $ deferred , $ ips ) {
95
96
$ that ->loop ->cancelTimer ($ timer );
96
97
$ deferred ->resolve ($ ips );
97
98
});
98
99
99
100
return $ deferred ->promise ();
100
101
})->then ($ lookupResolve (Message::TYPE_A ));
101
102
}, function ($ _ , $ reject ) use ($ that , &$ timer ) {
102
- $ that ->cleanUp ();
103
+ $ reject (new \RuntimeException ('Connection to ' . $ that ->uri . ' cancelled ' . (!$ that ->connectionPromises ? ' during DNS lookup ' : '' )));
104
+ $ _ = $ reject = null ;
103
105
106
+ $ that ->cleanUp ();
104
107
if ($ timer instanceof TimerInterface) {
105
108
$ that ->loop ->cancelTimer ($ timer );
106
109
}
107
-
108
- $ reject (new \RuntimeException ('Connection to ' . $ that ->uri . ' cancelled during DNS lookup ' ));
109
-
110
- $ _ = $ reject = null ;
111
110
});
112
111
}
113
112
@@ -126,7 +125,6 @@ public function resolve($type, $reject)
126
125
}
127
126
128
127
if ($ that ->ipsCount === 0 ) {
129
- $ that ->resolved = null ;
130
128
$ that ->resolverPromises = null ;
131
129
$ reject (new \RuntimeException ('Connection to ' . $ that ->uri . ' failed during DNS lookup: DNS error ' ));
132
130
}
@@ -138,9 +136,9 @@ public function resolve($type, $reject)
138
136
*/
139
137
public function check ($ resolve , $ reject )
140
138
{
141
- if (\count ($ this ->connectQueue ) === 0 && $ this ->resolved [Message::TYPE_A ] === true && $ this ->resolved [Message::TYPE_AAAA ] === true && $ this ->timer instanceof TimerInterface) {
142
- $ this ->loop ->cancelTimer ($ this ->timer );
143
- $ this ->timer = null ;
139
+ if (\count ($ this ->connectQueue ) === 0 && $ this ->resolved [Message::TYPE_A ] === true && $ this ->resolved [Message::TYPE_AAAA ] === true && $ this ->nextAttemptTimer instanceof TimerInterface) {
140
+ $ this ->loop ->cancelTimer ($ this ->nextAttemptTimer );
141
+ $ this ->nextAttemptTimer = null ;
144
142
}
145
143
146
144
if (\count ($ this ->connectQueue ) === 0 ) {
@@ -156,7 +154,7 @@ public function check($resolve, $reject)
156
154
$ that ->cleanUp ();
157
155
158
156
$ resolve ($ connection );
159
- }, function () use ($ that , $ ip , $ resolve , $ reject ) {
157
+ }, function () use ($ that , $ ip , $ reject ) {
160
158
unset($ that ->connectionPromises [$ ip ]);
161
159
162
160
$ that ->failureCount ++;
@@ -178,8 +176,8 @@ public function check($resolve, $reject)
178
176
*
179
177
* @link https://tools.ietf.org/html/rfc8305#section-5
180
178
*/
181
- if ((\count ($ this ->connectQueue ) > 0 || ($ this ->resolved [Message::TYPE_A ] === false || $ this ->resolved [Message::TYPE_AAAA ] === false )) && $ this ->timer === null ) {
182
- $ this ->timer = $ this ->loop ->addPeriodicTimer (self ::CONNECT_INTERVAL , function () use ($ that , $ resolve , $ reject ) {
179
+ if ((\count ($ this ->connectQueue ) > 0 || ($ this ->resolved [Message::TYPE_A ] === false || $ this ->resolved [Message::TYPE_AAAA ] === false )) && $ this ->nextAttemptTimer === null ) {
180
+ $ this ->nextAttemptTimer = $ this ->loop ->addPeriodicTimer (self ::CONNECTION_ATTEMPT_DELAY , function () use ($ that , $ resolve , $ reject ) {
183
181
$ that ->check ($ resolve , $ reject );
184
182
});
185
183
}
@@ -240,23 +238,21 @@ public function attemptConnection($ip)
240
238
*/
241
239
public function cleanUp ()
242
240
{
243
- /** @var CancellablePromiseInterface $promise */
244
- foreach ($ this ->connectionPromises as $ index => $ connectionPromise ) {
241
+ foreach ($ this ->connectionPromises as $ connectionPromise ) {
245
242
if ($ connectionPromise instanceof CancellablePromiseInterface) {
246
243
$ connectionPromise ->cancel ();
247
244
}
248
245
}
249
246
250
- /** @var CancellablePromiseInterface $promise */
251
- foreach ($ this ->resolverPromises as $ index => $ resolverPromise ) {
247
+ foreach ($ this ->resolverPromises as $ resolverPromise ) {
252
248
if ($ resolverPromise instanceof CancellablePromiseInterface) {
253
249
$ resolverPromise ->cancel ();
254
250
}
255
251
}
256
252
257
- if ($ this ->timer instanceof TimerInterface) {
258
- $ this ->loop ->cancelTimer ($ this ->timer );
259
- $ this ->timer = null ;
253
+ if ($ this ->nextAttemptTimer instanceof TimerInterface) {
254
+ $ this ->loop ->cancelTimer ($ this ->nextAttemptTimer );
255
+ $ this ->nextAttemptTimer = null ;
260
256
}
261
257
}
262
258
0 commit comments