curb 0.8.4 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +522 -0
- data/Rakefile +79 -26
- data/ext/banned.h +32 -0
- data/ext/curb.c +549 -230
- data/ext/curb.h +19 -10
- data/ext/curb_easy.c +1935 -366
- data/ext/curb_easy.h +16 -0
- data/ext/curb_errors.c +115 -18
- data/ext/curb_errors.h +8 -5
- data/ext/curb_macros.h +33 -21
- data/ext/curb_multi.c +1858 -272
- data/ext/curb_multi.h +10 -3
- data/ext/curb_postfield.c +149 -77
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +38 -11
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +353 -35
- data/lib/curb.rb +1 -0
- data/lib/curl/easy.rb +314 -78
- data/lib/curl/multi.rb +134 -28
- data/lib/curl.rb +217 -11
- data/tests/bug_crash_on_debug.rb +14 -28
- data/tests/bug_crash_on_progress.rb +32 -16
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
- data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
- data/tests/bug_follow_redirect_288.rb +83 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
- data/tests/bug_issue102.rb +1 -1
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/bug_multi_segfault.rb +1 -0
- data/tests/bug_raise_on_callback.rb +30 -0
- data/tests/helper.rb +400 -44
- data/tests/leak_trace.rb +237 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl.rb +31 -1
- data/tests/tc_curl_download.rb +12 -7
- data/tests/tc_curl_easy.rb +911 -63
- data/tests/tc_curl_easy_cookielist.rb +277 -0
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_curl_easy_resolve.rb +48 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +855 -53
- data/tests/tc_curl_native_coverage.rb +145 -0
- data/tests/tc_curl_postfield.rb +207 -30
- data/tests/tc_curl_protocols.rb +37 -0
- data/tests/tc_fiber_scheduler.rb +543 -0
- data/tests/tc_ftp_options.rb +39 -0
- data/tests/tc_gc_compact.rb +223 -0
- data/tests/tc_test_server_methods.rb +110 -0
- data/tests/timeout.rb +30 -6
- metadata +59 -36
- data/README +0 -194
data/lib/curl/multi.rb
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
module Curl
|
|
2
|
-
|
|
3
3
|
class Multi
|
|
4
|
+
class DownloadError < RuntimeError
|
|
5
|
+
attr_accessor :errors
|
|
6
|
+
end
|
|
4
7
|
class << self
|
|
5
8
|
# call-seq:
|
|
6
|
-
# Curl::Multi.get('url1','url2','url3','url4','url5', :follow_location => true) do|easy|
|
|
9
|
+
# Curl::Multi.get(['url1','url2','url3','url4','url5'], :follow_location => true) do|easy|
|
|
7
10
|
# easy
|
|
8
11
|
# end
|
|
9
|
-
#
|
|
12
|
+
#
|
|
10
13
|
# Blocking call to fetch multiple url's in parallel.
|
|
11
14
|
def get(urls, easy_options={}, multi_options={}, &blk)
|
|
12
15
|
url_confs = []
|
|
@@ -22,13 +25,13 @@ module Curl
|
|
|
22
25
|
# {:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
|
|
23
26
|
# {:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}],
|
|
24
27
|
# { :follow_location => true, :multipart_form_post => true },
|
|
25
|
-
# {:pipeline =>
|
|
28
|
+
# {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
|
|
26
29
|
# easy_handle_on_request_complete
|
|
27
30
|
# end
|
|
28
|
-
#
|
|
31
|
+
#
|
|
29
32
|
# Blocking call to POST multiple form's in parallel.
|
|
30
|
-
#
|
|
31
|
-
# urls_with_config: is a hash of url's pointing to the postfields to send
|
|
33
|
+
#
|
|
34
|
+
# urls_with_config: is a hash of url's pointing to the postfields to send
|
|
32
35
|
# easy_options: are a set of common options to set on all easy handles
|
|
33
36
|
# multi_options: options to set on the Curl::Multi handle
|
|
34
37
|
#
|
|
@@ -46,13 +49,13 @@ module Curl
|
|
|
46
49
|
# {:url => 'url2', :put_data => IO.read('filepath')},
|
|
47
50
|
# {:url => 'url3', :put_data => "maybe another string or socket?"],
|
|
48
51
|
# {:follow_location => true},
|
|
49
|
-
# {:pipeline =>
|
|
52
|
+
# {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
|
|
50
53
|
# easy_handle_on_request_complete
|
|
51
54
|
# end
|
|
52
|
-
#
|
|
55
|
+
#
|
|
53
56
|
# Blocking call to POST multiple form's in parallel.
|
|
54
|
-
#
|
|
55
|
-
# urls_with_config: is a hash of url's pointing to the postfields to send
|
|
57
|
+
#
|
|
58
|
+
# urls_with_config: is a hash of url's pointing to the postfields to send
|
|
56
59
|
# easy_options: are a set of common options to set on all easy handles
|
|
57
60
|
# multi_options: options to set on the Curl::Multi handle
|
|
58
61
|
#
|
|
@@ -74,12 +77,12 @@ module Curl
|
|
|
74
77
|
# :follow_location => true, :max_redirects => 3 },
|
|
75
78
|
# { :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') },
|
|
76
79
|
# { :url => 'url4', :method => :head }
|
|
77
|
-
# ], {:pipeline =>
|
|
80
|
+
# ], {:pipeline => Curl::CURLPIPE_HTTP1})
|
|
78
81
|
#
|
|
79
82
|
# Blocking call to issue multiple HTTP requests with varying verb's.
|
|
80
83
|
#
|
|
81
84
|
# urls_with_config: is a hash of url's pointing to the easy handle options as well as the special option :method, that can by one of [:get, :post, :put, :delete, :head], when no verb is provided e.g. :method => nil -> GET is used
|
|
82
|
-
# multi_options: options for the multi handle
|
|
85
|
+
# multi_options: options for the multi handle
|
|
83
86
|
# blk: a callback, that yeilds when a handle is completed
|
|
84
87
|
#
|
|
85
88
|
def http(urls_with_config, multi_options={}, &blk)
|
|
@@ -92,7 +95,7 @@ module Curl
|
|
|
92
95
|
|
|
93
96
|
# configure the multi handle
|
|
94
97
|
multi_options.each { |k,v| m.send("#{k}=", v) }
|
|
95
|
-
callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_body,:on_header]
|
|
98
|
+
callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_redirect,:on_missing,:on_body,:on_header]
|
|
96
99
|
|
|
97
100
|
add_free_handle = proc do|conf, easy|
|
|
98
101
|
c = conf.dup # avoid being destructive to input
|
|
@@ -128,23 +131,23 @@ module Curl
|
|
|
128
131
|
|
|
129
132
|
# headers is a special key
|
|
130
133
|
headers.each {|k,v| easy.headers[k] = v } if headers
|
|
131
|
-
|
|
134
|
+
|
|
132
135
|
#
|
|
133
136
|
# use the remaining options as specific configuration to the easy handle
|
|
134
137
|
# bad options should raise an undefined method error
|
|
135
138
|
#
|
|
136
139
|
c.each { |k,v| easy.send("#{k}=",v) }
|
|
137
140
|
|
|
138
|
-
easy.on_complete {|curl
|
|
141
|
+
easy.on_complete {|curl|
|
|
139
142
|
free_handles << curl
|
|
140
|
-
blk.call(curl,
|
|
143
|
+
blk.call(curl,curl.response_code,method) if blk
|
|
141
144
|
}
|
|
142
145
|
m.add(easy)
|
|
143
146
|
end
|
|
144
147
|
|
|
145
148
|
max_connects.times do
|
|
146
149
|
conf = urls_with_config.pop
|
|
147
|
-
add_free_handle.call
|
|
150
|
+
add_free_handle.call(conf, nil) if conf
|
|
148
151
|
break if urls_with_config.empty?
|
|
149
152
|
end
|
|
150
153
|
|
|
@@ -153,21 +156,26 @@ module Curl
|
|
|
153
156
|
if urls_with_config.size > 0 && free_handles.size > 0
|
|
154
157
|
easy = free_handles.pop
|
|
155
158
|
conf = urls_with_config.pop
|
|
156
|
-
add_free_handle.call
|
|
159
|
+
add_free_handle.call(conf, easy) if conf
|
|
157
160
|
end
|
|
158
161
|
end
|
|
159
162
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
begin
|
|
164
|
+
if urls_with_config.empty?
|
|
165
|
+
m.perform
|
|
166
|
+
else
|
|
167
|
+
until urls_with_config.empty?
|
|
168
|
+
m.perform do
|
|
169
|
+
consume_free_handles.call
|
|
170
|
+
end
|
|
165
171
|
consume_free_handles.call
|
|
166
172
|
end
|
|
167
|
-
|
|
173
|
+
free_handles = nil
|
|
168
174
|
end
|
|
169
|
-
|
|
175
|
+
ensure
|
|
176
|
+
m.close
|
|
170
177
|
end
|
|
178
|
+
|
|
171
179
|
end
|
|
172
180
|
|
|
173
181
|
# call-seq:
|
|
@@ -175,7 +183,7 @@ module Curl
|
|
|
175
183
|
# Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
|
|
176
184
|
#
|
|
177
185
|
# will create 2 new files file1.txt and file2.txt
|
|
178
|
-
#
|
|
186
|
+
#
|
|
179
187
|
# 2 files will be opened, and remain open until the call completes
|
|
180
188
|
#
|
|
181
189
|
# when using the :post or :put method, urls should be a hash, including the individual post fields per post
|
|
@@ -240,9 +248,107 @@ module Curl
|
|
|
240
248
|
errors << e
|
|
241
249
|
end
|
|
242
250
|
}
|
|
243
|
-
|
|
251
|
+
if errors.any?
|
|
252
|
+
de = Curl::Multi::DownloadError.new
|
|
253
|
+
de.errors = errors
|
|
254
|
+
raise de
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def cancel!
|
|
260
|
+
requests.each do |_,easy|
|
|
261
|
+
remove(easy)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def idle?
|
|
266
|
+
requests.empty?
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def requests
|
|
270
|
+
@requests ||= {}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def __idle_easy_references
|
|
274
|
+
@__curb_idle_easy_references ||= ObjectSpace::WeakMap.new
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def __register_idle_easy_reference(easy)
|
|
278
|
+
__idle_easy_references[easy] = true
|
|
279
|
+
self
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def __unregister_idle_easy_reference(easy)
|
|
283
|
+
return self unless instance_variable_defined?(:@__curb_idle_easy_references)
|
|
284
|
+
|
|
285
|
+
if @__curb_idle_easy_references.respond_to?(:delete)
|
|
286
|
+
@__curb_idle_easy_references.delete(easy)
|
|
287
|
+
else
|
|
288
|
+
retained_references = ObjectSpace::WeakMap.new
|
|
289
|
+
@__curb_idle_easy_references.each_key do |tracked_easy|
|
|
290
|
+
next if tracked_easy.equal?(easy)
|
|
291
|
+
|
|
292
|
+
retained_references[tracked_easy] = true
|
|
293
|
+
end
|
|
294
|
+
@__curb_idle_easy_references = retained_references
|
|
244
295
|
end
|
|
245
296
|
|
|
297
|
+
self
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def __clear_idle_easy_references
|
|
301
|
+
return unless instance_variable_defined?(:@__curb_idle_easy_references)
|
|
302
|
+
|
|
303
|
+
@__curb_idle_easy_references.keys.each do |easy|
|
|
304
|
+
easy.multi = nil if easy.multi.equal?(self)
|
|
305
|
+
end
|
|
306
|
+
@__curb_idle_easy_references = ObjectSpace::WeakMap.new
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
private :__idle_easy_references, :__register_idle_easy_reference,
|
|
310
|
+
:__unregister_idle_easy_reference, :__clear_idle_easy_references
|
|
311
|
+
|
|
312
|
+
def add(easy)
|
|
313
|
+
return self if requests[easy.object_id]
|
|
314
|
+
# Once a deferred callback exception is pending, Multi#perform is
|
|
315
|
+
# draining existing transfers only and must not start replacement work.
|
|
316
|
+
return self if instance_variable_defined?(:@__curb_deferred_exception)
|
|
317
|
+
_add(easy)
|
|
318
|
+
__unregister_idle_easy_reference(easy)
|
|
319
|
+
requests[easy.object_id] = easy
|
|
320
|
+
self
|
|
246
321
|
end
|
|
322
|
+
|
|
323
|
+
def remove(easy)
|
|
324
|
+
return self if !requests[easy.object_id]
|
|
325
|
+
requests.delete(easy.object_id)
|
|
326
|
+
_remove(easy)
|
|
327
|
+
self
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def close
|
|
331
|
+
__close(true)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def _autoclose
|
|
335
|
+
__close(false)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
private :_autoclose
|
|
339
|
+
|
|
340
|
+
private
|
|
341
|
+
|
|
342
|
+
def __close(permanent)
|
|
343
|
+
requests.values.each {|easy|
|
|
344
|
+
_remove(easy)
|
|
345
|
+
}
|
|
346
|
+
__clear_idle_easy_references if permanent
|
|
347
|
+
@requests = {}
|
|
348
|
+
_close
|
|
349
|
+
_mark_closed if permanent
|
|
350
|
+
self
|
|
351
|
+
end
|
|
352
|
+
|
|
247
353
|
end
|
|
248
354
|
end
|
data/lib/curl.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'curb_core'
|
|
2
3
|
require 'curl/easy'
|
|
3
4
|
require 'curl/multi'
|
|
@@ -5,13 +6,215 @@ require 'uri'
|
|
|
5
6
|
|
|
6
7
|
# expose shortcut methods
|
|
7
8
|
module Curl
|
|
8
|
-
|
|
9
|
+
def self.scheduler_active?
|
|
10
|
+
Fiber.respond_to?(:scheduler) && !Fiber.scheduler.nil?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.deferred_exception_source_id(state)
|
|
14
|
+
return unless state[:multi].instance_variable_defined?(:@__curb_deferred_exception_source_id)
|
|
15
|
+
|
|
16
|
+
state[:multi].instance_variable_get(:@__curb_deferred_exception_source_id)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.scheduler_waiter_blocking_supported?
|
|
20
|
+
scheduler = Fiber.scheduler
|
|
21
|
+
scheduler && scheduler.respond_to?(:block) && scheduler.respond_to?(:unblock)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.wake_scheduler_waiter(waiter)
|
|
25
|
+
fiber = waiter[:fiber]
|
|
26
|
+
scheduler = waiter[:scheduler]
|
|
27
|
+
return unless fiber&.alive? && scheduler&.respond_to?(:unblock)
|
|
28
|
+
|
|
29
|
+
scheduler.unblock(waiter, fiber)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.complete_scheduler_waiter(waiter)
|
|
33
|
+
return if waiter[:done]
|
|
34
|
+
|
|
35
|
+
waiter[:done] = true
|
|
36
|
+
wake_scheduler_waiter(waiter)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.fail_scheduler_waiter(waiter, error)
|
|
40
|
+
return if waiter[:error]
|
|
41
|
+
|
|
42
|
+
waiter[:error] = error
|
|
43
|
+
wake_scheduler_waiter(waiter)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.release_scheduler_error(state, error)
|
|
47
|
+
source_waiter = state[:waiters][deferred_exception_source_id(state)]
|
|
48
|
+
|
|
49
|
+
if source_waiter
|
|
50
|
+
fail_scheduler_waiter(source_waiter, error)
|
|
51
|
+
else
|
|
52
|
+
state[:error] = error
|
|
53
|
+
state[:waiters].each_value { |waiter| wake_scheduler_waiter(waiter) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.block_scheduler_waiter(waiter)
|
|
58
|
+
unless scheduler_waiter_blocking_supported?
|
|
59
|
+
sleep 0
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
waiter[:fiber] = Fiber.current
|
|
64
|
+
waiter[:scheduler] ||= Fiber.scheduler
|
|
65
|
+
return if waiter[:done] || waiter[:error]
|
|
66
|
+
|
|
67
|
+
waiter[:scheduler].block(waiter, nil)
|
|
68
|
+
ensure
|
|
69
|
+
waiter[:fiber] = nil if waiter[:fiber].equal?(Fiber.current)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.scheduler_yield
|
|
73
|
+
scheduler = Fiber.scheduler
|
|
74
|
+
|
|
75
|
+
if scheduler&.respond_to?(:kernel_sleep)
|
|
76
|
+
scheduler.kernel_sleep(0)
|
|
77
|
+
else
|
|
78
|
+
sleep 0
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.release_scheduler_waiters(state)
|
|
83
|
+
source_id = deferred_exception_source_id(state)
|
|
84
|
+
|
|
85
|
+
state[:waiters].each do |easy_id, waiter|
|
|
86
|
+
next if source_id == easy_id
|
|
87
|
+
|
|
88
|
+
complete_scheduler_waiter(waiter) if waiter[:completed]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.perform_with_scheduler(easy)
|
|
93
|
+
state = scheduler_state
|
|
94
|
+
waiter = {completed: false, done: false, error: nil, fiber: nil, scheduler: Fiber.scheduler}
|
|
95
|
+
state[:waiters][easy.object_id] = waiter
|
|
96
|
+
previous_complete = easy.on_complete do |completed_easy|
|
|
97
|
+
previous_complete.call(completed_easy) if previous_complete
|
|
98
|
+
waiter[:completed] = true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
state[:pending] << easy
|
|
102
|
+
ensure_scheduler_driver(state)
|
|
103
|
+
|
|
104
|
+
until waiter[:done]
|
|
105
|
+
raise waiter[:error] if waiter[:error]
|
|
106
|
+
raise state[:error] if state[:error]
|
|
107
|
+
block_scheduler_waiter(waiter)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
while state[:driver_running] && state[:pending].empty? &&
|
|
111
|
+
state[:waiters].length == 1 && state[:waiters].key?(easy.object_id)
|
|
112
|
+
scheduler_yield
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
true
|
|
116
|
+
ensure
|
|
117
|
+
state[:waiters].delete(easy.object_id) if defined?(state) && state[:waiters]
|
|
118
|
+
if defined?(previous_complete)
|
|
119
|
+
if previous_complete
|
|
120
|
+
easy.on_complete(&previous_complete)
|
|
121
|
+
else
|
|
122
|
+
easy.on_complete
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.scheduler_state
|
|
128
|
+
Thread.current.thread_variable_get(:curb_scheduler_state) || begin
|
|
129
|
+
state = {
|
|
130
|
+
multi: Curl::Multi.new,
|
|
131
|
+
pending: [],
|
|
132
|
+
driver_running: false,
|
|
133
|
+
error: nil,
|
|
134
|
+
waiters: {},
|
|
135
|
+
}
|
|
136
|
+
Thread.current.thread_variable_set(:curb_scheduler_state, state)
|
|
137
|
+
state
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.ensure_scheduler_driver(state)
|
|
142
|
+
return if state[:driver_running]
|
|
143
|
+
|
|
144
|
+
state[:driver_running] = true
|
|
145
|
+
state[:error] = nil
|
|
146
|
+
|
|
147
|
+
runner = proc do
|
|
148
|
+
begin
|
|
149
|
+
# Give sibling fibers a chance to enqueue work so the shared multi can
|
|
150
|
+
# batch scheduler-driven Easy#perform calls together.
|
|
151
|
+
pending_count = -1
|
|
152
|
+
until pending_count == state[:pending].size
|
|
153
|
+
pending_count = state[:pending].size
|
|
154
|
+
scheduler_yield
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
loop do
|
|
158
|
+
drain_scheduler_pending(state)
|
|
159
|
+
break if state[:multi].idle?
|
|
160
|
+
|
|
161
|
+
begin
|
|
162
|
+
state[:multi].perform do
|
|
163
|
+
drain_scheduler_pending(state)
|
|
164
|
+
release_scheduler_waiters(state)
|
|
165
|
+
scheduler_yield
|
|
166
|
+
end
|
|
167
|
+
ensure
|
|
168
|
+
# Release any siblings that completed just before a deferred
|
|
169
|
+
# callback exception is re-raised.
|
|
170
|
+
release_scheduler_waiters(state)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
rescue => e
|
|
174
|
+
release_scheduler_waiters(state)
|
|
175
|
+
release_scheduler_error(state, e)
|
|
176
|
+
ensure
|
|
177
|
+
state[:driver_running] = false
|
|
178
|
+
ensure_scheduler_driver(state) if state[:error].nil? && !state[:pending].empty?
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
if Fiber.respond_to?(:schedule)
|
|
183
|
+
Fiber.schedule(&runner)
|
|
184
|
+
else
|
|
185
|
+
Fiber.new(blocking: false, &runner).resume
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def self.drain_scheduler_pending(state)
|
|
190
|
+
pending = state[:pending]
|
|
191
|
+
until pending.empty?
|
|
192
|
+
easy = pending.first
|
|
193
|
+
|
|
194
|
+
break if state[:multi].instance_variable_defined?(:@__curb_deferred_exception)
|
|
195
|
+
|
|
196
|
+
state[:multi].add(easy)
|
|
197
|
+
break unless state[:multi].requests.key?(easy.object_id)
|
|
198
|
+
|
|
199
|
+
pending.shift
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
9
203
|
def self.http(verb, url, post_body=nil, put_data=nil, &block)
|
|
10
|
-
|
|
204
|
+
if Thread.current[:curb_curl_yielding]
|
|
205
|
+
handle = Curl::Easy.new # we can't reuse this
|
|
206
|
+
else
|
|
207
|
+
handle = Thread.current[:curb_curl] ||= Curl::Easy.new
|
|
208
|
+
handle.reset
|
|
209
|
+
end
|
|
11
210
|
handle.url = url
|
|
12
211
|
handle.post_body = post_body if post_body
|
|
13
212
|
handle.put_data = put_data if put_data
|
|
14
|
-
|
|
213
|
+
if block_given?
|
|
214
|
+
Thread.current[:curb_curl_yielding] = true
|
|
215
|
+
yield handle
|
|
216
|
+
Thread.current[:curb_curl_yielding] = false
|
|
217
|
+
end
|
|
15
218
|
handle.http(verb)
|
|
16
219
|
handle
|
|
17
220
|
end
|
|
@@ -45,18 +248,21 @@ module Curl
|
|
|
45
248
|
end
|
|
46
249
|
|
|
47
250
|
def self.urlalize(url, params={})
|
|
48
|
-
|
|
49
|
-
if
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
251
|
+
uri = URI(url)
|
|
252
|
+
# early return if we didn't specify any extra params
|
|
253
|
+
return uri.to_s if (params || {}).empty?
|
|
254
|
+
|
|
255
|
+
params_query = URI.encode_www_form(params || {})
|
|
256
|
+
uri.query = [uri.query.to_s, params_query].reject(&:empty?).join('&')
|
|
257
|
+
uri.to_s
|
|
56
258
|
end
|
|
57
259
|
|
|
58
260
|
def self.postalize(params={})
|
|
59
261
|
params.respond_to?(:map) ? URI.encode_www_form(params) : (params.respond_to?(:to_s) ? params.to_s : params)
|
|
60
262
|
end
|
|
61
263
|
|
|
264
|
+
def self.reset
|
|
265
|
+
Thread.current[:curb_curl] = Curl::Easy.new
|
|
266
|
+
end
|
|
267
|
+
|
|
62
268
|
end
|
data/tests/bug_crash_on_debug.rb
CHANGED
|
@@ -1,37 +1,23 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
2
|
|
|
3
|
-
require 'webrick'
|
|
4
|
-
class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
|
|
5
|
-
class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
|
|
6
|
-
|
|
7
|
-
require 'curl'
|
|
8
|
-
|
|
9
3
|
class BugCrashOnDebug < Test::Unit::TestCase
|
|
4
|
+
include BugTestServerSetupTeardown
|
|
10
5
|
|
|
11
6
|
def test_on_debug
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
c.on_debug do|x|
|
|
24
|
-
puts x.inspect
|
|
25
|
-
raise "error" # this will get swallowed
|
|
7
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
|
|
8
|
+
did_raise = false
|
|
9
|
+
did_call = false
|
|
10
|
+
begin
|
|
11
|
+
c.on_success do|x|
|
|
12
|
+
did_call = true
|
|
13
|
+
raise "error" # this will get swallowed
|
|
14
|
+
end
|
|
15
|
+
c.perform
|
|
16
|
+
rescue => e
|
|
17
|
+
did_raise = true
|
|
26
18
|
end
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
ensure
|
|
30
|
-
puts 'd'
|
|
31
|
-
server.shutdown
|
|
32
|
-
puts 'e'
|
|
33
|
-
puts thread.exit
|
|
34
|
-
puts 'f'
|
|
19
|
+
assert did_raise
|
|
20
|
+
assert did_call
|
|
35
21
|
end
|
|
36
22
|
|
|
37
23
|
end
|
|
@@ -1,33 +1,49 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
-
require 'webrick'
|
|
3
|
-
class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
|
|
4
|
-
class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
|
|
5
2
|
|
|
6
3
|
class BugCrashOnDebug < Test::Unit::TestCase
|
|
7
|
-
|
|
8
|
-
def test_on_debug
|
|
9
|
-
server = WEBrick::HTTPServer.new( :Port => 9999 )
|
|
10
|
-
server.mount_proc("/test") do|req,res|
|
|
11
|
-
res.body = "hi"
|
|
12
|
-
res['Content-Type'] = "text/html"
|
|
13
|
-
end
|
|
4
|
+
include BugTestServerSetupTeardown
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
def test_on_progress_raise
|
|
7
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
|
|
8
|
+
c.on_progress do|x|
|
|
9
|
+
raise "error"
|
|
17
10
|
end
|
|
11
|
+
c.perform
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
assert false, "should not reach this point"
|
|
14
|
+
|
|
15
|
+
rescue => e
|
|
16
|
+
assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
|
|
17
|
+
c.close
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_on_progress_abort
|
|
21
|
+
# see: https://github.com/taf2/curb/issues/192,
|
|
22
|
+
# to pass:
|
|
23
|
+
#
|
|
24
|
+
# c = Curl::Easy.new('http://127.0.0.1:9999/test')
|
|
25
|
+
# c.on_progress do|x|
|
|
26
|
+
# puts "we're in the progress callback"
|
|
27
|
+
# false
|
|
28
|
+
# end
|
|
29
|
+
# c.perform
|
|
30
|
+
#
|
|
31
|
+
# notice no return keyword
|
|
32
|
+
#
|
|
33
|
+
c = Curl::Easy.new("http://127.0.0.1:#{@port}/test")
|
|
34
|
+
did_progress = false
|
|
20
35
|
c.on_progress do|x|
|
|
21
|
-
|
|
36
|
+
did_progress = true
|
|
37
|
+
return false
|
|
22
38
|
end
|
|
23
39
|
c.perform
|
|
40
|
+
assert did_progress
|
|
24
41
|
|
|
25
42
|
assert false, "should not reach this point"
|
|
26
43
|
|
|
27
44
|
rescue => e
|
|
28
45
|
assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
|
|
29
46
|
c.close
|
|
30
|
-
ensure
|
|
31
|
-
server.shutdown
|
|
32
47
|
end
|
|
48
|
+
|
|
33
49
|
end
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
-
require 'webrick'
|
|
3
|
-
class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
|
|
4
|
-
class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
|
|
5
2
|
|
|
6
3
|
class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
include BugTestServerSetupTeardown
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
@port = unused_local_port
|
|
8
|
+
@response_proc = lambda do|res|
|
|
10
9
|
sleep 0.5
|
|
11
10
|
res.body = "hi"
|
|
12
11
|
res['Content-Type'] = "text/html"
|
|
13
12
|
end
|
|
13
|
+
super
|
|
14
|
+
end
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
srv.start
|
|
17
|
-
end
|
|
18
|
-
|
|
16
|
+
def test_bug
|
|
19
17
|
threads = []
|
|
20
18
|
timer = Time.now
|
|
21
19
|
|
|
22
20
|
5.times do |i|
|
|
23
21
|
t = Thread.new do
|
|
24
|
-
c = Curl::Easy.perform(
|
|
22
|
+
c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
|
|
25
23
|
c.header_str
|
|
26
24
|
end
|
|
27
25
|
threads << t
|
|
@@ -37,7 +35,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
|
|
37
35
|
timer = Time.now
|
|
38
36
|
single_responses = []
|
|
39
37
|
5.times do |i|
|
|
40
|
-
c = Curl::Easy.perform(
|
|
38
|
+
c = Curl::Easy.perform("http://127.0.0.1:#{@port}/test")
|
|
41
39
|
single_responses << c.header_str
|
|
42
40
|
end
|
|
43
41
|
|
|
@@ -45,8 +43,5 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
|
|
45
43
|
puts "requested in #{single_time}"
|
|
46
44
|
|
|
47
45
|
assert single_time > multi_time
|
|
48
|
-
|
|
49
|
-
server.shutdown
|
|
50
|
-
thread.join
|
|
51
46
|
end
|
|
52
47
|
end
|