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/tests/leak_trace.rb
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'objspace'
|
|
6
|
+
require 'optparse'
|
|
7
|
+
require 'rbconfig'
|
|
8
|
+
require 'tmpdir'
|
|
9
|
+
|
|
10
|
+
TOPDIR = File.expand_path('..', __dir__)
|
|
11
|
+
LIBDIR = File.join(TOPDIR, 'lib')
|
|
12
|
+
EXTDIR = File.join(TOPDIR, 'ext')
|
|
13
|
+
$LOAD_PATH.unshift(LIBDIR)
|
|
14
|
+
$LOAD_PATH.unshift(EXTDIR)
|
|
15
|
+
|
|
16
|
+
require 'curb'
|
|
17
|
+
|
|
18
|
+
module LeakTrace
|
|
19
|
+
Record = Struct.new(:identifier, :created_location, :closed, keyword_init: true)
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def install_multi_trace!
|
|
24
|
+
return if @multi_trace_installed
|
|
25
|
+
|
|
26
|
+
@multi_trace_installed = true
|
|
27
|
+
@multi_records = {}
|
|
28
|
+
|
|
29
|
+
multi_singleton = class << Curl::Multi
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
multi_singleton.alias_method(:__leak_trace_new, :new)
|
|
34
|
+
multi_singleton.define_method(:new) do |*args, **kwargs, &block|
|
|
35
|
+
multi = if kwargs.empty?
|
|
36
|
+
__leak_trace_new(*args, &block)
|
|
37
|
+
else
|
|
38
|
+
__leak_trace_new(*args, **kwargs, &block)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
LeakTrace.multi_records[multi.object_id] ||= Record.new(
|
|
42
|
+
identifier: multi.object_id,
|
|
43
|
+
created_location: caller_locations(1, 6).map(&:to_s),
|
|
44
|
+
closed: false
|
|
45
|
+
)
|
|
46
|
+
multi
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Curl::Multi.class_eval do
|
|
50
|
+
alias_method :__leak_trace__close, :_close
|
|
51
|
+
|
|
52
|
+
def _close(*args, **kwargs, &block)
|
|
53
|
+
record = LeakTrace.multi_records[object_id]
|
|
54
|
+
record.closed = true if record
|
|
55
|
+
|
|
56
|
+
if kwargs.empty?
|
|
57
|
+
__leak_trace__close(*args, &block)
|
|
58
|
+
else
|
|
59
|
+
__leak_trace__close(*args, **kwargs, &block)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def multi_records
|
|
66
|
+
@multi_records ||= {}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def live_multis
|
|
70
|
+
ObjectSpace.each_object(Curl::Multi).to_a
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def fixture_path
|
|
74
|
+
File.expand_path('helper.rb', __dir__)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def fixture_url
|
|
78
|
+
path = fixture_path.tr('\\', '/')
|
|
79
|
+
"file://#{path.start_with?('/') ? '' : '/'}#{path}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def full_gc(compact: false)
|
|
83
|
+
GC.start(full_mark: true, immediate_sweep: true)
|
|
84
|
+
GC.compact if compact && GC.respond_to?(:compact)
|
|
85
|
+
GC.start(full_mark: true, immediate_sweep: true)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def close_multi(multi)
|
|
89
|
+
return unless multi
|
|
90
|
+
|
|
91
|
+
multi.instance_variable_set(:@requests, {}) if multi.respond_to?(:instance_variable_set)
|
|
92
|
+
multi._close
|
|
93
|
+
rescue StandardError
|
|
94
|
+
multi.close rescue nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def multi_perform(iterations:, handles:, close:, compact:)
|
|
98
|
+
iterations.times do
|
|
99
|
+
multi = Curl::Multi.new
|
|
100
|
+
handles.times { multi.add(Curl::Easy.new(fixture_url)) }
|
|
101
|
+
multi.perform
|
|
102
|
+
ensure
|
|
103
|
+
close_multi(multi) if close
|
|
104
|
+
multi = nil
|
|
105
|
+
full_gc(compact: compact)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def multi_gc_cleanup(iterations:, handles:, compact:)
|
|
110
|
+
iterations.times do
|
|
111
|
+
multi = Curl::Multi.new
|
|
112
|
+
handles.times { multi.add(Curl::Easy.new(fixture_url)) }
|
|
113
|
+
multi.perform
|
|
114
|
+
multi = nil
|
|
115
|
+
full_gc(compact: compact)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def easy_perform(iterations:, compact:)
|
|
120
|
+
iterations.times do
|
|
121
|
+
easy = Curl::Easy.new(fixture_url)
|
|
122
|
+
easy.perform
|
|
123
|
+
easy.close
|
|
124
|
+
easy = nil
|
|
125
|
+
full_gc(compact: compact)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def download(iterations:, compact:)
|
|
130
|
+
iterations.times do |i|
|
|
131
|
+
Dir.mktmpdir("curb-leak-trace-#{i}") do |dir|
|
|
132
|
+
Curl::Easy.download(fixture_url, File.join(dir, 'helper-copy.rb'))
|
|
133
|
+
end
|
|
134
|
+
full_gc(compact: compact)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def report(io:, verbose:)
|
|
139
|
+
live = live_multis
|
|
140
|
+
tracked_live = live.filter_map { |multi| multi_records[multi.object_id] }
|
|
141
|
+
|
|
142
|
+
io.puts "scanned live Curl::Multi objects: #{live.size}"
|
|
143
|
+
io.puts "tracked Curl::Multi allocations: #{multi_records.size}"
|
|
144
|
+
io.puts "tracked Curl::Multi closes: #{multi_records.count { |_id, record| record.closed }}"
|
|
145
|
+
|
|
146
|
+
grouped = tracked_live.group_by { |record| record.created_location.first || '<unknown>' }
|
|
147
|
+
grouped.sort_by { |location, records| [-records.size, location] }.each do |location, records|
|
|
148
|
+
io.puts "live #{records.size}: #{location}"
|
|
149
|
+
next unless verbose
|
|
150
|
+
|
|
151
|
+
records.each do |record|
|
|
152
|
+
io.puts " object_id=#{record.identifier}"
|
|
153
|
+
record.created_location.drop(1).each do |line|
|
|
154
|
+
io.puts " #{line}"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
options = {
|
|
162
|
+
scenario: 'multi_perform',
|
|
163
|
+
iterations: 25,
|
|
164
|
+
handles: 3,
|
|
165
|
+
close: true,
|
|
166
|
+
compact: false,
|
|
167
|
+
verbose: false,
|
|
168
|
+
fail_on_live: false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
OptionParser.new do |opts|
|
|
172
|
+
opts.banner = 'Usage: ruby tests/leak_trace.rb [options]'
|
|
173
|
+
|
|
174
|
+
opts.on('--scenario NAME', 'multi_perform, multi_gc_cleanup, easy_perform, download') do |value|
|
|
175
|
+
options[:scenario] = value
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
opts.on('--iterations N', Integer, 'number of iterations to run') do |value|
|
|
179
|
+
options[:iterations] = value
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
opts.on('--handles N', Integer, 'number of easy handles per multi iteration') do |value|
|
|
183
|
+
options[:handles] = value
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
opts.on('--[no-]close', 'explicitly close multi handles when the scenario finishes') do |value|
|
|
187
|
+
options[:close] = value
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
opts.on('--compact', 'run GC.compact between iterations when available') do
|
|
191
|
+
options[:compact] = true
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
opts.on('--verbose', 'print full creation stacks for live multi handles') do
|
|
195
|
+
options[:verbose] = true
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
opts.on('--fail-on-live', 'exit non-zero when any live Curl::Multi objects remain') do
|
|
199
|
+
options[:fail_on_live] = true
|
|
200
|
+
end
|
|
201
|
+
end.parse!
|
|
202
|
+
|
|
203
|
+
LeakTrace.install_multi_trace!
|
|
204
|
+
|
|
205
|
+
case options[:scenario]
|
|
206
|
+
when 'multi_perform'
|
|
207
|
+
LeakTrace.multi_perform(
|
|
208
|
+
iterations: options[:iterations],
|
|
209
|
+
handles: options[:handles],
|
|
210
|
+
close: options[:close],
|
|
211
|
+
compact: options[:compact]
|
|
212
|
+
)
|
|
213
|
+
when 'multi_gc_cleanup'
|
|
214
|
+
LeakTrace.multi_gc_cleanup(
|
|
215
|
+
iterations: options[:iterations],
|
|
216
|
+
handles: options[:handles],
|
|
217
|
+
compact: options[:compact]
|
|
218
|
+
)
|
|
219
|
+
when 'easy_perform'
|
|
220
|
+
LeakTrace.easy_perform(
|
|
221
|
+
iterations: options[:iterations],
|
|
222
|
+
compact: options[:compact]
|
|
223
|
+
)
|
|
224
|
+
when 'download'
|
|
225
|
+
LeakTrace.download(
|
|
226
|
+
iterations: options[:iterations],
|
|
227
|
+
compact: options[:compact]
|
|
228
|
+
)
|
|
229
|
+
else
|
|
230
|
+
warn "unknown scenario: #{options[:scenario]}"
|
|
231
|
+
exit 64
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
LeakTrace.full_gc(compact: options[:compact])
|
|
235
|
+
LeakTrace.report(io: $stdout, verbose: options[:verbose])
|
|
236
|
+
|
|
237
|
+
exit 1 if options[:fail_on_live] && LeakTrace.live_multis.any?
|
data/tests/mem_check.rb
CHANGED
|
@@ -6,6 +6,9 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
|
6
6
|
# Run some tests to measure the memory usage of curb, these tests require fork and ps
|
|
7
7
|
#
|
|
8
8
|
class TestCurbMemory < Test::Unit::TestCase
|
|
9
|
+
def setup
|
|
10
|
+
omit('Memory fork/ps tests not supported on Windows') if WINDOWS || NO_FORK
|
|
11
|
+
end
|
|
9
12
|
|
|
10
13
|
def test_easy_memory
|
|
11
14
|
easy_avg, easy_std = measure_object_memory(Curl::Easy)
|
data/tests/tc_curl.rb
CHANGED
|
@@ -31,7 +31,37 @@ class TestCurl < Test::Unit::TestCase
|
|
|
31
31
|
assert_equal "OPTIONSfoo=bar", curl.body_str
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
def test_urlalize_without_extra_params
|
|
35
|
+
url_no_params = 'http://localhost/test'
|
|
36
|
+
url_with_params = 'http://localhost/test?a=1'
|
|
37
|
+
|
|
38
|
+
assert_equal(url_no_params, Curl.urlalize(url_no_params))
|
|
39
|
+
assert_equal(url_with_params, Curl.urlalize(url_with_params))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_urlalize_with_nil_as_params
|
|
43
|
+
url = 'http://localhost/test'
|
|
44
|
+
assert_equal(url, Curl.urlalize(url, nil))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_urlalize_with_extra_params
|
|
48
|
+
url_no_params = 'http://localhost/test'
|
|
49
|
+
url_with_params = 'http://localhost/test?a=1'
|
|
50
|
+
extra_params = { :b => 2 }
|
|
51
|
+
|
|
52
|
+
expected_url_no_params = 'http://localhost/test?b=2'
|
|
53
|
+
expected_url_with_params = 'http://localhost/test?a=1&b=2'
|
|
54
|
+
|
|
55
|
+
assert_equal(expected_url_no_params, Curl.urlalize(url_no_params, extra_params))
|
|
56
|
+
assert_equal(expected_url_with_params, Curl.urlalize(url_with_params, extra_params))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_urlalize_does_not_strip_trailing_?
|
|
60
|
+
url_empty_params = 'http://localhost/test?'
|
|
61
|
+
assert_equal(url_empty_params, Curl.urlalize(url_empty_params))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
include TestServerMethods
|
|
35
65
|
|
|
36
66
|
def setup
|
|
37
67
|
server_setup
|
data/tests/tc_curl_download.rb
CHANGED
|
@@ -6,12 +6,12 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
6
6
|
def setup
|
|
7
7
|
server_setup
|
|
8
8
|
end
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
def test_download_url_to_file_via_string
|
|
11
11
|
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
12
12
|
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Curl::Easy.download(dl_url, dl_path)
|
|
15
15
|
assert File.exist?(dl_path)
|
|
16
16
|
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
17
17
|
ensure
|
|
@@ -23,7 +23,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
23
23
|
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
24
24
|
io = File.open(dl_path, 'wb')
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Curl::Easy.download(dl_url, io)
|
|
27
27
|
assert io.closed?
|
|
28
28
|
assert File.exist?(dl_path)
|
|
29
29
|
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
@@ -32,15 +32,19 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def test_download_url_to_file_via_io
|
|
35
|
+
omit('fork not available on this platform') if NO_FORK || WINDOWS
|
|
35
36
|
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
36
37
|
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
37
38
|
reader, writer = IO.pipe
|
|
38
39
|
|
|
39
40
|
# Write to local file
|
|
40
|
-
fork do
|
|
41
|
+
child_pid = fork do
|
|
41
42
|
begin
|
|
42
43
|
writer.close
|
|
43
44
|
File.open(dl_path, 'wb') { |file| file << reader.read }
|
|
45
|
+
exit! 0
|
|
46
|
+
rescue StandardError
|
|
47
|
+
exit! 1
|
|
44
48
|
ensure
|
|
45
49
|
reader.close rescue IOError # if the stream has already been closed
|
|
46
50
|
end
|
|
@@ -49,8 +53,9 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
49
53
|
# Download remote source
|
|
50
54
|
begin
|
|
51
55
|
reader.close
|
|
52
|
-
|
|
53
|
-
Process.
|
|
56
|
+
Curl::Easy.download(dl_url, writer)
|
|
57
|
+
_pid, status = Process.wait2(child_pid)
|
|
58
|
+
assert_predicate status, :success?
|
|
54
59
|
ensure
|
|
55
60
|
writer.close rescue IOError # if the stream has already been closed, which occurs in Easy::download
|
|
56
61
|
end
|
|
@@ -58,7 +63,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
58
63
|
assert File.exist?(dl_path)
|
|
59
64
|
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
60
65
|
ensure
|
|
61
|
-
File.unlink(dl_path) if File.exist?(dl_path)
|
|
66
|
+
File.unlink(dl_path) if dl_path && File.exist?(dl_path)
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
def test_download_bad_url_gives_404
|