curb 1.2.2 → 1.3.0
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 +4 -4
- data/Rakefile +22 -0
- data/ext/curb.c +282 -231
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +608 -215
- data/ext/curb_easy.h +5 -0
- data/ext/curb_errors.c +5 -5
- data/ext/curb_errors.h +1 -1
- data/ext/curb_macros.h +14 -14
- data/ext/curb_multi.c +611 -141
- data/ext/curb_multi.h +3 -1
- data/ext/curb_postfield.c +47 -21
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +32 -9
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +40 -0
- data/lib/curl/easy.rb +154 -13
- data/lib/curl/multi.rb +69 -9
- data/lib/curl.rb +193 -0
- data/tests/helper.rb +222 -36
- data/tests/leak_trace.rb +237 -0
- data/tests/tc_curl_download.rb +6 -2
- data/tests/tc_curl_easy.rb +450 -1
- data/tests/tc_curl_multi.rb +573 -59
- data/tests/tc_curl_native_coverage.rb +130 -0
- data/tests/tc_curl_postfield.rb +161 -0
- data/tests/tc_fiber_scheduler.rb +342 -7
- data/tests/tc_gc_compact.rb +178 -16
- data/tests/tc_test_server_methods.rb +110 -0
- metadata +10 -14
- data/tests/test_basic.rb +0 -29
- data/tests/test_fiber_debug.rb +0 -69
- data/tests/test_fiber_simple.rb +0 -65
- data/tests/test_real_url.rb +0 -65
- data/tests/test_simple_fiber.rb +0 -34
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/tc_curl_download.rb
CHANGED
|
@@ -38,10 +38,13 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
38
38
|
reader, writer = IO.pipe
|
|
39
39
|
|
|
40
40
|
# Write to local file
|
|
41
|
-
fork do
|
|
41
|
+
child_pid = fork do
|
|
42
42
|
begin
|
|
43
43
|
writer.close
|
|
44
44
|
File.open(dl_path, 'wb') { |file| file << reader.read }
|
|
45
|
+
exit! 0
|
|
46
|
+
rescue StandardError
|
|
47
|
+
exit! 1
|
|
45
48
|
ensure
|
|
46
49
|
reader.close rescue IOError # if the stream has already been closed
|
|
47
50
|
end
|
|
@@ -51,7 +54,8 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
51
54
|
begin
|
|
52
55
|
reader.close
|
|
53
56
|
Curl::Easy.download(dl_url, writer)
|
|
54
|
-
Process.
|
|
57
|
+
_pid, status = Process.wait2(child_pid)
|
|
58
|
+
assert_predicate status, :success?
|
|
55
59
|
ensure
|
|
56
60
|
writer.close rescue IOError # if the stream has already been closed, which occurs in Easy::download
|
|
57
61
|
end
|