Completed
Pull Request — master (#54)
by Hiraku
06:18
created

CurlMulti::__destruct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 3
eloc 7
nc 4
nop 0
1
<?php
2
/*
3
 * hirak/prestissimo
4
 * @author Hiraku NAKANO
5
 * @license MIT https://github.com/hirak/prestissimo
6
 */
7
namespace Hirak\Prestissimo;
8
9
class CurlMulti
10
{
11
    /** @var int */
12
    private $maxConnections;
13
14
    /** @var resource curl_multi */
15
    private $mh;
16
17
    /** @var resource[] curl */
18
    private $unused = array();
19
20
    /** @var resource[] curl */
21
    private $using = array();
22
23
    /** @var array {src: Aspects\HttpGetRequest, dest: OutputFile}*/
24
    private $targets;
25
26
    /** @var array {src: Aspects\HttpGetRequest, dest: OutputFile}*/
27
    private $runningTargets;
28
29
    /**
30
     * @param int $maxConnections
31
     */
32
    public function __construct($maxConnections)
33
    {
34
        $this->mh = curl_multi_init();
35
        $this->maxConnections = $maxConnections;
36
37
        for ($i = 0; $i < $maxConnections; ++$i) {
38
            $this->unused[] = curl_init();
39
        }
40
    }
41
42
    public function __destruct()
43
    {
44
        foreach ($this->using as $ch) {
45
            curl_multi_remove_handle($this->mh, $ch);
46
            curl_close($ch);
47
        }
48
49
        foreach ($this->unused as $ch) {
50
            curl_close($ch);
51
        }
52
53
        curl_multi_close($this->mh);
54
    }
55
56
    /**
57
     * @param bool $pipeline
58
     * @codeCoverageIgnore
59
     */
60
    public function setupShareHandler($pipeline)
61
    {
62
        if (function_exists('curl_share_init')) {
63
            $sh = curl_share_init();
64
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
65
66
            foreach ($this->unused as $ch) {
67
                curl_setopt($ch, CURLOPT_SHARE, $sh);
68
            }
69
        }
70
71
        if ($pipeline && function_exists('curl_multi_setopt')) {
72
            curl_multi_setopt($this->mh, CURLMOPT_PIPELINING, true);
73
        }
74
    }
75
76
    /**
77
     * @param array $targets {src: Aspects\HttpGetRequest, dest: OutputFile}
78
     */
79
    public function setTargets(array $targets)
80
    {
81
        $this->targets = $targets;
82
    }
83
84
    public function setupEventLoop()
85
    {
86
        while (count($this->unused) > 0 && count($this->targets) > 0) {
87
            $target = array_pop($this->targets);
88
            $ch = array_pop($this->unused);
89
            $index = (int)$ch;
90
91
            $this->using[$index] = $ch;
92
            $this->runningTargets[$index] = $target;
93
            Factory::getPreEvent($target['src'])->notify();
94
95
            $opts = $target['src']->getCurlOpts();
96
            unset($opts[CURLOPT_ENCODING], $opts[CURLOPT_USERPWD]);
97
            curl_setopt_array($ch, $opts);
98
            curl_setopt($ch, CURLOPT_FILE, $target['dest']->getPointer());
99
            curl_multi_add_handle($this->mh, $ch);
100
        }
101
    }
102
103
    public function wait()
104
    {
105
        $expectRunning = count($this->using);
106
        $running = 0;
107
        $retryCnt = 0;
108
109
        do {
110
            do {
111
                $stat = curl_multi_exec($this->mh, $running);
112
            } while ($stat === CURLM_CALL_MULTI_PERFORM);
113
            if (-1 === curl_multi_select($this->mh, 5)) {
114
                // @codeCoverageIgnoreStart
115
                if ($retryCnt++ > 10) {
116
                    throw new \RuntimeException('curl_multi_select failure');
117
                }
118
                // @codeCoverageIgnoreEnd
119
                usleep(200 * 1000);
120
                continue;
121
            }
122
        } while ($running > 0 && $running >= $expectRunning);
123
124
        do {
125
            $stat = curl_multi_exec($this->mh, $running);
126
        } while ($stat === CURLM_CALL_MULTI_PERFORM);
127
    }
128
129
    public function getFinishedResults()
130
    {
131
        $results = array();
132
        $successCnt = $failureCnt = 0;
133
        do {
134
            if ($raised = curl_multi_info_read($this->mh, $remains)) {
135
                $ch = $raised['handle'];
136
                $errno = curl_errno($ch);
137
                $info = curl_getinfo($ch);
138
                curl_setopt($ch, CURLOPT_FILE, STDOUT);
139
                $index = (int)$ch;
140
                $target = $this->runningTargets[$index];
141
                unset($this->using[$index], $this->runningTargets[$index]);
142
                if (CURLE_OK === $errno && 200 === $info['http_code']) {
143
                    ++$successCnt;
144
                    $target['dest']->setSuccess();
145
                    $results[] = $info['url'];
146
                } else {
147
                    ++$failureCnt;
148
                }
149
                curl_multi_remove_handle($this->mh, $ch);
150
                $this->unused[] = $ch;
151
            }
152
        } while ($remains > 0);
153
154
        return compact('successCnt', 'failureCnt', 'results');
155
    }
156
157
    public function remain()
158
    {
159
        return count($this->targets) > 0;
160
    }
161
}
162