curb 0.9.4 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
data/ext/curb_easy.h CHANGED
@@ -55,6 +55,8 @@ typedef struct {
55
55
  unsigned long ftp_response_timeout;
56
56
  long low_speed_limit;
57
57
  long low_speed_time;
58
+ long max_send_speed_large;
59
+ long max_recv_speed_large;
58
60
  long ssl_version;
59
61
  long use_ssl;
60
62
  long ftp_filemethod;
@@ -76,7 +78,9 @@ typedef struct {
76
78
  char callback_active;
77
79
 
78
80
  struct curl_slist *curl_headers;
81
+ struct curl_slist *curl_proxy_headers;
79
82
  struct curl_slist *curl_ftp_commands;
83
+ struct curl_slist *curl_resolve;
80
84
 
81
85
  int last_result; /* last result code from multi loop */
82
86
 
data/ext/curb_errors.c CHANGED
@@ -21,6 +21,7 @@ VALUE eCurlErrFileError;
21
21
  VALUE eCurlErrLDAPError;
22
22
  VALUE eCurlErrTelnetError;
23
23
  VALUE eCurlErrTFTPError;
24
+ VALUE eCurlErrRTSPError;
24
25
 
25
26
  /* Specific libcurl errors */
26
27
  VALUE eCurlErrOK; /* not really an error but a return code */
@@ -127,6 +128,18 @@ VALUE mCurlErrUnknownOption;
127
128
  VALUE eCurlErrInvalidPostField;
128
129
 
129
130
 
131
+ /* new errors */
132
+ VALUE eCurlErrFTPPRETFailed;
133
+ VALUE eCurlErrRTSPCseqError;
134
+ VALUE eCurlErrRTSPSessionError;
135
+ VALUE eCurlErrFTPBadFileList;
136
+ VALUE eCurlErrChunkFailed;
137
+ VALUE eCurlErrNoConnectionAvailable;
138
+ VALUE eCurlErrSSLPinnedPubKeyNotMatch;
139
+ VALUE eCurlErrSSLInvalidCertStatus;
140
+ VALUE eCurlErrHTTP2Stream;
141
+
142
+
130
143
  VALUE rb_curl_easy_error(CURLcode code) {
131
144
  VALUE exclz;
132
145
  const char *exmsg = NULL;
@@ -294,9 +307,11 @@ VALUE rb_curl_easy_error(CURLcode code) {
294
307
  exclz = eCurlErrObsolete;
295
308
  break;
296
309
  #endif
310
+ #if LIBCURL_VERSION_NUM < 0x073e00
297
311
  case CURLE_SSL_PEER_CERTIFICATE: /* 51 - peer's certificate wasn't ok */
298
312
  exclz = eCurlErrSSLPeerCertificate;
299
313
  break;
314
+ #endif
300
315
  case CURLE_GOT_NOTHING: /* 52 - when this is a specific error */
301
316
  exclz = eCurlErrGotNothing;
302
317
  break;
@@ -321,8 +336,13 @@ VALUE rb_curl_easy_error(CURLcode code) {
321
336
  case CURLE_SSL_CIPHER: /* 59 - couldn't use specified cipher */
322
337
  exclz = eCurlErrSSLCipher;
323
338
  break;
339
+ #if LIBCURL_VERSION_NUM >= 0x073e00
340
+ case CURLE_PEER_FAILED_VERIFICATION: /* 60 - problem with the CA cert (path?) */
341
+ exclz = eCurlErrSSLPeerCertificate;
342
+ #else
324
343
  case CURLE_SSL_CACERT: /* 60 - problem with the CA cert (path?) */
325
344
  exclz = eCurlErrSSLCACertificate;
345
+ #endif
326
346
  break;
327
347
  case CURLE_BAD_CONTENT_ENCODING: /* 61 - Unrecognized transfer encoding */
328
348
  exclz = eCurlErrBadContentEncoding;
@@ -445,6 +465,61 @@ VALUE rb_curl_easy_error(CURLcode code) {
445
465
  exclz = eCurlErrSSLIssuerError;
446
466
  break;
447
467
  #endif
468
+
469
+ #ifdef HAVE_CURLE_FTP_PRET_FAILED
470
+ case CURLE_FTP_PRET_FAILED: /* 84 */
471
+ exclz = eCurlErrFTPPRETFailed;
472
+ break;
473
+ #endif
474
+
475
+ #ifdef HAVE_CURLE_RTSP_CSEQ_ERROR
476
+ case CURLE_RTSP_CSEQ_ERROR: /* 85 */
477
+ exclz = eCurlErrRTSPCseqError;
478
+ break;
479
+ #endif
480
+
481
+ #ifdef HAVE_CURLE_RTSP_SESSION_ERROR
482
+ case CURLE_RTSP_SESSION_ERROR: /* 86 */
483
+ exclz = eCurlErrRTSPSessionError;
484
+ break;
485
+ #endif
486
+
487
+ #ifdef HAVE_CURLE_FTP_BAD_FILE_LIST
488
+ case CURLE_FTP_BAD_FILE_LIST: /* 87 */
489
+ exclz = eCurlErrFTPBadFileList;
490
+ break;
491
+ #endif
492
+
493
+ #ifdef HAVE_CURLE_CHUNK_FAILED
494
+ case CURLE_CHUNK_FAILED: /* 88 */
495
+ exclz = eCurlErrChunkFailed;
496
+ break;
497
+ #endif
498
+
499
+ #ifdef HAVE_CURLE_NO_CONNECTION_AVAILABLE
500
+ case CURLE_NO_CONNECTION_AVAILABLE: /* 89 */
501
+ exclz = eCurlErrNoConnectionAvailable;
502
+ break;
503
+ #endif
504
+
505
+ #ifdef HAVE_CURLE_SSL_PINNEDPUBKEYNOTMATCH
506
+ case CURLE_SSL_PINNEDPUBKEYNOTMATCH: /* 90 */
507
+ exclz = eCurlErrSSLPinnedPubKeyNotMatch;
508
+ break;
509
+ #endif
510
+
511
+ #ifdef HAVE_CURLE_SSL_INVALIDCERTSTATUS
512
+ case CURLE_SSL_INVALIDCERTSTATUS: /* 91 */
513
+ exclz = eCurlErrSSLInvalidCertStatus;
514
+ break;
515
+ #endif
516
+
517
+ #ifdef HAVE_CURLE_HTTP2_STREAM
518
+ case CURLE_HTTP2_STREAM: /* 92 */
519
+ exclz = eCurlErrHTTP2Stream;
520
+ break;
521
+ #endif
522
+
448
523
  default:
449
524
  exclz = eCurlErrError;
450
525
  exmsg = "Unknown error result from libcurl";
@@ -532,6 +607,7 @@ void init_curb_errors() {
532
607
  eCurlErrLDAPError = rb_define_class_under(mCurlErr, "LDAPError", eCurlErrError);
533
608
  eCurlErrTelnetError = rb_define_class_under(mCurlErr, "TelnetError", eCurlErrError);
534
609
  eCurlErrTFTPError = rb_define_class_under(mCurlErr, "TFTPError", eCurlErrError);
610
+ eCurlErrRTSPError = rb_define_class_under(mCurlErr, "RTSPError", eCurlErrError);
535
611
 
536
612
  eCurlErrOK = rb_define_class_under(mCurlErr, "CurlOK", eCurlErrError);
537
613
  eCurlErrUnsupportedProtocol = rb_define_class_under(mCurlErr, "UnsupportedProtocolError", eCurlErrError);
@@ -657,4 +733,14 @@ void init_curb_errors() {
657
733
  eCurlErrTFTPNoSuchUser = rb_define_class_under(mCurlErr, "NoSuchUserError", eCurlErrTFTPError);
658
734
 
659
735
  eCurlErrInvalidPostField = rb_define_class_under(mCurlErr, "InvalidPostFieldError", eCurlErrError);
736
+
737
+ eCurlErrFTPPRETFailed = rb_define_class_under(mCurlErr, "PPRETFailedError", eCurlErrFTPError);
738
+ eCurlErrRTSPCseqError = rb_define_class_under(mCurlErr, "CseqError", eCurlErrRTSPError);
739
+ eCurlErrRTSPSessionError = rb_define_class_under(mCurlErr, "SessionError", eCurlErrRTSPError);
740
+ eCurlErrFTPBadFileList = rb_define_class_under(mCurlErr, "BadFileListError", eCurlErrFTPError);
741
+ eCurlErrChunkFailed = rb_define_class_under(mCurlErr, "ChunkFailedError", eCurlErrError);
742
+ eCurlErrNoConnectionAvailable = rb_define_class_under(mCurlErr, "NoConnectionAvailableError", eCurlErrError);
743
+ eCurlErrSSLPinnedPubKeyNotMatch = rb_define_class_under(mCurlErr, "SSLPinnedPubKeyNotMatchError", eCurlErrError);
744
+ eCurlErrSSLInvalidCertStatus = rb_define_class_under(mCurlErr, "SSLInvalidCertStatusError", eCurlErrError);
745
+ eCurlErrHTTP2Stream = rb_define_class_under(mCurlErr, "HTTP2StreamError", eCurlErrHTTPError);
660
746
  }
data/ext/curb_multi.c CHANGED
@@ -37,6 +37,7 @@ 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);
@@ -44,43 +45,21 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
44
45
 
45
46
  static VALUE callback_exception(VALUE unused) {
46
47
  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
48
  }
70
49
 
71
50
  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); */
51
+ curl_multi_cleanup(rbcm->handle);
52
+ free(rbcm);
53
+ }
78
54
 
79
- rbcm->requests = Qnil;
55
+ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
56
+ rbcm->handle = curl_multi_init();
57
+ if (!rbcm->handle) {
58
+ rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
80
59
  }
81
60
 
82
- curl_multi_cleanup(rbcm->handle);
83
- free(rbcm);
61
+ rbcm->active = 0;
62
+ rbcm->running = 0;
84
63
  }
85
64
 
86
65
  /*
@@ -90,23 +69,17 @@ void curl_multi_free(ruby_curl_multi *rbcm) {
90
69
  * Create a new Curl::Multi instance
91
70
  */
92
71
  VALUE ruby_curl_multi_new(VALUE klass) {
93
- VALUE new_curlm;
94
-
95
72
  ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
96
73
 
97
- rbcm->handle = curl_multi_init();
98
- if (!rbcm->handle) {
99
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
100
- }
74
+ ruby_curl_multi_init(rbcm);
101
75
 
102
- rbcm->requests = rb_hash_new();
103
-
104
- rbcm->active = 0;
105
- rbcm->running = 0;
106
-
107
- new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
108
-
109
- return new_curlm;
76
+ /*
77
+ * The mark routine will be called by the garbage collector during its ``mark'' phase.
78
+ * If your structure references other Ruby objects, then your mark function needs to
79
+ * identify these objects using rb_gc_mark(value). If the structure doesn't reference
80
+ * other Ruby objects, you can simply pass 0 as a function pointer.
81
+ */
82
+ return Data_Wrap_Struct(klass, 0, curl_multi_free, rbcm);
110
83
  }
111
84
 
112
85
  /*
@@ -133,52 +106,36 @@ VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
133
106
  return LONG2NUM(cCurlMutiDefaulttimeout);
134
107
  }
135
108
 
136
- /* Hash#foreach callback for ruby_curl_multi_requests */
137
- static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
138
- rb_ary_push(result_array, value);
139
-
140
- return ST_CONTINUE;
109
+ /*
110
+ * call-seq:
111
+ * Curl::Multi.autoclose = true => true
112
+ *
113
+ * Automatically close open connections after each request. Otherwise, the connection will remain open
114
+ * for reuse until the next GC
115
+ *
116
+ */
117
+ VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
118
+ cCurlMutiAutoClose = ((onoff == Qtrue) ? 1 : 0);
119
+ return onoff;
141
120
  }
142
121
 
143
122
  /*
144
123
  * call-seq:
145
- * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
146
- *
147
- * Returns an array containing all the active requests on this Curl::Multi object.
124
+ * Curl::Multi.autoclose => true|false
125
+ *
126
+ * Get the global default autoclose setting for all Curl::Multi Handles.
127
+ *
148
128
  */
149
- static VALUE ruby_curl_multi_requests(VALUE self) {
150
- ruby_curl_multi *rbcm;
151
- VALUE result_array;
152
-
153
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
154
-
155
- result_array = rb_ary_new();
156
-
157
- /* iterate over the requests hash, and stuff references into the array. */
158
- rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
159
-
160
- return result_array;
129
+ VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
130
+ return cCurlMutiAutoClose == 1 ? Qtrue : Qfalse;
161
131
  }
162
132
 
163
133
  /*
164
134
  * call-seq:
165
- * multi.idle? => true or false
135
+ * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
166
136
  *
167
- * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
168
- * true when multi.requests.length == 0.
137
+ * Returns an array containing all the active requests on this Curl::Multi object.
169
138
  */
170
- static VALUE ruby_curl_multi_idle(VALUE self) {
171
- ruby_curl_multi *rbcm;
172
-
173
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
174
-
175
- if (RHASH_SIZE(rbcm->requests) == 0) {
176
- return Qtrue;
177
- } else {
178
- return Qfalse;
179
- }
180
- }
181
-
182
139
  /*
183
140
  * call-seq:
184
141
  * multi = Curl::Multi.new
@@ -241,19 +198,12 @@ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) {
241
198
  */
242
199
  VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
243
200
  CURLMcode mcode;
244
- VALUE r;
245
201
  ruby_curl_easy *rbce;
246
202
  ruby_curl_multi *rbcm;
247
203
 
248
204
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
249
205
  Data_Get_Struct(easy, ruby_curl_easy, rbce);
250
206
 
251
- // check if this curl handle has been added before adding again
252
- r = rb_hash_aref(rbcm->requests, LONG2NUM((long)rbce->curl));
253
- if ( r != Qnil ) {
254
- return Qnil;
255
- }
256
-
257
207
  /* setup the easy handle */
258
208
  ruby_curl_easy_setup( rbce );
259
209
 
@@ -271,8 +221,6 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
271
221
  /* track a reference to associated multi handle */
272
222
  rbce->multi = self;
273
223
 
274
- rb_hash_aset( rbcm->requests, LONG2NUM((long)rbce->curl), easy );
275
-
276
224
  return self;
277
225
  }
278
226
 
@@ -290,28 +238,20 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
290
238
  *
291
239
  * Will raise an exception if the easy handle is not found
292
240
  */
293
- VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
241
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
294
242
  ruby_curl_multi *rbcm;
295
243
 
296
244
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
297
245
 
298
- rb_curl_multi_remove(rbcm,easy);
246
+ rb_curl_multi_remove(rbcm, rb_easy_handle);
299
247
 
300
248
  return self;
301
249
  }
302
250
  static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
303
251
  CURLMcode result;
304
252
  ruby_curl_easy *rbce;
305
- VALUE r;
306
253
 
307
254
  Data_Get_Struct(easy, ruby_curl_easy, rbce);
308
-
309
- // check if this curl handle has been added before removing
310
- r = rb_hash_aref(rbcm->requests, LONG2NUM((long)rbce->curl));
311
- if ( r == Qnil ) {
312
- return;
313
- }
314
-
315
255
  result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
316
256
  if (result != 0) {
317
257
  raise_curl_multi_error_exception(result);
@@ -320,36 +260,6 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
320
260
  rbcm->active--;
321
261
 
322
262
  ruby_curl_easy_cleanup( easy, rbce );
323
-
324
- // active should equal LONG2NUM(RHASH(rbcm->requests)->tbl->num_entries)
325
- r = rb_hash_delete( rbcm->requests, LONG2NUM((long)rbce->curl) );
326
- if( r != easy || r == Qnil ) {
327
- rb_warn("Possibly lost track of Curl::Easy VALUE, it may not be reclaimed by GC");
328
- }
329
- }
330
-
331
- /* Hash#foreach callback for ruby_curl_multi_cancel */
332
- static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
333
- rb_curl_multi_remove(rbcm, value);
334
-
335
- return ST_CONTINUE;
336
- }
337
-
338
- /*
339
- * call-seq:
340
- * multi.cancel!
341
- *
342
- * Cancels all requests currently being made on this Curl::Multi handle.
343
- */
344
- static VALUE ruby_curl_multi_cancel(VALUE self) {
345
- ruby_curl_multi *rbcm;
346
-
347
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
348
-
349
- rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
350
-
351
- /* for chaining */
352
- return self;
353
263
  }
354
264
 
355
265
  // on_success, on_failure, on_complete
@@ -361,7 +271,6 @@ static VALUE call_status_handler2(VALUE ary) {
361
271
  }
362
272
 
363
273
  static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
364
-
365
274
  long response_code = -1;
366
275
  VALUE easy;
367
276
  ruby_curl_easy *rbce = NULL;
@@ -373,7 +282,8 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
373
282
 
374
283
  rbce->last_result = result; /* save the last easy result code */
375
284
 
376
- ruby_curl_multi_remove( self, easy );
285
+ // remove the easy handle from multi on completion so it can be reused again
286
+ rb_funcall(self, rb_intern("remove"), 1, easy);
377
287
 
378
288
  /* after running a request cleanup the headers, these are set before each request */
379
289
  if (rbce->curl_headers) {
@@ -436,26 +346,30 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
436
346
  //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
437
347
  }
438
348
 
439
- if (val == Qfalse) {
440
- rb_warn("uncaught exception from callback");
441
- }
442
-
443
349
  }
444
350
 
445
351
  static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
446
- int msgs_left, result;
447
- CURLMsg *msg;
448
- CURL *easy_handle;
449
-
450
- /* check for finished easy handles and remove from the multi handle */
451
- while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
452
- if (msg->msg == CURLMSG_DONE) {
453
- easy_handle = msg->easy_handle;
454
- result = msg->data.result;
455
- if (easy_handle) {
456
- rb_curl_mutli_handle_complete(self, easy_handle, result);
457
- }
458
- }
352
+ int msgs_left;
353
+
354
+ CURLcode c_easy_result;
355
+ CURLMsg *c_multi_result; // for picking up messages with the transfer status
356
+ CURL *c_easy_handle;
357
+
358
+ /* Check for finished easy handles and remove from the multi handle.
359
+ * curl_multi_info_read will query for messages from individual handles.
360
+ *
361
+ * The messages fetched with this function are removed from the curl internal
362
+ * queue and when there are no messages left it will return NULL (and break
363
+ * the loop effectively).
364
+ */
365
+ while ((c_multi_result = curl_multi_info_read(multi_handle, &msgs_left))) {
366
+ // A message is there, but we really care only about transfer completetion.
367
+ if (c_multi_result->msg != CURLMSG_DONE) continue;
368
+
369
+ c_easy_handle = c_multi_result->easy_handle;
370
+ c_easy_result = c_multi_result->data.result; /* return code for transfer */
371
+
372
+ rb_curl_mutli_handle_complete(self, c_easy_handle, c_easy_result);
459
373
  }
460
374
  }
461
375
 
@@ -463,14 +377,32 @@ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
463
377
  static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
464
378
  CURLMcode mcode;
465
379
 
380
+ /*
381
+ * curl_multi_perform will return CURLM_CALL_MULTI_PERFORM only when it wants to be called again immediately.
382
+ * When things are fine and there is nothing immediate it wants done, it'll return CURLM_OK.
383
+ *
384
+ * It will perform all pending actions on all added easy handles attached to this multi handle. We will loop
385
+ * here as long as mcode is CURLM_CALL_MULTIPERFORM.
386
+ */
466
387
  do {
467
388
  mcode = curl_multi_perform(multi_handle, still_running);
468
389
  } while (mcode == CURLM_CALL_MULTI_PERFORM);
469
390
 
391
+ /*
392
+ * Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
393
+ */
394
+
470
395
  if (mcode != CURLM_OK) {
471
396
  raise_curl_multi_error_exception(mcode);
472
397
  }
473
-
398
+
399
+ /*
400
+ * Everything is ok, but this does not mean all the transfers are completed.
401
+ * There is no data to read or write available for Curl at the moment.
402
+ *
403
+ * At this point we can return control to the caller to do something else while
404
+ * curl is waiting for more actions to queue.
405
+ */
474
406
  }
475
407
 
476
408
  #ifdef _WIN32
@@ -541,6 +473,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
541
473
  #endif
542
474
  long timeout_milliseconds;
543
475
  struct timeval tv = {0, 0};
476
+ struct timeval tv_100ms = {0, 100000};
544
477
  VALUE block = Qnil;
545
478
  #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
546
479
  struct _select_set fdset_args;
@@ -552,10 +485,23 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
552
485
 
553
486
  timeout_milliseconds = cCurlMutiDefaulttimeout;
554
487
 
488
+ // Run curl_multi_perform for the first time to get the ball rolling
555
489
  rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
490
+
491
+ // Check the easy handles for new messages one more time before yielding
492
+ // control to passed ruby block.
493
+ //
494
+ // This call will block until all queued messages are processed and if any
495
+ // handle completed the transfer we will run the on_complete callback here too.
556
496
  rb_curl_multi_read_info( self, rbcm->handle );
557
- if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
558
-
497
+
498
+ // There are no more messages to handle by curl and we can run the ruby block
499
+ // passed to perform method.
500
+ // When the block completes curl will resume.
501
+ if (block != Qnil) {
502
+ rb_funcall(block, rb_intern("call"), 1, self);
503
+ }
504
+
559
505
  do {
560
506
  while (rbcm->running) {
561
507
 
@@ -597,7 +543,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
597
543
 
598
544
  if (maxfd == -1) {
599
545
  /* libcurl recommends sleeping for 100ms */
600
- rb_thread_wait_for(rb_time_timeval(DBL2NUM(0.1)));
546
+ rb_thread_wait_for(tv_100ms);
601
547
  rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
602
548
  rb_curl_multi_read_info( self, rbcm->handle );
603
549
  if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
@@ -610,12 +556,15 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
610
556
  create_crt_fd(&fdexcep, &crt_fdexcep);
611
557
  #endif
612
558
 
613
- #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
559
+
560
+ #if (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
614
561
  fdset_args.maxfd = maxfd+1;
615
562
  fdset_args.fdread = &fdread;
616
563
  fdset_args.fdwrite = &fdwrite;
617
564
  fdset_args.fdexcep = &fdexcep;
618
565
  fdset_args.tv = &tv;
566
+ #endif
567
+
619
568
  #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
620
569
  rc = (int)(VALUE) rb_thread_call_without_gvl((void *(*)(void *))curb_select, &fdset_args, RUBY_UBF_IO, 0);
621
570
  #elif HAVE_RB_THREAD_BLOCKING_REGION
@@ -626,8 +575,6 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
626
575
  rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
627
576
  #endif
628
577
 
629
- #endif
630
-
631
578
  #ifdef _WIN32
632
579
  cleanup_crt_fd(&fdread, &crt_fdread);
633
580
  cleanup_crt_fd(&fdwrite, &crt_fdwrite);
@@ -653,30 +600,44 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
653
600
 
654
601
  rb_curl_multi_read_info( self, rbcm->handle );
655
602
  if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
656
-
603
+ if (cCurlMutiAutoClose == 1) {
604
+ rb_funcall(self, rb_intern("close"), 0);
605
+ }
657
606
  return Qtrue;
658
607
  }
659
608
 
609
+ /*
610
+ * call-seq:
611
+ *
612
+ * multi.close
613
+ * after closing the multi handle all connections will be closed and the handle will no longer be usable
614
+ *
615
+ */
616
+ VALUE ruby_curl_multi_close(VALUE self) {
617
+ ruby_curl_multi *rbcm;
618
+ Data_Get_Struct(self, ruby_curl_multi, rbcm);
619
+ curl_multi_cleanup(rbcm->handle);
620
+ ruby_curl_multi_init(rbcm);
621
+ return self;
622
+ }
623
+
624
+
660
625
  /* =================== INIT LIB =====================*/
661
626
  void init_curb_multi() {
662
627
  idCall = rb_intern("call");
663
-
664
628
  cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
665
629
 
666
630
  /* Class methods */
667
631
  rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
668
632
  rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
669
633
  rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
670
-
671
- /* "Attributes" */
672
- rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
673
- rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
674
-
634
+ rb_define_singleton_method(cCurlMulti, "autoclose=", ruby_curl_multi_set_autoclose, 1);
635
+ rb_define_singleton_method(cCurlMulti, "autoclose", ruby_curl_multi_get_autoclose, 0);
675
636
  /* Instance methods */
676
637
  rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
677
638
  rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
678
- rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
679
- rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
680
- rb_define_method(cCurlMulti, "cancel!", ruby_curl_multi_cancel, 0);
639
+ rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
640
+ rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
681
641
  rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
642
+ rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
682
643
  }