curb 0.9.3 → 1.0.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.
data/ext/curb_multi.c CHANGED
@@ -37,78 +37,66 @@ 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);
46
+ static VALUE callback_exception(VALUE did_raise, VALUE exception) {
47
+ // TODO: we could have an option to enable exception reporting
48
+ /* VALUE ret = rb_funcall(exception, rb_intern("message"), 0);
49
+ VALUE trace = rb_funcall(exception, rb_intern("backtrace"), 0);
50
+ if (RB_TYPE_P(trace, T_ARRAY) && RARRAY_LEN(trace) > 0) {
51
+ printf("we got an exception: %s:%d\n", StringValueCStr(ret), RARRAY_LEN(trace));
52
+ VALUE sep = rb_str_new_cstr("\n");
53
+ VALUE trace_lines = rb_ary_join(trace, sep);
54
+ if (RB_TYPE_P(trace_lines, T_STRING)) {
55
+ printf("%s\n", StringValueCStr(trace_lines));
56
+ } else {
57
+ printf("trace is not a string??\n");
65
58
  }
59
+ } else {
60
+ printf("we got an exception: %s\nno stack available\n", StringValueCStr(ret));
66
61
  }
67
-
68
- return ST_DELETE;
62
+ */
63
+ rb_hash_aset(did_raise, rb_easy_hkey("error"), exception);
64
+ return exception;
69
65
  }
70
66
 
71
67
  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); */
68
+ curl_multi_cleanup(rbcm->handle);
69
+ free(rbcm);
70
+ }
78
71
 
79
- rbcm->requests = Qnil;
72
+ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
73
+ rbcm->handle = curl_multi_init();
74
+ if (!rbcm->handle) {
75
+ rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
80
76
  }
81
77
 
82
- if (rbcm->handle) {
83
- curl_multi_cleanup(rbcm->handle);
84
- }
85
- free(rbcm);
78
+ rbcm->active = 0;
79
+ rbcm->running = 0;
86
80
  }
87
81
 
88
82
  /*
89
83
  * call-seq:
90
- * Curl::Multi.new => #<Curl::Easy...>
84
+ * Curl::Multi.new => #<Curl::Easy...>
91
85
  *
92
86
  * Create a new Curl::Multi instance
93
87
  */
94
88
  VALUE ruby_curl_multi_new(VALUE klass) {
95
- VALUE new_curlm;
96
-
97
89
  ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
98
90
 
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();
91
+ ruby_curl_multi_init(rbcm);
105
92
 
106
- rbcm->active = 0;
107
- rbcm->running = 0;
108
-
109
- new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
110
-
111
- return new_curlm;
93
+ /*
94
+ * The mark routine will be called by the garbage collector during its ``mark'' phase.
95
+ * If your structure references other Ruby objects, then your mark function needs to
96
+ * identify these objects using rb_gc_mark(value). If the structure doesn't reference
97
+ * other Ruby objects, you can simply pass 0 as a function pointer.
98
+ */
99
+ return Data_Wrap_Struct(klass, 0, curl_multi_free, rbcm);
112
100
  }
113
101
 
114
102
  /*
@@ -135,52 +123,36 @@ VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
135
123
  return LONG2NUM(cCurlMutiDefaulttimeout);
136
124
  }
137
125
 
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;
126
+ /*
127
+ * call-seq:
128
+ * Curl::Multi.autoclose = true => true
129
+ *
130
+ * Automatically close open connections after each request. Otherwise, the connection will remain open
131
+ * for reuse until the next GC
132
+ *
133
+ */
134
+ VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
135
+ cCurlMutiAutoClose = ((onoff == Qtrue) ? 1 : 0);
136
+ return onoff;
143
137
  }
144
138
 
145
139
  /*
146
140
  * call-seq:
147
- * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
148
- *
149
- * Returns an array containing all the active requests on this Curl::Multi object.
141
+ * Curl::Multi.autoclose => true|false
142
+ *
143
+ * Get the global default autoclose setting for all Curl::Multi Handles.
144
+ *
150
145
  */
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;
146
+ VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
147
+ return cCurlMutiAutoClose == 1 ? Qtrue : Qfalse;
163
148
  }
164
149
 
165
150
  /*
166
151
  * call-seq:
167
- * multi.idle? => true or false
152
+ * multi.requests => [#<Curl::Easy...>, ...]
168
153
  *
169
- * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
170
- * true when multi.requests.length == 0.
154
+ * Returns an array containing all the active requests on this Curl::Multi object.
171
155
  */
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
156
  /*
185
157
  * call-seq:
186
158
  * multi = Curl::Multi.new
@@ -194,13 +166,6 @@ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
194
166
 
195
167
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
196
168
 
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
169
  curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2LONG(count));
205
170
  #endif
206
171
 
@@ -234,14 +199,6 @@ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) {
234
199
  }
235
200
 
236
201
  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
202
  curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, value);
246
203
  #endif
247
204
  return method == Qtrue ? 1 : 0;
@@ -258,29 +215,15 @@ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) {
258
215
  */
259
216
  VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
260
217
  CURLMcode mcode;
261
- VALUE r;
262
218
  ruby_curl_easy *rbce;
263
219
  ruby_curl_multi *rbcm;
264
220
 
265
221
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
266
222
  Data_Get_Struct(easy, ruby_curl_easy, rbce);
267
223
 
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
224
  /* setup the easy handle */
275
225
  ruby_curl_easy_setup( rbce );
276
226
 
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
227
  mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
285
228
  if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
286
229
  raise_curl_multi_error_exception(mcode);
@@ -295,8 +238,6 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
295
238
  /* track a reference to associated multi handle */
296
239
  rbce->multi = self;
297
240
 
298
- rb_hash_aset( rbcm->requests, LONG2NUM((long)rbce->curl), easy );
299
-
300
241
  return self;
301
242
  }
302
243
 
@@ -314,35 +255,20 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
314
255
  *
315
256
  * Will raise an exception if the easy handle is not found
316
257
  */
317
- VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
258
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
318
259
  ruby_curl_multi *rbcm;
319
260
 
320
261
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
321
262
 
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);
263
+ rb_curl_multi_remove(rbcm, rb_easy_handle);
330
264
 
331
265
  return self;
332
266
  }
333
267
  static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
334
268
  CURLMcode result;
335
269
  ruby_curl_easy *rbce;
336
- VALUE r;
337
270
 
338
271
  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
272
  result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
347
273
  if (result != 0) {
348
274
  raise_curl_multi_error_exception(result);
@@ -351,43 +277,6 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
351
277
  rbcm->active--;
352
278
 
353
279
  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
280
  }
392
281
 
393
282
  // on_success, on_failure, on_complete
@@ -399,11 +288,10 @@ static VALUE call_status_handler2(VALUE ary) {
399
288
  }
400
289
 
401
290
  static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
402
-
403
291
  long response_code = -1;
404
292
  VALUE easy;
405
293
  ruby_curl_easy *rbce = NULL;
406
- VALUE callargs, val = Qtrue;
294
+ VALUE callargs;
407
295
 
408
296
  CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
409
297
 
@@ -411,7 +299,8 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
411
299
 
412
300
  rbce->last_result = result; /* save the last easy result code */
413
301
 
414
- ruby_curl_multi_remove( self, easy );
302
+ // remove the easy handle from multi on completion so it can be reused again
303
+ rb_funcall(self, rb_intern("remove"), 1, easy);
415
304
 
416
305
  /* after running a request cleanup the headers, these are set before each request */
417
306
  if (rbce->curl_headers) {
@@ -423,77 +312,88 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
423
312
  raise_curl_easy_error_exception(ecode);
424
313
  }
425
314
 
315
+ VALUE did_raise = rb_hash_new();
316
+
426
317
  if (!rb_easy_nil("complete_proc")) {
427
318
  callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), easy);
428
319
  rbce->callback_active = 1;
429
- val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
320
+ rb_rescue(call_status_handler1, callargs, callback_exception, did_raise);
430
321
  rbce->callback_active = 0;
431
- //rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
322
+ CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
432
323
  }
433
324
 
325
+ #ifdef HAVE_CURLINFO_RESPONSE_CODE
434
326
  curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
327
+ #else
328
+ // old libcurl
329
+ curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CODE, &response_code);
330
+ #endif
331
+ long redirect_count;
332
+ curl_easy_getinfo(rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
435
333
 
436
334
  if (result != 0) {
437
335
  if (!rb_easy_nil("failure_proc")) {
438
336
  callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
439
337
  rbce->callback_active = 1;
440
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
338
+ rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
441
339
  rbce->callback_active = 0;
442
- //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
340
+ CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
443
341
  }
444
- }
445
- else if (!rb_easy_nil("success_proc") &&
342
+ } else if (!rb_easy_nil("success_proc") &&
446
343
  ((response_code >= 200 && response_code < 300) || response_code == 0)) {
447
344
  /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
448
345
  callargs = rb_ary_new3(2, rb_easy_get("success_proc"), easy);
449
346
  rbce->callback_active = 1;
450
- val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
347
+ rb_rescue(call_status_handler1, callargs, callback_exception, did_raise);
451
348
  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)) {
349
+ CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
350
+
351
+ } else if (!rb_easy_nil("redirect_proc") && ((response_code >= 300 && response_code < 400) || redirect_count > 0) ) {
456
352
  rbce->callback_active = 1;
457
353
  callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
458
354
  rbce->callback_active = 0;
459
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
460
- }
461
- else if (!rb_easy_nil("missing_proc") &&
355
+ rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
356
+ CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
357
+ } else if (!rb_easy_nil("missing_proc") &&
462
358
  (response_code >= 400 && response_code < 500)) {
463
359
  rbce->callback_active = 1;
464
360
  callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), easy, rb_curl_easy_error(result));
465
361
  rbce->callback_active = 0;
466
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
467
- }
468
- else if (!rb_easy_nil("failure_proc") &&
362
+ rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
363
+ CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
364
+ } else if (!rb_easy_nil("failure_proc") &&
469
365
  (response_code >= 500 && response_code <= 999)) {
470
366
  callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
471
367
  rbce->callback_active = 1;
472
- val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
368
+ rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
473
369
  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");
370
+ CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
479
371
  }
480
372
 
481
373
  }
482
374
 
483
375
  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
- }
376
+ int msgs_left;
377
+
378
+ CURLcode c_easy_result;
379
+ CURLMsg *c_multi_result; // for picking up messages with the transfer status
380
+ CURL *c_easy_handle;
381
+
382
+ /* Check for finished easy handles and remove from the multi handle.
383
+ * curl_multi_info_read will query for messages from individual handles.
384
+ *
385
+ * The messages fetched with this function are removed from the curl internal
386
+ * queue and when there are no messages left it will return NULL (and break
387
+ * the loop effectively).
388
+ */
389
+ while ((c_multi_result = curl_multi_info_read(multi_handle, &msgs_left))) {
390
+ // A message is there, but we really care only about transfer completetion.
391
+ if (c_multi_result->msg != CURLMSG_DONE) continue;
392
+
393
+ c_easy_handle = c_multi_result->easy_handle;
394
+ c_easy_result = c_multi_result->data.result; /* return code for transfer */
395
+
396
+ rb_curl_mutli_handle_complete(self, c_easy_handle, c_easy_result);
497
397
  }
498
398
  }
499
399
 
@@ -501,15 +401,32 @@ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
501
401
  static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
502
402
  CURLMcode mcode;
503
403
 
404
+ /*
405
+ * curl_multi_perform will return CURLM_CALL_MULTI_PERFORM only when it wants to be called again immediately.
406
+ * When things are fine and there is nothing immediate it wants done, it'll return CURLM_OK.
407
+ *
408
+ * It will perform all pending actions on all added easy handles attached to this multi handle. We will loop
409
+ * here as long as mcode is CURLM_CALL_MULTIPERFORM.
410
+ */
504
411
  do {
505
412
  mcode = curl_multi_perform(multi_handle, still_running);
506
413
  } while (mcode == CURLM_CALL_MULTI_PERFORM);
507
414
 
415
+ /*
416
+ * Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
417
+ */
508
418
 
509
419
  if (mcode != CURLM_OK) {
510
420
  raise_curl_multi_error_exception(mcode);
511
421
  }
512
422
 
423
+ /*
424
+ * Everything is ok, but this does not mean all the transfers are completed.
425
+ * There is no data to read or write available for Curl at the moment.
426
+ *
427
+ * At this point we can return control to the caller to do something else while
428
+ * curl is waiting for more actions to queue.
429
+ */
513
430
  }
514
431
 
515
432
  #ifdef _WIN32
@@ -580,6 +497,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
580
497
  #endif
581
498
  long timeout_milliseconds;
582
499
  struct timeval tv = {0, 0};
500
+ struct timeval tv_100ms = {0, 100000};
583
501
  VALUE block = Qnil;
584
502
  #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
585
503
  struct _select_set fdset_args;
@@ -591,10 +509,23 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
591
509
 
592
510
  timeout_milliseconds = cCurlMutiDefaulttimeout;
593
511
 
512
+ // Run curl_multi_perform for the first time to get the ball rolling
594
513
  rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
514
+
515
+ // Check the easy handles for new messages one more time before yielding
516
+ // control to passed ruby block.
517
+ //
518
+ // This call will block until all queued messages are processed and if any
519
+ // handle completed the transfer we will run the on_complete callback here too.
595
520
  rb_curl_multi_read_info( self, rbcm->handle );
596
- if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
597
-
521
+
522
+ // There are no more messages to handle by curl and we can run the ruby block
523
+ // passed to perform method.
524
+ // When the block completes curl will resume.
525
+ if (block != Qnil) {
526
+ rb_funcall(block, rb_intern("call"), 1, self);
527
+ }
528
+
598
529
  do {
599
530
  while (rbcm->running) {
600
531
 
@@ -634,18 +565,30 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
634
565
  raise_curl_multi_error_exception(mcode);
635
566
  }
636
567
 
568
+ if (maxfd == -1) {
569
+ /* libcurl recommends sleeping for 100ms */
570
+ rb_thread_wait_for(tv_100ms);
571
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
572
+ rb_curl_multi_read_info( self, rbcm->handle );
573
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
574
+ continue;
575
+ }
576
+
637
577
  #ifdef _WIN32
638
578
  create_crt_fd(&fdread, &crt_fdread);
639
579
  create_crt_fd(&fdwrite, &crt_fdwrite);
640
580
  create_crt_fd(&fdexcep, &crt_fdexcep);
641
581
  #endif
642
582
 
643
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
583
+
584
+ #if (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
644
585
  fdset_args.maxfd = maxfd+1;
645
586
  fdset_args.fdread = &fdread;
646
587
  fdset_args.fdwrite = &fdwrite;
647
588
  fdset_args.fdexcep = &fdexcep;
648
589
  fdset_args.tv = &tv;
590
+ #endif
591
+
649
592
  #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
650
593
  rc = (int)(VALUE) rb_thread_call_without_gvl((void *(*)(void *))curb_select, &fdset_args, RUBY_UBF_IO, 0);
651
594
  #elif HAVE_RB_THREAD_BLOCKING_REGION
@@ -656,8 +599,6 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
656
599
  rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
657
600
  #endif
658
601
 
659
- #endif
660
-
661
602
  #ifdef _WIN32
662
603
  cleanup_crt_fd(&fdread, &crt_fdread);
663
604
  cleanup_crt_fd(&fdwrite, &crt_fdwrite);
@@ -683,38 +624,46 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
683
624
 
684
625
  rb_curl_multi_read_info( self, rbcm->handle );
685
626
  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);
627
+ if (cCurlMutiAutoClose == 1) {
628
+ rb_funcall(self, rb_intern("close"), 0);
691
629
  }
692
- curl_multi_cleanup(rbcm->handle);
693
- rbcm->handle = NULL;
694
-
695
630
  return Qtrue;
696
631
  }
697
632
 
633
+ /*
634
+ * call-seq:
635
+ *
636
+ * multi.close
637
+ * after closing the multi handle all connections will be closed and the handle will no longer be usable
638
+ *
639
+ */
640
+ VALUE ruby_curl_multi_close(VALUE self) {
641
+ ruby_curl_multi *rbcm;
642
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
643
+ curl_multi_cleanup(rbcm->handle);
644
+ ruby_curl_multi_init(rbcm);
645
+ return self;
646
+ }
647
+
648
+
698
649
  /* =================== INIT LIB =====================*/
699
650
  void init_curb_multi() {
700
651
  idCall = rb_intern("call");
701
-
702
652
  cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
703
653
 
654
+ rb_undef_alloc_func(cCurlMulti);
655
+
704
656
  /* Class methods */
705
657
  rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
706
658
  rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
707
659
  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
-
660
+ rb_define_singleton_method(cCurlMulti, "autoclose=", ruby_curl_multi_set_autoclose, 1);
661
+ rb_define_singleton_method(cCurlMulti, "autoclose", ruby_curl_multi_get_autoclose, 0);
713
662
  /* Instance methods */
714
663
  rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
715
664
  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);
665
+ rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
666
+ rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
719
667
  rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
668
+ rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
720
669
  }
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