curb 0.9.3 → 1.0.2

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.c CHANGED
@@ -37,78 +37,45 @@ static VALUE idCall;
37
37
  VALUE cCurlMulti;
38
38
 
39
39
  static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
40
+ static char cCurlMutiAutoClose = 0;
40
41
 
41
42
  static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
42
43
  static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
43
44
  static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
44
45
 
45
- static VALUE callback_exception(VALUE unused) {
46
- return Qfalse;
47
- }
48
-
49
- static void curl_multi_mark(ruby_curl_multi *rbcm) {
50
- if (!NIL_P(rbcm->requests)) rb_gc_mark(rbcm->requests);
51
- }
52
-
53
- /* Hash#foreach callback for curl_multi_free */
54
- static int curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
55
- CURLMcode result;
56
- ruby_curl_easy *rbce;
57
-
58
- // sometimes the type is T_ZOMBIE, e.g. after Ruby has received the SIGTERM signal
59
- if (rb_type(easy) == T_DATA) {
60
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
61
-
62
- result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
63
- if (result != 0) {
64
- raise_curl_multi_error_exception(result);
65
- }
66
- }
67
-
68
- return ST_DELETE;
69
- }
70
-
71
46
  void curl_multi_free(ruby_curl_multi *rbcm) {
72
- VALUE hash = rbcm->requests;
73
-
74
- if (!NIL_P(hash) && rb_type(hash) == T_HASH && RHASH_SIZE(hash) > 0) {
75
-
76
- rb_hash_foreach(hash, curl_multi_flush_easy, (VALUE)rbcm);
77
- /* rb_hash_clear(rbcm->requests); */
47
+ curl_multi_cleanup(rbcm->handle);
48
+ free(rbcm);
49
+ }
78
50
 
79
- rbcm->requests = Qnil;
51
+ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
52
+ rbcm->handle = curl_multi_init();
53
+ if (!rbcm->handle) {
54
+ rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
80
55
  }
81
56
 
82
- if (rbcm->handle) {
83
- curl_multi_cleanup(rbcm->handle);
84
- }
85
- free(rbcm);
57
+ rbcm->active = 0;
58
+ rbcm->running = 0;
86
59
  }
87
60
 
88
61
  /*
89
62
  * call-seq:
90
- * Curl::Multi.new => #<Curl::Easy...>
63
+ * Curl::Multi.new => #<Curl::Easy...>
91
64
  *
92
65
  * Create a new Curl::Multi instance
93
66
  */
94
67
  VALUE ruby_curl_multi_new(VALUE klass) {
95
- VALUE new_curlm;
96
-
97
68
  ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
98
69
 
99
- rbcm->handle = curl_multi_init();
100
- if (!rbcm->handle) {
101
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
102
- }
103
-
104
- rbcm->requests = rb_hash_new();
105
-
106
- rbcm->active = 0;
107
- rbcm->running = 0;
70
+ ruby_curl_multi_init(rbcm);
108
71
 
109
- new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
110
-
111
- return new_curlm;
72
+ /*
73
+ * The mark routine will be called by the garbage collector during its ``mark'' phase.
74
+ * If your structure references other Ruby objects, then your mark function needs to
75
+ * identify these objects using rb_gc_mark(value). If the structure doesn't reference
76
+ * other Ruby objects, you can simply pass 0 as a function pointer.
77
+ */
78
+ return Data_Wrap_Struct(klass, 0, curl_multi_free, rbcm);
112
79
  }
113
80
 
114
81
  /*
@@ -135,52 +102,36 @@ VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
135
102
  return LONG2NUM(cCurlMutiDefaulttimeout);
136
103
  }
137
104
 
138
- /* Hash#foreach callback for ruby_curl_multi_requests */
139
- static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
140
- rb_ary_push(result_array, value);
141
-
142
- return ST_CONTINUE;
105
+ /*
106
+ * call-seq:
107
+ * Curl::Multi.autoclose = true => true
108
+ *
109
+ * Automatically close open connections after each request. Otherwise, the connection will remain open
110
+ * for reuse until the next GC
111
+ *
112
+ */
113
+ VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
114
+ cCurlMutiAutoClose = ((onoff == Qtrue) ? 1 : 0);
115
+ return onoff;
143
116
  }
144
117
 
145
118
  /*
146
119
  * call-seq:
147
- * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
148
- *
149
- * Returns an array containing all the active requests on this Curl::Multi object.
120
+ * Curl::Multi.autoclose => true|false
121
+ *
122
+ * Get the global default autoclose setting for all Curl::Multi Handles.
123
+ *
150
124
  */
151
- static VALUE ruby_curl_multi_requests(VALUE self) {
152
- ruby_curl_multi *rbcm;
153
- VALUE result_array;
154
-
155
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
156
-
157
- result_array = rb_ary_new();
158
-
159
- /* iterate over the requests hash, and stuff references into the array. */
160
- rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
161
-
162
- return result_array;
125
+ VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
126
+ return cCurlMutiAutoClose == 1 ? Qtrue : Qfalse;
163
127
  }
164
128
 
165
129
  /*
166
130
  * call-seq:
167
- * multi.idle? => true or false
131
+ * multi.requests => [#<Curl::Easy...>, ...]
168
132
  *
169
- * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
170
- * true when multi.requests.length == 0.
133
+ * Returns an array containing all the active requests on this Curl::Multi object.
171
134
  */
172
- static VALUE ruby_curl_multi_idle(VALUE self) {
173
- ruby_curl_multi *rbcm;
174
-
175
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
176
-
177
- if (RHASH_SIZE(rbcm->requests) == 0) {
178
- return Qtrue;
179
- } else {
180
- return Qfalse;
181
- }
182
- }
183
-
184
135
  /*
185
136
  * call-seq:
186
137
  * multi = Curl::Multi.new
@@ -194,13 +145,6 @@ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
194
145
 
195
146
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
196
147
 
197
- if (!rbcm->handle) {
198
- rbcm->handle = curl_multi_init();
199
- if (!rbcm->handle) {
200
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
201
- }
202
- }
203
-
204
148
  curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2LONG(count));
205
149
  #endif
206
150
 
@@ -234,14 +178,6 @@ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) {
234
178
  }
235
179
 
236
180
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
237
-
238
- if (!rbcm->handle) {
239
- rbcm->handle = curl_multi_init();
240
- if (!rbcm->handle) {
241
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
242
- }
243
- }
244
-
245
181
  curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, value);
246
182
  #endif
247
183
  return method == Qtrue ? 1 : 0;
@@ -258,29 +194,15 @@ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) {
258
194
  */
259
195
  VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
260
196
  CURLMcode mcode;
261
- VALUE r;
262
197
  ruby_curl_easy *rbce;
263
198
  ruby_curl_multi *rbcm;
264
199
 
265
200
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
266
201
  Data_Get_Struct(easy, ruby_curl_easy, rbce);
267
202
 
268
- // check if this curl handle has been added before adding again
269
- r = rb_hash_aref(rbcm->requests, LONG2NUM((long)rbce->curl));
270
- if ( r != Qnil ) {
271
- return Qnil;
272
- }
273
-
274
203
  /* setup the easy handle */
275
204
  ruby_curl_easy_setup( rbce );
276
205
 
277
- if (!rbcm->handle) {
278
- rbcm->handle = curl_multi_init();
279
- if (!rbcm->handle) {
280
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
281
- }
282
- }
283
-
284
206
  mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
285
207
  if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
286
208
  raise_curl_multi_error_exception(mcode);
@@ -295,8 +217,6 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
295
217
  /* track a reference to associated multi handle */
296
218
  rbce->multi = self;
297
219
 
298
- rb_hash_aset( rbcm->requests, LONG2NUM((long)rbce->curl), easy );
299
-
300
220
  return self;
301
221
  }
302
222
 
@@ -314,35 +234,20 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
314
234
  *
315
235
  * Will raise an exception if the easy handle is not found
316
236
  */
317
- VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
237
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
318
238
  ruby_curl_multi *rbcm;
319
239
 
320
240
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
321
241
 
322
- if (!rbcm->handle) {
323
- rbcm->handle = curl_multi_init();
324
- if (!rbcm->handle) {
325
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
326
- }
327
- }
328
-
329
- rb_curl_multi_remove(rbcm,easy);
242
+ rb_curl_multi_remove(rbcm, rb_easy_handle);
330
243
 
331
244
  return self;
332
245
  }
333
246
  static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
334
247
  CURLMcode result;
335
248
  ruby_curl_easy *rbce;
336
- VALUE r;
337
249
 
338
250
  Data_Get_Struct(easy, ruby_curl_easy, rbce);
339
-
340
- // check if this curl handle has been added before removing
341
- r = rb_hash_aref(rbcm->requests, LONG2NUM((long)rbce->curl));
342
- if ( r == Qnil ) {
343
- return;
344
- }
345
-
346
251
  result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
347
252
  if (result != 0) {
348
253
  raise_curl_multi_error_exception(result);
@@ -351,43 +256,6 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
351
256
  rbcm->active--;
352
257
 
353
258
  ruby_curl_easy_cleanup( easy, rbce );
354
-
355
- // active should equal LONG2NUM(RHASH(rbcm->requests)->tbl->num_entries)
356
- r = rb_hash_delete( rbcm->requests, LONG2NUM((long)rbce->curl) );
357
- if( r != easy || r == Qnil ) {
358
- rb_warn("Possibly lost track of Curl::Easy VALUE, it may not be reclaimed by GC");
359
- }
360
- }
361
-
362
- /* Hash#foreach callback for ruby_curl_multi_cancel */
363
- static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
364
- rb_curl_multi_remove(rbcm, value);
365
-
366
- return ST_CONTINUE;
367
- }
368
-
369
- /*
370
- * call-seq:
371
- * multi.cancel!
372
- *
373
- * Cancels all requests currently being made on this Curl::Multi handle.
374
- */
375
- static VALUE ruby_curl_multi_cancel(VALUE self) {
376
- ruby_curl_multi *rbcm;
377
-
378
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
379
-
380
- if (!rbcm->handle) {
381
- rbcm->handle = curl_multi_init();
382
- if (!rbcm->handle) {
383
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
384
- }
385
- }
386
-
387
- rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
388
-
389
- /* for chaining */
390
- return self;
391
259
  }
392
260
 
393
261
  // on_success, on_failure, on_complete
@@ -399,11 +267,10 @@ static VALUE call_status_handler2(VALUE ary) {
399
267
  }
400
268
 
401
269
  static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
402
-
403
270
  long response_code = -1;
404
271
  VALUE easy;
405
272
  ruby_curl_easy *rbce = NULL;
406
- VALUE callargs, val = Qtrue;
273
+ VALUE callargs;
407
274
 
408
275
  CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
409
276
 
@@ -411,7 +278,8 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
411
278
 
412
279
  rbce->last_result = result; /* save the last easy result code */
413
280
 
414
- ruby_curl_multi_remove( self, easy );
281
+ // remove the easy handle from multi on completion so it can be reused again
282
+ rb_funcall(self, rb_intern("remove"), 1, easy);
415
283
 
416
284
  /* after running a request cleanup the headers, these are set before each request */
417
285
  if (rbce->curl_headers) {
@@ -423,77 +291,99 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
423
291
  raise_curl_easy_error_exception(ecode);
424
292
  }
425
293
 
294
+ int status;
295
+
426
296
  if (!rb_easy_nil("complete_proc")) {
427
297
  callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), easy);
428
298
  rbce->callback_active = 1;
429
- val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
299
+ rb_protect(call_status_handler1, callargs, &status);
430
300
  rbce->callback_active = 0;
431
- //rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
301
+ if (status) {
302
+ CURB_RB_CALLBACK_RAISE("complete")
303
+ }
432
304
  }
433
305
 
306
+ #ifdef HAVE_CURLINFO_RESPONSE_CODE
434
307
  curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
308
+ #else
309
+ // old libcurl
310
+ curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CODE, &response_code);
311
+ #endif
312
+ long redirect_count;
313
+ curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
435
314
 
436
315
  if (result != 0) {
437
316
  if (!rb_easy_nil("failure_proc")) {
438
317
  callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
439
318
  rbce->callback_active = 1;
440
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
319
+ rb_protect(call_status_handler2, callargs, &status);
441
320
  rbce->callback_active = 0;
442
- //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
321
+ if (status) {
322
+ CURB_RB_CALLBACK_RAISE("failure")
323
+ }
443
324
  }
444
- }
445
- else if (!rb_easy_nil("success_proc") &&
325
+ } else if (!rb_easy_nil("success_proc") &&
446
326
  ((response_code >= 200 && response_code < 300) || response_code == 0)) {
447
327
  /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
448
328
  callargs = rb_ary_new3(2, rb_easy_get("success_proc"), easy);
449
329
  rbce->callback_active = 1;
450
- val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
330
+ rb_protect(call_status_handler1, callargs, &status);
451
331
  rbce->callback_active = 0;
452
- //rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
453
- }
454
- else if (!rb_easy_nil("redirect_proc") &&
455
- (response_code >= 300 && response_code < 400)) {
332
+ if (status) {
333
+ CURB_RB_CALLBACK_RAISE("success")
334
+ }
335
+ } else if (!rb_easy_nil("redirect_proc") && ((response_code >= 300 && response_code < 400) || redirect_count > 0) ) {
456
336
  rbce->callback_active = 1;
457
337
  callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
458
338
  rbce->callback_active = 0;
459
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
460
- }
461
- else if (!rb_easy_nil("missing_proc") &&
339
+ rb_protect(call_status_handler2, callargs, &status);
340
+ if (status) {
341
+ CURB_RB_CALLBACK_RAISE("redirect")
342
+ }
343
+ } else if (!rb_easy_nil("missing_proc") &&
462
344
  (response_code >= 400 && response_code < 500)) {
463
345
  rbce->callback_active = 1;
464
346
  callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), easy, rb_curl_easy_error(result));
465
347
  rbce->callback_active = 0;
466
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
467
- }
468
- else if (!rb_easy_nil("failure_proc") &&
348
+ rb_protect(call_status_handler2, callargs, &status);
349
+ if (status) {
350
+ CURB_RB_CALLBACK_RAISE("missing")
351
+ }
352
+ } else if (!rb_easy_nil("failure_proc") &&
469
353
  (response_code >= 500 && response_code <= 999)) {
470
354
  callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
471
355
  rbce->callback_active = 1;
472
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
356
+ rb_protect(call_status_handler2, callargs, &status);
473
357
  rbce->callback_active = 0;
474
- //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
475
- }
476
-
477
- if (val == Qfalse) {
478
- rb_warn("uncaught exception from callback");
358
+ if (status) {
359
+ CURB_RB_CALLBACK_RAISE("failure")
360
+ }
479
361
  }
480
362
 
481
363
  }
482
364
 
483
365
  static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
484
- int msgs_left, result;
485
- CURLMsg *msg;
486
- CURL *easy_handle;
487
-
488
- /* check for finished easy handles and remove from the multi handle */
489
- while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
490
- if (msg->msg == CURLMSG_DONE) {
491
- easy_handle = msg->easy_handle;
492
- result = msg->data.result;
493
- if (easy_handle) {
494
- rb_curl_mutli_handle_complete(self, easy_handle, result);
495
- }
496
- }
366
+ int msgs_left;
367
+
368
+ CURLcode c_easy_result;
369
+ CURLMsg *c_multi_result; // for picking up messages with the transfer status
370
+ CURL *c_easy_handle;
371
+
372
+ /* Check for finished easy handles and remove from the multi handle.
373
+ * curl_multi_info_read will query for messages from individual handles.
374
+ *
375
+ * The messages fetched with this function are removed from the curl internal
376
+ * queue and when there are no messages left it will return NULL (and break
377
+ * the loop effectively).
378
+ */
379
+ while ((c_multi_result = curl_multi_info_read(multi_handle, &msgs_left))) {
380
+ // A message is there, but we really care only about transfer completetion.
381
+ if (c_multi_result->msg != CURLMSG_DONE) continue;
382
+
383
+ c_easy_handle = c_multi_result->easy_handle;
384
+ c_easy_result = c_multi_result->data.result; /* return code for transfer */
385
+
386
+ rb_curl_mutli_handle_complete(self, c_easy_handle, c_easy_result);
497
387
  }
498
388
  }
499
389
 
@@ -501,15 +391,32 @@ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
501
391
  static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
502
392
  CURLMcode mcode;
503
393
 
394
+ /*
395
+ * curl_multi_perform will return CURLM_CALL_MULTI_PERFORM only when it wants to be called again immediately.
396
+ * When things are fine and there is nothing immediate it wants done, it'll return CURLM_OK.
397
+ *
398
+ * It will perform all pending actions on all added easy handles attached to this multi handle. We will loop
399
+ * here as long as mcode is CURLM_CALL_MULTIPERFORM.
400
+ */
504
401
  do {
505
402
  mcode = curl_multi_perform(multi_handle, still_running);
506
403
  } while (mcode == CURLM_CALL_MULTI_PERFORM);
507
404
 
405
+ /*
406
+ * Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
407
+ */
508
408
 
509
409
  if (mcode != CURLM_OK) {
510
410
  raise_curl_multi_error_exception(mcode);
511
411
  }
512
412
 
413
+ /*
414
+ * Everything is ok, but this does not mean all the transfers are completed.
415
+ * There is no data to read or write available for Curl at the moment.
416
+ *
417
+ * At this point we can return control to the caller to do something else while
418
+ * curl is waiting for more actions to queue.
419
+ */
513
420
  }
514
421
 
515
422
  #ifdef _WIN32
@@ -580,6 +487,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
580
487
  #endif
581
488
  long timeout_milliseconds;
582
489
  struct timeval tv = {0, 0};
490
+ struct timeval tv_100ms = {0, 100000};
583
491
  VALUE block = Qnil;
584
492
  #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
585
493
  struct _select_set fdset_args;
@@ -591,10 +499,23 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
591
499
 
592
500
  timeout_milliseconds = cCurlMutiDefaulttimeout;
593
501
 
502
+ // Run curl_multi_perform for the first time to get the ball rolling
594
503
  rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
504
+
505
+ // Check the easy handles for new messages one more time before yielding
506
+ // control to passed ruby block.
507
+ //
508
+ // This call will block until all queued messages are processed and if any
509
+ // handle completed the transfer we will run the on_complete callback here too.
595
510
  rb_curl_multi_read_info( self, rbcm->handle );
596
- if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
597
-
511
+
512
+ // There are no more messages to handle by curl and we can run the ruby block
513
+ // passed to perform method.
514
+ // When the block completes curl will resume.
515
+ if (block != Qnil) {
516
+ rb_funcall(block, rb_intern("call"), 1, self);
517
+ }
518
+
598
519
  do {
599
520
  while (rbcm->running) {
600
521
 
@@ -634,18 +555,30 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
634
555
  raise_curl_multi_error_exception(mcode);
635
556
  }
636
557
 
558
+ if (maxfd == -1) {
559
+ /* libcurl recommends sleeping for 100ms */
560
+ rb_thread_wait_for(tv_100ms);
561
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
562
+ rb_curl_multi_read_info( self, rbcm->handle );
563
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
564
+ continue;
565
+ }
566
+
637
567
  #ifdef _WIN32
638
568
  create_crt_fd(&fdread, &crt_fdread);
639
569
  create_crt_fd(&fdwrite, &crt_fdwrite);
640
570
  create_crt_fd(&fdexcep, &crt_fdexcep);
641
571
  #endif
642
572
 
643
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
573
+
574
+ #if (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
644
575
  fdset_args.maxfd = maxfd+1;
645
576
  fdset_args.fdread = &fdread;
646
577
  fdset_args.fdwrite = &fdwrite;
647
578
  fdset_args.fdexcep = &fdexcep;
648
579
  fdset_args.tv = &tv;
580
+ #endif
581
+
649
582
  #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
650
583
  rc = (int)(VALUE) rb_thread_call_without_gvl((void *(*)(void *))curb_select, &fdset_args, RUBY_UBF_IO, 0);
651
584
  #elif HAVE_RB_THREAD_BLOCKING_REGION
@@ -656,8 +589,6 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
656
589
  rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
657
590
  #endif
658
591
 
659
- #endif
660
-
661
592
  #ifdef _WIN32
662
593
  cleanup_crt_fd(&fdread, &crt_fdread);
663
594
  cleanup_crt_fd(&fdwrite, &crt_fdwrite);
@@ -683,38 +614,46 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
683
614
 
684
615
  rb_curl_multi_read_info( self, rbcm->handle );
685
616
  if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
686
-
687
- /* do early cleanup */
688
- VALUE hash = rbcm->requests;
689
- if (!NIL_P(hash) && rb_type(hash) == T_HASH && RHASH_SIZE(hash) > 0) {
690
- rb_hash_foreach(hash, curl_multi_flush_easy, (VALUE)rbcm);
617
+ if (cCurlMutiAutoClose == 1) {
618
+ rb_funcall(self, rb_intern("close"), 0);
691
619
  }
692
- curl_multi_cleanup(rbcm->handle);
693
- rbcm->handle = NULL;
694
-
695
620
  return Qtrue;
696
621
  }
697
622
 
623
+ /*
624
+ * call-seq:
625
+ *
626
+ * multi.close
627
+ * after closing the multi handle all connections will be closed and the handle will no longer be usable
628
+ *
629
+ */
630
+ VALUE ruby_curl_multi_close(VALUE self) {
631
+ ruby_curl_multi *rbcm;
632
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
633
+ curl_multi_cleanup(rbcm->handle);
634
+ ruby_curl_multi_init(rbcm);
635
+ return self;
636
+ }
637
+
638
+
698
639
  /* =================== INIT LIB =====================*/
699
640
  void init_curb_multi() {
700
641
  idCall = rb_intern("call");
701
-
702
642
  cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
703
643
 
644
+ rb_undef_alloc_func(cCurlMulti);
645
+
704
646
  /* Class methods */
705
647
  rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
706
648
  rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
707
649
  rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
708
-
709
- /* "Attributes" */
710
- rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
711
- rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
712
-
650
+ rb_define_singleton_method(cCurlMulti, "autoclose=", ruby_curl_multi_set_autoclose, 1);
651
+ rb_define_singleton_method(cCurlMulti, "autoclose", ruby_curl_multi_get_autoclose, 0);
713
652
  /* Instance methods */
714
653
  rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
715
654
  rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
716
- rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
717
- rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
718
- rb_define_method(cCurlMulti, "cancel!", ruby_curl_multi_cancel, 0);
655
+ rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
656
+ rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
719
657
  rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
658
+ rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
720
659
  }
data/ext/curb_multi.h CHANGED
@@ -14,7 +14,6 @@
14
14
  typedef struct {
15
15
  int active;
16
16
  int running;
17
- VALUE requests; /* hash of handles currently added */
18
17
  CURLM *handle;
19
18
  } ruby_curl_multi;
20
19