curb 0.9.3 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
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