1
1
local curl = require " cURL"
2
2
local uv = require " lluv"
3
+ local ut = require " lluv.utils"
3
4
4
5
local fprintf = function (f , ...) f :write ((string.format (... ))) end
5
6
local printf = function (...) fprintf (io.stdout , ... ) end
6
7
7
8
local stderr = io.stderr
8
9
9
- local timeout , curl_handle
10
+ local trace = false
11
+
12
+ trace = trace and print or function () end
10
13
11
14
local ACTION_NAMES = {
12
15
[curl .POLL_IN ] = " POLL_IN" ;
@@ -15,141 +18,240 @@ local ACTION_NAMES = {
15
18
[curl .POLL_NONE ] = " POLL_NONE" ;
16
19
[curl .POLL_REMOVE ] = " POLL_REMOVE" ;
17
20
}
21
+
18
22
local EVENT_NAMES = {
19
23
[ uv .READABLE ] = " READABLE" ;
20
24
[ uv .WRITABLE ] = " WRITABLE" ;
21
25
[ uv .READABLE + uv .WRITABLE ] = " READABLE + WRITABLE" ;
22
26
}
27
+
23
28
local FLAGS = {
24
- [ uv .READABLE ] = curl .CSELECT_IN ;
25
- [ uv .WRITABLE ] = curl .CSELECT_OUT ;
29
+ [ uv .READABLE ] = curl .CSELECT_IN ;
30
+ [ uv .WRITABLE ] = curl .CSELECT_OUT ;
26
31
[ uv .READABLE + uv .WRITABLE ] = curl .CSELECT_IN + curl .CSELECT_OUT ;
32
+ }
27
33
34
+ local POLL_IO_FLAGS = {
35
+ [ curl .POLL_IN ] = uv .READABLE ;
36
+ [ curl .POLL_OUT ] = uv .WRITABLE ;
37
+ [ curl .POLL_INOUT ] = uv .READABLE + uv .WRITABLE ;
28
38
}
29
39
30
- local trace = true
40
+ local Context = ut . class () do
31
41
32
- trace = trace and print or function () end
42
+ function Context :__init (fd )
43
+ self ._fd = assert (fd )
44
+ self ._poll = uv .poll_socket (fd )
45
+ self ._poll .data = {context = self }
46
+
47
+ assert (self ._poll :fileno () == fd )
48
+
49
+ return self
50
+ end
33
51
34
- local CONTEXT = {}
52
+ function Context :close ()
53
+ if not self ._poll then return end
54
+ self ._poll .data = nil
55
+ self ._poll :close ()
56
+ self ._poll , self ._fd = nil
57
+ end
58
+
59
+ function Context :poll (...)
60
+ self ._poll :start (... )
61
+ end
62
+
63
+ function Context :fileno ()
64
+ return self ._fd
65
+ end
35
66
36
- function create_curl_context (sockfd )
37
- local context = {
38
- sockfd = sockfd ;
39
- poll_handle = uv .poll_socket (sockfd );
40
- }
41
- context .poll_handle .data = context
42
-
43
- return context
44
67
end
45
68
46
- function destroy_curl_context (context )
47
- context .poll_handle :close ()
48
- end
69
+ -- Number of parallel request
70
+ local MAX_REQUESTS
71
+ local timer , multi
72
+ local qtask = ut .Queue .new () -- wait tasks
73
+ local qfree = ut .Queue .new () -- avaliable easy handles
74
+ local qeasy = {} -- all easy handles
49
75
50
- function add_download ( url , num )
76
+ local function on_begin ( handle , url , num )
51
77
local filename = tostring (num ) .. " .download"
52
78
local file = io.open (filename , " w" )
53
79
if not file then
54
80
fprintf (stderr , " Error opening %s\n " , filename )
55
81
return
56
82
end
83
+ handle .data .file = file
84
+ handle :setopt_writefunction (file )
57
85
58
- local handle = curl .easy {
59
- url = url ;
60
- writefunction = file ;
61
- }
86
+ fprintf (stderr , " Added download %s -> %s\n " , url , filename );
87
+ return true
88
+ end
62
89
63
- handle .data = file
90
+ local function on_end (handle , err , url )
91
+ handle .data .file :close ()
92
+ handle .data .file = nil
64
93
65
- curl_handle :add_handle (handle )
66
- fprintf (stderr , " Added download %s -> %s\n " , url , filename );
94
+ if err then
95
+ printf (" %s ERROR - %s\n " , url , tostring (err ));
96
+ else
97
+ printf (" %s DONE\n " , url );
98
+ end
67
99
end
68
100
69
- function check_multi_info ()
70
- while true do
71
- local easy , ok , err = curl_handle :info_read (true )
72
- if not easy then curl_handle :close () error (err ) end
73
- if easy == 0 then break end
101
+ local function cleanup ()
102
+ timer :close ()
74
103
75
- local context = CONTEXT [e ]
76
- if context then destroy_curl_context (context ) end
77
- local file = assert (easy .data )
78
- file :close ()
79
- local done_url = easy :getinfo_effective_url ()
104
+ for i , easy in ipairs (qeasy ) do
105
+ multi :remove_handle (easy )
80
106
easy :close ()
81
- if ok then
82
- printf (" %s DONE\n " , done_url );
83
- elseif data == " error" then
84
- printf (" %s ERROR - %s\n " , done_url , tostring (err ));
85
- end
86
107
end
108
+
109
+ multi :close ()
87
110
end
88
111
89
- function curl_perform (handle , err , events )
90
- -- calls by libuv --
91
- trace (" UV::POLL" , handle , err , EVENT_NAMES [events ] or events )
112
+ local proceed_queue , add_download do
92
113
93
- local flags = assert (FLAGS [events ], (" unknown event:" .. events ))
114
+ proceed_queue = function ()
115
+ while true do
116
+ if qtask :empty () then return end
117
+
118
+ if qfree :empty () then
119
+ if # qeasy < MAX_REQUESTS then
120
+ local easy = assert (curl .easy ())
121
+ qeasy [# qeasy + 1 ] = easy
122
+ qfree :push (easy )
123
+ else
124
+ return
125
+ end
126
+ end
94
127
95
- context = handle .data
128
+ local task = assert (qtask :pop ())
129
+ local url , num = task [1 ], task [2 ]
96
130
97
- curl_handle : socket_action ( context . sockfd , flags )
131
+ local handle = assert ( qfree : pop () )
98
132
99
- check_multi_info ()
133
+ handle :setopt {
134
+ url = url ;
135
+ fresh_connect = true ;
136
+ forbid_reuse = true ;
137
+ }
138
+
139
+ handle .data = {}
140
+
141
+ if on_begin (handle , url , num ) then
142
+ multi :add_handle (handle )
143
+ else
144
+ handle :reset ().data = nil
145
+ qfree :push (handle )
146
+ end
147
+ end
100
148
end
101
149
102
- function on_timeout (timer )
103
- -- calls by libuv --
104
- trace (" UV::TIMEOUT" , timer )
150
+ add_download = function (url , num )
151
+ qtask :push {url , num }
105
152
106
- local running_handles , err = curl_handle :socket_action ()
153
+ proceed_queue ()
154
+ end
107
155
108
- check_multi_info ()
109
156
end
110
157
111
- function start_timeout (timeout_ms )
158
+ local on_libuv_poll , on_libuv_timeout
159
+
160
+ local on_curl_timeout , on_curl_action do
161
+
162
+ on_curl_timeout = function (ms )
112
163
-- calls by curl --
113
- trace (" CURL::TIMEOUT" , timeout_ms )
164
+ trace (" CURL::TIMEOUT" , ms )
114
165
115
- -- 0 means directly call socket_action, but we'll do it in a bit
116
- if timeout_ms <= 0 then timeout_ms = 1 end
166
+ if ms <= 0 then ms = 1 end
117
167
118
- timeout : stop (): start (timeout_ms , 0 , on_timeout )
168
+ timer : start (ms , 0 , on_libuv_timeout )
119
169
end
120
170
121
- function handle_socket (easy , s , action )
171
+ on_curl_action = function (easy , fd , action )
122
172
local ok , err = pcall (function ()
123
- -- calls by curl --
124
173
trace (" CURL::SOCKET" , easy , s , ACTION_NAMES [action ] or action )
125
174
126
- local curl_context = CONTEXT [easy ] or create_curl_context (s )
127
- CONTEXT [easy ] = curl_context
128
-
129
- assert (curl_context .sockfd == s )
175
+ local context = easy .data .context
130
176
131
- if action == curl .POLL_IN then
132
- curl_context .poll_handle :start (uv .READABLE , curl_perform )
133
- elseif action == curl .POLL_OUT then
134
- curl_context .poll_handle :start (uv .WRITABLE , curl_perform )
177
+ local flag = POLL_IO_FLAGS [action ]
178
+ if flag then
179
+ if not context then
180
+ context = Context .new (fd )
181
+ easy .data .context = context
182
+ end
183
+ context :poll (flag , on_libuv_poll )
135
184
elseif action == curl .POLL_REMOVE then
136
- CONTEXT [easy ] = nil
137
- destroy_curl_context (curl_context )
185
+ if context then
186
+ easy .data .context = nil
187
+ context :close ()
188
+ end
138
189
end
139
190
end )
191
+
140
192
if not ok then uv .defer (function () error (err ) end ) end
141
193
end
142
194
143
- timeout = uv . timer ()
195
+ end
144
196
145
- curl_handle = curl .multi {
146
- socketfunction = handle_socket ;
147
- timerfunction = start_timeout ;
148
- }
197
+ -- on_libuv_poll, on_libuv_timeout
198
+ do
199
+
200
+ local curl_check_multi_info = function ()
201
+ while true do
202
+ local easy , ok , err = multi :info_read (true )
203
+
204
+ if not easy then
205
+ multi :close ()
206
+ error (err )
207
+ end
208
+
209
+ if easy == 0 then break end
149
210
150
- curl_handle = curl .multi {
151
- socketfunction = handle_socket ;
152
- timerfunction = start_timeout ;
211
+ local done_url = easy :getinfo_effective_url ()
212
+
213
+ local context = easy .data .context
214
+ if context then context :close () end
215
+ easy .data .context = nil
216
+
217
+ if ok then on_end (easy , nil , done_url ) else on_end (easy , err , done_url ) end
218
+
219
+ easy :reset ().data = nil
220
+ qfree :push (easy )
221
+ end
222
+
223
+ proceed_queue ()
224
+ end
225
+
226
+ on_libuv_poll = function (handle , err , events )
227
+ trace (" UV::POLL" , handle , err , EVENT_NAMES [events ] or events )
228
+
229
+ local flags = assert (FLAGS [events ], (" unknown event:" .. events ))
230
+
231
+ context = handle .data .context
232
+
233
+ multi :socket_action (context :fileno (), flags )
234
+
235
+ curl_check_multi_info ()
236
+ end
237
+
238
+ on_libuv_timeout = function (timer )
239
+ trace (" UV::TIMEOUT" , timer )
240
+
241
+ local running_handles , err = multi :socket_action ()
242
+
243
+ curl_check_multi_info ()
244
+ end
245
+
246
+ end
247
+
248
+ MAX_REQUESTS = 64
249
+
250
+ timer = uv .timer ()
251
+
252
+ multi = curl .multi {
253
+ timerfunction = on_curl_timeout ;
254
+ socketfunction = on_curl_action ;
153
255
}
154
256
155
257
for i = 1 , math.huge do
@@ -158,4 +260,6 @@ for i = 1, math.huge do
158
260
add_download (url , i )
159
261
end
160
262
161
- uv .run (loop , UV_RUN_DEFAULT )
263
+ uv .run ()
264
+
265
+ cleanup ()
0 commit comments