curb 1.2.2 → 1.3.1

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.
data/ext/curb_multi.h CHANGED
@@ -15,13 +15,15 @@ struct st_table;
15
15
  typedef struct {
16
16
  int active;
17
17
  int running;
18
+ char closed;
18
19
  CURLM *handle;
19
20
  struct st_table *attached;
20
21
  } ruby_curl_multi;
21
22
 
22
23
  extern VALUE cCurlMulti;
24
+ extern const rb_data_type_t ruby_curl_multi_data_type;
25
+
23
26
  void init_curb_multi();
24
- VALUE ruby_curl_multi_new(VALUE klass);
25
27
  void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
26
28
 
27
29
 
data/ext/curb_postfield.c CHANGED
@@ -32,7 +32,7 @@ void append_to_form(VALUE self,
32
32
  ruby_curl_postfield *rbcpf;
33
33
  CURLFORMcode result = -1;
34
34
 
35
- Data_Get_Struct(self, ruby_curl_postfield, rbcpf);
35
+ TypedData_Get_Struct(self, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf);
36
36
 
37
37
  if (rbcpf->name == Qnil) {
38
38
  rb_raise(eCurlErrInvalidPostField, "Cannot post unnamed field");
@@ -177,19 +177,43 @@ void append_to_form(VALUE self,
177
177
 
178
178
 
179
179
  /* ================== MARK/FREE FUNC ==================*/
180
- void curl_postfield_mark(ruby_curl_postfield *rbcpf) {
181
- rb_gc_mark(rbcpf->name);
182
- rb_gc_mark(rbcpf->content);
183
- rb_gc_mark(rbcpf->content_type);
184
- rb_gc_mark(rbcpf->local_file);
185
- rb_gc_mark(rbcpf->remote_file);
186
- rb_gc_mark(rbcpf->buffer_str);
180
+ static void curl_postfield_mark(void *ptr) {
181
+ ruby_curl_postfield *rbcpf = (ruby_curl_postfield *)ptr;
182
+ if (rbcpf) {
183
+ rb_gc_mark(rbcpf->name);
184
+ rb_gc_mark(rbcpf->content);
185
+ rb_gc_mark(rbcpf->content_type);
186
+ rb_gc_mark(rbcpf->local_file);
187
+ rb_gc_mark(rbcpf->remote_file);
188
+ rb_gc_mark(rbcpf->buffer_str);
189
+ }
190
+ }
191
+
192
+ static void curl_postfield_free(void *ptr) {
193
+ if (ptr) free(ptr);
187
194
  }
188
195
 
189
- void curl_postfield_free(ruby_curl_postfield *rbcpf) {
190
- free(rbcpf);
196
+ static size_t curl_postfield_memsize(const void *ptr) {
197
+ (void)ptr;
198
+ return sizeof(ruby_curl_postfield);
191
199
  }
192
200
 
201
+ const rb_data_type_t ruby_curl_postfield_data_type = {
202
+ "Curl::PostField",
203
+ {
204
+ curl_postfield_mark,
205
+ curl_postfield_free,
206
+ curl_postfield_memsize,
207
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
208
+ NULL, /* compact */
209
+ #endif
210
+ },
211
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
212
+ NULL, NULL, /* parent, data */
213
+ RUBY_TYPED_FREE_IMMEDIATELY
214
+ #endif
215
+ };
216
+
193
217
 
194
218
  /* ================= ALLOC METHODS ====================*/
195
219
 
@@ -208,10 +232,11 @@ void curl_postfield_free(ruby_curl_postfield *rbcpf) {
208
232
  * data.
209
233
  */
210
234
  static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass) {
211
- ruby_curl_postfield *rbcpf = ALLOC(ruby_curl_postfield);
212
- if (!rbcpf) {
213
- rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::PostField");
214
- }
235
+ VALUE self;
236
+ ruby_curl_postfield *rbcpf;
237
+
238
+ self = TypedData_Make_Struct(klass, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf);
239
+ MEMZERO(rbcpf, ruby_curl_postfield, 1);
215
240
 
216
241
  // wierdness - we actually require two args, unless a block is provided, but
217
242
  // we have to work that out below.
@@ -239,7 +264,7 @@ static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass)
239
264
  rbcpf->remote_file = Qnil;
240
265
  rbcpf->buffer_str = Qnil;
241
266
 
242
- return Data_Wrap_Struct(cCurlPostField, curl_postfield_mark, curl_postfield_free, rbcpf);
267
+ return self;
243
268
  }
244
269
 
245
270
  /*
@@ -257,10 +282,11 @@ static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass)
257
282
  */
258
283
  static VALUE ruby_curl_postfield_new_file(int argc, VALUE *argv, VALUE klass) {
259
284
  // TODO needs to handle content-type too
260
- ruby_curl_postfield *rbcpf = ALLOC(ruby_curl_postfield);
261
- if (!rbcpf) {
262
- rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::PostField");
263
- }
285
+ VALUE self;
286
+ ruby_curl_postfield *rbcpf;
287
+
288
+ self = TypedData_Make_Struct(klass, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf);
289
+ MEMZERO(rbcpf, ruby_curl_postfield, 1);
264
290
 
265
291
  rb_scan_args(argc, argv, "21&", &rbcpf->name, &rbcpf->local_file, &rbcpf->remote_file, &rbcpf->content_proc);
266
292
 
@@ -288,7 +314,7 @@ static VALUE ruby_curl_postfield_new_file(int argc, VALUE *argv, VALUE klass) {
288
314
  rbcpf->content_type = Qnil;
289
315
  rbcpf->buffer_str = Qnil;
290
316
 
291
- return Data_Wrap_Struct(cCurlPostField, curl_postfield_mark, curl_postfield_free, rbcpf);
317
+ return self;
292
318
  }
293
319
 
294
320
  /* ================= ATTRIBUTES ====================*/
@@ -435,7 +461,7 @@ static VALUE ruby_curl_postfield_to_str(VALUE self) {
435
461
  CURL *curl_handle = NULL;
436
462
  #endif
437
463
 
438
- Data_Get_Struct(self, ruby_curl_postfield, rbcpf);
464
+ TypedData_Get_Struct(self, ruby_curl_postfield, &ruby_curl_postfield_data_type, rbcpf);
439
465
 
440
466
  if (rbcpf->name != Qnil) {
441
467
  name = rbcpf->name;
data/ext/curb_postfield.h CHANGED
@@ -30,6 +30,7 @@ typedef struct {
30
30
  } ruby_curl_postfield;
31
31
 
32
32
  extern VALUE cCurlPostField;
33
+ extern const rb_data_type_t ruby_curl_postfield_data_type;
33
34
 
34
35
  void append_to_form(VALUE self,
35
36
  struct curl_httppost **first,
data/ext/curb_upload.c CHANGED
@@ -10,13 +10,36 @@ VALUE cCurlUpload;
10
10
  mCurl = rb_define_module("Curl");
11
11
  #endif
12
12
 
13
- static void curl_upload_mark(ruby_curl_upload *rbcu) {
14
- if (rbcu->stream && !NIL_P(rbcu->stream)) rb_gc_mark(rbcu->stream);
13
+ static void curl_upload_mark(void *ptr) {
14
+ ruby_curl_upload *rbcu = (ruby_curl_upload *)ptr;
15
+ if (rbcu && rbcu->stream && !NIL_P(rbcu->stream)) rb_gc_mark(rbcu->stream);
15
16
  }
16
- static void curl_upload_free(ruby_curl_upload *rbcu) {
17
- free(rbcu);
17
+
18
+ static void curl_upload_free(void *ptr) {
19
+ if (ptr) free(ptr);
20
+ }
21
+
22
+ static size_t curl_upload_memsize(const void *ptr) {
23
+ (void)ptr;
24
+ return sizeof(ruby_curl_upload);
18
25
  }
19
26
 
27
+ const rb_data_type_t ruby_curl_upload_data_type = {
28
+ "Curl::Upload",
29
+ {
30
+ curl_upload_mark,
31
+ curl_upload_free,
32
+ curl_upload_memsize,
33
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
34
+ NULL, /* compact */
35
+ #endif
36
+ },
37
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
38
+ NULL, NULL, /* parent, data */
39
+ RUBY_TYPED_FREE_IMMEDIATELY
40
+ #endif
41
+ };
42
+
20
43
  /*
21
44
  * call-seq:
22
45
  * internal class for sending large file uploads
@@ -29,7 +52,7 @@ VALUE ruby_curl_upload_new(VALUE klass) {
29
52
  }
30
53
  rbcu->stream = Qnil;
31
54
  rbcu->offset = 0;
32
- upload = Data_Wrap_Struct(klass, curl_upload_mark, curl_upload_free, rbcu);
55
+ upload = TypedData_Wrap_Struct(klass, &ruby_curl_upload_data_type, rbcu);
33
56
  return upload;
34
57
  }
35
58
 
@@ -39,7 +62,7 @@ VALUE ruby_curl_upload_new(VALUE klass) {
39
62
  */
40
63
  VALUE ruby_curl_upload_stream_set(VALUE self, VALUE stream) {
41
64
  ruby_curl_upload *rbcu;
42
- Data_Get_Struct(self, ruby_curl_upload, rbcu);
65
+ TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
43
66
  rbcu->stream = stream;
44
67
  return stream;
45
68
  }
@@ -49,7 +72,7 @@ VALUE ruby_curl_upload_stream_set(VALUE self, VALUE stream) {
49
72
  */
50
73
  VALUE ruby_curl_upload_stream_get(VALUE self) {
51
74
  ruby_curl_upload *rbcu;
52
- Data_Get_Struct(self, ruby_curl_upload, rbcu);
75
+ TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
53
76
  return rbcu->stream;
54
77
  }
55
78
  /*
@@ -58,7 +81,7 @@ VALUE ruby_curl_upload_stream_get(VALUE self) {
58
81
  */
59
82
  VALUE ruby_curl_upload_offset_set(VALUE self, VALUE offset) {
60
83
  ruby_curl_upload *rbcu;
61
- Data_Get_Struct(self, ruby_curl_upload, rbcu);
84
+ TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
62
85
  rbcu->offset = NUM2LONG(offset);
63
86
  return offset;
64
87
  }
@@ -68,7 +91,7 @@ VALUE ruby_curl_upload_offset_set(VALUE self, VALUE offset) {
68
91
  */
69
92
  VALUE ruby_curl_upload_offset_get(VALUE self) {
70
93
  ruby_curl_upload *rbcu;
71
- Data_Get_Struct(self, ruby_curl_upload, rbcu);
94
+ TypedData_Get_Struct(self, ruby_curl_upload, &ruby_curl_upload_data_type, rbcu);
72
95
  return LONG2NUM(rbcu->offset);
73
96
  }
74
97
 
data/ext/curb_upload.h CHANGED
@@ -19,6 +19,8 @@ typedef struct {
19
19
  } ruby_curl_upload;
20
20
 
21
21
  extern VALUE cCurlUpload;
22
+ extern const rb_data_type_t ruby_curl_upload_data_type;
23
+
22
24
  void init_curb_upload();
23
25
 
24
26
  VALUE ruby_curl_upload_new(VALUE klass);
data/ext/extconf.rb CHANGED
@@ -299,6 +299,38 @@ have_constant "curlopt_sockoptdata"
299
299
  have_constant "curlopt_opensocketfunction"
300
300
  have_constant "curlopt_opensocketdata"
301
301
 
302
+ # Deprecated constants (still check for them for backward compat)
303
+ have_constant "curlopt_ioctlfunction"
304
+ have_constant "curlopt_ioctldata"
305
+ have_constant "curlopt_progressfunction"
306
+ have_constant "curlopt_progressdata"
307
+ have_constant "curlopt_conv_to_network_function"
308
+ have_constant "curlopt_conv_from_network_function"
309
+ have_constant "curlopt_conv_from_utf8_function"
310
+
311
+ # Replacements for deprecated constants
312
+ # CURLOPT_PROGRESSFUNCTION -> CURLOPT_XFERINFOFUNCTION (since 7.32.0)
313
+ have_constant "curlopt_xferinfofunction"
314
+ have_constant "curlopt_xferinfodata"
315
+
316
+ # CURLOPT_PROTOCOLS -> CURLOPT_PROTOCOLS_STR (since 7.85.0)
317
+ have_constant "curlopt_protocols_str"
318
+ have_constant "curlopt_redir_protocols_str"
319
+
320
+ # CURLOPT_SOCKS5_GSSAPI_SERVICE -> CURLOPT_PROXY_SERVICE_NAME (since 7.49.0)
321
+ have_constant "curlopt_proxy_service_name"
322
+
323
+ # CURLOPT_HTTPPOST -> CURLOPT_MIMEPOST (since 7.56.0)
324
+ have_constant "curlopt_mimepost"
325
+
326
+ # CURLINFO_* -> CURLINFO_*_T (since 7.55.0)
327
+ have_constant "curlinfo_size_upload_t"
328
+ have_constant "curlinfo_size_download_t"
329
+ have_constant "curlinfo_speed_upload_t"
330
+ have_constant "curlinfo_speed_download_t"
331
+ have_constant "curlinfo_content_length_download_t"
332
+ have_constant "curlinfo_content_length_upload_t"
333
+
302
334
  # additional consts
303
335
  have_constant "curle_conv_failed"
304
336
  have_constant "curle_conv_reqd"
@@ -621,6 +653,14 @@ if try_compile('int main() { return 0; }','-Wall')
621
653
  $CFLAGS << ' -Wall'
622
654
  end
623
655
 
656
+ # Clang-specific warning suppressions (not recognized by GCC)
657
+ # These are used to suppress warnings in Ruby header macros
658
+ %w[-Wno-self-assign -Wno-parentheses-equality -Wno-constant-logical-operand].each do |flag|
659
+ if try_compile('int main() { return 0; }', flag)
660
+ $CFLAGS << " #{flag}"
661
+ end
662
+ end
663
+
624
664
  # do some checking to detect ruby 1.8 hash.c vs ruby 1.9 hash.c
625
665
  def test_for(name, const, src)
626
666
  checking_for name do
@@ -645,7 +685,8 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
645
685
  have_func('rb_thread_blocking_region')
646
686
  have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
647
687
  have_header('ruby/io.h')
648
- have_func('rb_thread_fd_select', 'ruby/io.h')
688
+ # Ruby 4.x exports rb_thread_fd_select without declaring it in ruby/io.h.
689
+ have_func('rb_thread_fd_select')
649
690
  have_func('rb_wait_for_single_fd', 'ruby/io.h')
650
691
  have_header('ruby/fiber/scheduler.h')
651
692
  have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
data/lib/curl/easy.rb CHANGED
@@ -1,7 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
  module Curl
3
3
  class Easy
4
+ class << self
5
+ def deferred_multi_close_mutex
6
+ @deferred_multi_close_mutex ||= Mutex.new
7
+ end
8
+
9
+ def deferred_multi_closes
10
+ deferred_multi_close_mutex.synchronize do
11
+ (@deferred_multi_closes ||= []).dup
12
+ end
13
+ end
14
+
15
+ def release_deferred_multi_close(multi, easy)
16
+ if easy && multi.requests[easy.object_id]
17
+ begin
18
+ multi.remove(easy)
19
+ rescue StandardError
20
+ # Deferred cleanup only applies to implicit single-easy multis, so
21
+ # clear any stale Ruby bookkeeping and continue closing the handle.
22
+ multi.instance_variable_set(:@requests, {})
23
+ end
24
+ else
25
+ multi.instance_variable_set(:@requests, {})
26
+ end
27
+
28
+ multi.instance_variable_set(:@deferred_close, false)
29
+ multi._close
30
+ true
31
+ rescue StandardError
32
+ false
33
+ end
4
34
 
35
+ def defer_multi_close(multi, easy, owner: Thread.current)
36
+ deferred_multi_close_mutex.synchronize do
37
+ @deferred_multi_closes ||= []
38
+ return if @deferred_multi_closes.any? { |entry| entry[:multi].equal?(multi) }
39
+
40
+ multi.instance_variable_set(:@deferred_close, true)
41
+ @deferred_multi_closes << { multi: multi, easy: easy, owner: owner }
42
+ end
43
+ end
44
+
45
+ def flush_deferred_multi_closes(all_threads: false)
46
+ pending = deferred_multi_close_mutex.synchronize do
47
+ @deferred_multi_closes ||= []
48
+
49
+ if all_threads
50
+ @deferred_multi_closes.shift(@deferred_multi_closes.length)
51
+ else
52
+ owner = Thread.current
53
+ remaining = []
54
+ current = []
55
+
56
+ @deferred_multi_closes.each do |entry|
57
+ if entry[:owner].equal?(owner)
58
+ current << entry
59
+ else
60
+ remaining << entry
61
+ end
62
+ end
63
+
64
+ @deferred_multi_closes = remaining
65
+ current
66
+ end
67
+ end
68
+
69
+ pending.each do |entry|
70
+ multi = entry[:multi]
71
+ easy = entry[:easy]
72
+
73
+ unless release_deferred_multi_close(multi, easy)
74
+ defer_multi_close(multi, easy, owner: entry[:owner])
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ at_exit do
81
+ flush_deferred_multi_closes(all_threads: true)
82
+ end
83
+
84
+ alias_method :_curb_native_close, :close
85
+ alias_method :_curb_native_multi_set, :multi=
86
+
87
+ def close
88
+ previous_multi = self.multi
89
+ result = _curb_native_close
90
+ previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi
91
+ result
92
+ end
93
+
94
+ def multi=(multi)
95
+ previous_multi = self.multi
96
+ return multi if previous_multi.equal?(multi)
97
+
98
+ result = _curb_native_multi_set(multi)
99
+ previous_multi.__send__(:__unregister_idle_easy_reference, self) if previous_multi
100
+ multi.__send__(:__register_idle_easy_reference, self) if multi
101
+ result
102
+ end
103
+
5
104
  alias post http_post
6
105
  alias put http_put
7
106
  alias body body_str
@@ -64,18 +163,55 @@ module Curl
64
163
  # the configured HTTP Verb.
65
164
  #
66
165
  def perform
67
- self.multi = Curl::Multi.new if self.multi.nil?
68
- self.multi.add self
69
- ret = self.multi.perform
70
- self.multi.remove self
71
-
72
- if Curl::Multi.autoclose
73
- self.multi.close
74
- self.multi = nil
166
+ self.class.flush_deferred_multi_closes
167
+
168
+ if Curl.scheduler_active? && self.multi.nil?
169
+ ret = Curl.perform_with_scheduler(self)
170
+ else
171
+ multi = self.multi
172
+ created_multi = multi.nil?
173
+ raised = false
174
+
175
+ if created_multi
176
+ multi = Curl::Multi.new
177
+ self.multi = multi
178
+ end
179
+
180
+ begin
181
+ multi.add(self)
182
+ ret = multi.perform
183
+ multi.remove(self) if self.multi == multi
184
+ rescue Exception
185
+ raised = true
186
+ raise
187
+ ensure
188
+ if created_multi
189
+ if raised
190
+ unless self.class.release_deferred_multi_close(multi, self)
191
+ self.class.defer_multi_close(multi, self)
192
+ end
193
+ self.multi = nil if self.multi == multi
194
+ elsif Curl::Multi.autoclose
195
+ multi.__send__(:_autoclose)
196
+ self.multi = nil if self.multi == multi
197
+ else
198
+ self.multi = multi
199
+ end
200
+ elsif Curl::Multi.autoclose
201
+ multi.__send__(:_autoclose)
202
+ self.multi = nil if self.multi == multi
203
+ else
204
+ self.multi = multi
205
+ end
206
+ end
207
+ end
208
+
209
+ if (callback_error = _take_callback_error)
210
+ raise callback_error
75
211
  end
76
212
 
77
213
  if self.last_result != 0 && self.on_failure.nil?
78
- (err_class, err_summary) = Curl::Easy.error(self.last_result)
214
+ err_class, err_summary = Curl::Easy.error(self.last_result)
79
215
  err_detail = self.last_error
80
216
  raise err_class.new([err_summary, err_detail].compact.join(": "))
81
217
  end
@@ -85,6 +221,7 @@ module Curl
85
221
 
86
222
  #
87
223
  # call-seq:
224
+
88
225
  #
89
226
  # easy = Curl::Easy.new
90
227
  # easy.nosignal = true
@@ -109,12 +246,16 @@ module Curl
109
246
  #
110
247
  # easy = Curl::Easy.new("url")
111
248
  # easy.version = Curl::HTTP_2_0
112
- # easy.version = Curl::HTTP_1_1
113
- # easy.version = Curl::HTTP_1_0
114
- # easy.version = Curl::HTTP_NONE
249
+ # easy.http_version = Curl::HTTP_1_1
250
+ # easy.http_version = Curl::HTTP_1_0
251
+ # easy.http_version = Curl::HTTP_NONE
115
252
  #
116
253
  def version=(http_version)
117
- set :http_version, http_version
254
+ self.http_version = http_version
255
+ end
256
+
257
+ def version
258
+ http_version
118
259
  end
119
260
 
120
261
  #
data/lib/curl/multi.rb CHANGED
@@ -160,16 +160,20 @@ module Curl
160
160
  end
161
161
  end
162
162
 
163
- if urls_with_config.empty?
164
- m.perform
165
- else
166
- until urls_with_config.empty?
167
- m.perform do
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
168
171
  consume_free_handles.call
169
172
  end
170
- consume_free_handles.call
173
+ free_handles = nil
171
174
  end
172
- free_handles = nil
175
+ ensure
176
+ m.close
173
177
  end
174
178
 
175
179
  end
@@ -266,10 +270,53 @@ module Curl
266
270
  @requests ||= {}
267
271
  end
268
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
295
+ end
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
+
269
312
  def add(easy)
270
313
  return self if requests[easy.object_id]
271
- requests[easy.object_id] = easy
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)
272
317
  _add(easy)
318
+ __unregister_idle_easy_reference(easy)
319
+ requests[easy.object_id] = easy
273
320
  self
274
321
  end
275
322
 
@@ -281,14 +328,27 @@ module Curl
281
328
  end
282
329
 
283
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)
284
343
  requests.values.each {|easy|
285
344
  _remove(easy)
286
345
  }
346
+ __clear_idle_easy_references if permanent
287
347
  @requests = {}
288
348
  _close
349
+ _mark_closed if permanent
289
350
  self
290
351
  end
291
352
 
292
-
293
353
  end
294
354
  end