1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace mpyw\Co\Internal; |
4
|
|
|
use mpyw\Co\Co; |
5
|
|
|
use mpyw\Co\Internal\CoOption; |
6
|
|
|
use mpyw\Co\CURLException; |
7
|
|
|
use React\Promise\Deferred; |
8
|
|
|
|
9
|
|
|
class CURLPool |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* Options. |
13
|
|
|
* @var CoOption |
14
|
|
|
*/ |
15
|
|
|
private $options; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* cURL multi handle. |
19
|
|
|
* @var resource |
20
|
|
|
*/ |
21
|
|
|
private $mh; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* The number of dispatched cURL handle. |
25
|
|
|
* @var int |
26
|
|
|
*/ |
27
|
|
|
private $count = 0; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* cURL handles those have not been dispatched. |
31
|
|
|
* @var array |
32
|
|
|
*/ |
33
|
|
|
private $queue = []; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* React Deferreds. |
37
|
|
|
* @var Deferred |
38
|
|
|
*/ |
39
|
|
|
private $deferreds = []; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Constructor. |
43
|
|
|
* Initialize cURL multi handle. |
44
|
|
|
* @param CoOption $options |
45
|
|
|
*/ |
46
|
1 |
|
public function __construct(CoOption $options) |
47
|
1 |
|
{ |
48
|
1 |
|
$this->options = $options; |
49
|
1 |
|
$this->mh = curl_multi_init(); |
50
|
1 |
|
$flags = (int)$options['pipeline'] + (int)$options['multiplex'] * 2; |
51
|
1 |
|
curl_multi_setopt($this->mh, CURLMOPT_PIPELINING, $flags); |
52
|
1 |
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Call curl_multi_add_handle() or push into queue. |
56
|
|
|
* @param resource $ch |
57
|
|
|
* @param Deferred $deferred |
58
|
|
|
*/ |
59
|
|
|
public function addOrEnqueue($ch, $deferred = null) |
60
|
|
|
{ |
61
|
|
|
if ($this->count >= $this->options['concurrency']) { |
62
|
|
|
if (isset($this->queue[(string)$ch])) { |
63
|
|
|
throw new \InvalidArgumentException("The cURL handle is already enqueued: $ch"); |
64
|
|
|
} |
65
|
|
|
$this->queue[(string)$ch] = $ch; |
66
|
|
|
} else { |
67
|
|
|
$errno = curl_multi_add_handle($this->mh, $ch); |
68
|
|
|
if ($errno !== CURLM_OK) { |
69
|
|
|
$msg = curl_multi_strerror($errno) . ": $ch"; |
70
|
|
|
$class = $errno === 7 ? '\InvalidArgumentException' : '\RuntimeException'; |
71
|
|
|
throw new $class($msg); |
72
|
|
|
} |
73
|
|
|
++$this->count; |
74
|
|
|
} |
75
|
|
|
if ($deferred) { |
76
|
|
|
$this->deferreds[(string)$ch] = $deferred; |
77
|
|
|
} |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Run curl_multi_exec() loop. |
82
|
|
|
*/ |
83
|
1 |
|
public function wait() |
84
|
1 |
|
{ |
85
|
1 |
|
curl_multi_exec($this->mh, $active); // Start requests. |
86
|
|
|
do { |
87
|
1 |
|
curl_multi_select($this->mh, $this->options['interval']); // Wait events. |
88
|
1 |
|
curl_multi_exec($this->mh, $active); |
89
|
1 |
|
foreach ($this->readEntries() as $entry) { |
90
|
|
|
$r = $entry['result'] === CURLE_OK |
91
|
|
|
? curl_multi_getcontent($entry['handle']) |
92
|
|
|
: new CURLException(curl_error($entry['handle']), $entry['result'], $entry['handle']); |
93
|
|
|
if (isset($this->deferreds[(string)$entry['handle']])) { |
94
|
|
|
$deferred = $this->deferreds[(string)$entry['handle']]; |
95
|
|
|
unset($this->deferreds[(string)$entry['handle']]); |
96
|
|
|
$r instanceof CURLException ? $deferred->reject($r) : $deferred->resolve($r); |
97
|
|
|
} |
98
|
|
|
} |
99
|
1 |
|
} while ($this->count > 0 || $this->queue); |
100
|
|
|
// All request must be done when reached here. |
101
|
1 |
|
if ($active) { |
102
|
|
|
throw new \LogicException('Unreachable statement.'); |
103
|
|
|
} |
104
|
1 |
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Read completed cURL handles. |
108
|
|
|
* @return array |
109
|
|
|
*/ |
110
|
1 |
|
private function readEntries() |
111
|
1 |
|
{ |
112
|
1 |
|
$entries = []; |
113
|
1 |
|
while ($entry = curl_multi_info_read($this->mh)) { |
114
|
|
|
$entries[] = $entry; |
115
|
|
|
} |
116
|
1 |
|
foreach ($entries as $entry) { |
117
|
|
|
curl_multi_remove_handle($this->mh, $entry['handle']); |
118
|
|
|
--$this->count; |
119
|
|
|
if ($this->queue) { |
|
|
|
|
120
|
|
|
$ch = array_shift($this->queue); |
121
|
|
|
$this->addOrEnqueue($ch); |
122
|
|
|
} |
123
|
|
|
} |
124
|
1 |
|
return $entries; |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.