Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
<?php
2
namespace GuzzleHttp\Handler;
3
 
4
use GuzzleHttp\Promise as P;
5
use GuzzleHttp\Promise\Promise;
6
use GuzzleHttp\Psr7;
7
use Psr\Http\Message\RequestInterface;
8
 
9
/**
10
 * Returns an asynchronous response using curl_multi_* functions.
11
 *
12
 * When using the CurlMultiHandler, custom curl options can be specified as an
13
 * associative array of curl option constants mapping to values in the
14
 * **curl** key of the provided request options.
15
 *
16
 * @property resource $_mh Internal use only. Lazy loaded multi-handle.
17
 */
18
class CurlMultiHandler
19
{
20
    /** @var CurlFactoryInterface */
21
    private $factory;
22
    private $selectTimeout;
23
    private $active;
24
    private $handles = [];
25
    private $delays = [];
26
 
27
    /**
28
     * This handler accepts the following options:
29
     *
30
     * - handle_factory: An optional factory  used to create curl handles
31
     * - select_timeout: Optional timeout (in seconds) to block before timing
32
     *   out while selecting curl handles. Defaults to 1 second.
33
     *
34
     * @param array $options
35
     */
36
    public function __construct(array $options = [])
37
    {
38
        $this->factory = isset($options['handle_factory'])
39
            ? $options['handle_factory'] : new CurlFactory(50);
40
        $this->selectTimeout = isset($options['select_timeout'])
41
            ? $options['select_timeout'] : 1;
42
    }
43
 
44
    public function __get($name)
45
    {
46
        if ($name === '_mh') {
47
            return $this->_mh = curl_multi_init();
48
        }
49
 
50
        throw new \BadMethodCallException();
51
    }
52
 
53
    public function __destruct()
54
    {
55
        if (isset($this->_mh)) {
56
            curl_multi_close($this->_mh);
57
            unset($this->_mh);
58
        }
59
    }
60
 
61
    public function __invoke(RequestInterface $request, array $options)
62
    {
63
        $easy = $this->factory->create($request, $options);
64
        $id = (int) $easy->handle;
65
 
66
        $promise = new Promise(
67
            [$this, 'execute'],
68
            function () use ($id) { return $this->cancel($id); }
69
        );
70
 
71
        $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
72
 
73
        return $promise;
74
    }
75
 
76
    /**
77
     * Ticks the curl event loop.
78
     */
79
    public function tick()
80
    {
81
        // Add any delayed handles if needed.
82
        if ($this->delays) {
83
            $currentTime = microtime(true);
84
            foreach ($this->delays as $id => $delay) {
85
                if ($currentTime >= $delay) {
86
                    unset($this->delays[$id]);
87
                    curl_multi_add_handle(
88
                        $this->_mh,
89
                        $this->handles[$id]['easy']->handle
90
                    );
91
                }
92
            }
93
        }
94
 
95
        // Step through the task queue which may add additional requests.
96
        P\queue()->run();
97
 
98
        if ($this->active &&
99
            curl_multi_select($this->_mh, $this->selectTimeout) === -1
100
        ) {
101
            // Perform a usleep if a select returns -1.
102
            // See: https://bugs.php.net/bug.php?id=61141
103
            usleep(250);
104
        }
105
 
106
        while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
107
 
108
        $this->processMessages();
109
    }
110
 
111
    /**
112
     * Runs until all outstanding connections have completed.
113
     */
114
    public function execute()
115
    {
116
        $queue = P\queue();
117
 
118
        while ($this->handles || !$queue->isEmpty()) {
119
            // If there are no transfers, then sleep for the next delay
120
            if (!$this->active && $this->delays) {
121
                usleep($this->timeToNext());
122
            }
123
            $this->tick();
124
        }
125
    }
126
 
127
    private function addRequest(array $entry)
128
    {
129
        $easy = $entry['easy'];
130
        $id = (int) $easy->handle;
131
        $this->handles[$id] = $entry;
132
        if (empty($easy->options['delay'])) {
133
            curl_multi_add_handle($this->_mh, $easy->handle);
134
        } else {
135
            $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
136
        }
137
    }
138
 
139
    /**
140
     * Cancels a handle from sending and removes references to it.
141
     *
142
     * @param int $id Handle ID to cancel and remove.
143
     *
144
     * @return bool True on success, false on failure.
145
     */
146
    private function cancel($id)
147
    {
148
        // Cannot cancel if it has been processed.
149
        if (!isset($this->handles[$id])) {
150
            return false;
151
        }
152
 
153
        $handle = $this->handles[$id]['easy']->handle;
154
        unset($this->delays[$id], $this->handles[$id]);
155
        curl_multi_remove_handle($this->_mh, $handle);
156
        curl_close($handle);
157
 
158
        return true;
159
    }
160
 
161
    private function processMessages()
162
    {
163
        while ($done = curl_multi_info_read($this->_mh)) {
164
            $id = (int) $done['handle'];
165
            curl_multi_remove_handle($this->_mh, $done['handle']);
166
 
167
            if (!isset($this->handles[$id])) {
168
                // Probably was cancelled.
169
                continue;
170
            }
171
 
172
            $entry = $this->handles[$id];
173
            unset($this->handles[$id], $this->delays[$id]);
174
            $entry['easy']->errno = $done['result'];
175
            $entry['deferred']->resolve(
176
                CurlFactory::finish(
177
                    $this,
178
                    $entry['easy'],
179
                    $this->factory
180
                )
181
            );
182
        }
183
    }
184
 
185
    private function timeToNext()
186
    {
187
        $currentTime = microtime(true);
188
        $nextTime = PHP_INT_MAX;
189
        foreach ($this->delays as $time) {
190
            if ($time < $nextTime) {
191
                $nextTime = $time;
192
            }
193
        }
194
 
195
        return max(0, $nextTime - $currentTime) * 1000000;
196
    }
197
}