curb 0.7.15 → 1.0.0

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
@@ -3,15 +3,18 @@
3
3
  * Licensed under the Ruby License. See LICENSE for details.
4
4
  *
5
5
  */
6
-
7
6
  #include "curb_config.h"
8
- #ifdef HAVE_RUBY19_ST_H
9
- #include <ruby.h>
7
+ #include <ruby.h>
8
+ #ifdef HAVE_RUBY_ST_H
10
9
  #include <ruby/st.h>
11
10
  #else
12
- #include <ruby.h>
13
11
  #include <st.h>
14
12
  #endif
13
+
14
+ #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
15
+ #include <ruby/thread.h>
16
+ #endif
17
+
15
18
  #include "curb_easy.h"
16
19
  #include "curb_errors.h"
17
20
  #include "curb_postfield.h"
@@ -34,47 +37,29 @@ static VALUE idCall;
34
37
  VALUE cCurlMulti;
35
38
 
36
39
  static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
40
+ static char cCurlMutiAutoClose = 0;
37
41
 
38
42
  static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
39
43
  static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
40
44
  static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
41
45
 
42
- static void rb_curl_multi_mark_all_easy(VALUE key, VALUE rbeasy, ruby_curl_multi *rbcm) {
43
- rb_gc_mark(rbeasy);
46
+ static VALUE callback_exception(VALUE unused) {
47
+ return Qfalse;
44
48
  }
45
49
 
46
- static void curl_multi_mark(ruby_curl_multi *rbcm) {
47
- rb_gc_mark(rbcm->requests);
48
- rb_hash_foreach( rbcm->requests, (int (*)())rb_curl_multi_mark_all_easy, (VALUE)rbcm );
50
+ void curl_multi_free(ruby_curl_multi *rbcm) {
51
+ curl_multi_cleanup(rbcm->handle);
52
+ free(rbcm);
49
53
  }
50
54
 
51
- static void curl_multi_flush_easy(VALUE key, VALUE easy, ruby_curl_multi *rbcm) {
52
- CURLMcode result;
53
- ruby_curl_easy *rbce;
54
-
55
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
56
- result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
57
- if (result != 0) {
58
- raise_curl_multi_error_exception(result);
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");
59
59
  }
60
- }
61
-
62
- static int
63
- rb_hash_clear_i(VALUE key, VALUE value, VALUE dummy) {
64
- return ST_DELETE;
65
- }
66
-
67
- static void curl_multi_free(ruby_curl_multi *rbcm) {
68
60
 
69
- if (rbcm && !rbcm->requests == Qnil && rb_type(rbcm->requests) == T_HASH && RHASH_LEN(rbcm->requests) > 0) {
70
-
71
- rb_hash_foreach( rbcm->requests, (int (*)())curl_multi_flush_easy, (VALUE)rbcm );
72
-
73
- rb_hash_foreach(rbcm->requests, rb_hash_clear_i, 0); //rb_hash_clear(rbcm->requests);
74
- rbcm->requests = Qnil;
75
- }
76
- curl_multi_cleanup(rbcm->handle);
77
- free(rbcm);
61
+ rbcm->active = 0;
62
+ rbcm->running = 0;
78
63
  }
79
64
 
80
65
  /*
@@ -84,23 +69,17 @@ static void curl_multi_free(ruby_curl_multi *rbcm) {
84
69
  * Create a new Curl::Multi instance
85
70
  */
86
71
  VALUE ruby_curl_multi_new(VALUE klass) {
87
- VALUE new_curlm;
88
-
89
72
  ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
90
73
 
91
- rbcm->handle = curl_multi_init();
92
- if (!rbcm->handle) {
93
- rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
94
- }
95
-
96
- rbcm->requests = rb_hash_new();
97
-
98
- rbcm->active = 0;
99
- rbcm->running = 0;
100
-
101
- new_curlm = Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
74
+ ruby_curl_multi_init(rbcm);
102
75
 
103
- 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);
104
83
  }
105
84
 
106
85
  /*
@@ -112,7 +91,7 @@ VALUE ruby_curl_multi_new(VALUE klass) {
112
91
  *
113
92
  */
114
93
  VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
115
- cCurlMutiDefaulttimeout = FIX2LONG(timeout);
94
+ cCurlMutiDefaulttimeout = NUM2LONG(timeout);
116
95
  return timeout;
117
96
  }
118
97
 
@@ -124,55 +103,39 @@ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
124
103
  *
125
104
  */
126
105
  VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
127
- return INT2FIX(cCurlMutiDefaulttimeout);
106
+ return LONG2NUM(cCurlMutiDefaulttimeout);
128
107
  }
129
108
 
130
- /* Hash#foreach callback for ruby_curl_multi_requests */
131
- static int ruby_curl_multi_requests_callback(VALUE key, VALUE value, VALUE result_array) {
132
- rb_ary_push(result_array, value);
133
-
134
- 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;
135
120
  }
136
121
 
137
122
  /*
138
123
  * call-seq:
139
- * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
140
- *
141
- * 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
+ *
142
128
  */
143
- static VALUE ruby_curl_multi_requests(VALUE self) {
144
- ruby_curl_multi *rbcm;
145
- VALUE result_array;
146
-
147
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
148
-
149
- result_array = rb_ary_new();
150
-
151
- /* iterate over the requests hash, and stuff references into the array. */
152
- rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
153
-
154
- return result_array;
129
+ VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
130
+ return cCurlMutiAutoClose == 1 ? Qtrue : Qfalse;
155
131
  }
156
132
 
157
133
  /*
158
134
  * call-seq:
159
- * multi.idle? => true or false
135
+ * multi.requests => [#&lt;Curl::Easy...&gt;, ...]
160
136
  *
161
- * Returns whether or not this Curl::Multi handle is processing any requests. E.g. this returns
162
- * true when multi.requests.length == 0.
137
+ * Returns an array containing all the active requests on this Curl::Multi object.
163
138
  */
164
- static VALUE ruby_curl_multi_idle(VALUE self) {
165
- ruby_curl_multi *rbcm;
166
-
167
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
168
-
169
- if ( FIX2INT( rb_funcall(rbcm->requests, rb_intern("length"), 0) ) == 0 ) {
170
- return Qtrue;
171
- } else {
172
- return Qfalse;
173
- }
174
- }
175
-
176
139
  /*
177
140
  * call-seq:
178
141
  * multi = Curl::Multi.new
@@ -185,7 +148,8 @@ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
185
148
  ruby_curl_multi *rbcm;
186
149
 
187
150
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
188
- curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2INT(count));
151
+
152
+ curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2LONG(count));
189
153
  #endif
190
154
 
191
155
  return count;
@@ -196,20 +160,31 @@ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
196
160
  * multi = Curl::Multi.new
197
161
  * multi.pipeline = true
198
162
  *
199
- * Pass a long set to 1 to enable or 0 to disable. Enabling pipelining on a multi handle will make it
200
- * attempt to perform HTTP Pipelining as far as possible for transfers using this handle. This means
201
- * that if you add a second request that can use an already existing connection, the second request will
202
- * be "piped" on the same connection rather than being executed in parallel. (Added in 7.16.0)
163
+ * Pass a long set to 1 for HTTP/1.1 pipelining, 2 for HTTP/2 multiplexing, or 0 to disable.
164
+ * Enabling pipelining on a multi handle will make it attempt to perform HTTP Pipelining as
165
+ * far as possible for transfers using this handle. This means that if you add a second request
166
+ * that can use an already existing connection, the second request will be "piped" on the same
167
+ * connection rather than being executed in parallel. (Added in 7.16.0, multiplex added in 7.43.0)
203
168
  *
204
169
  */
205
- static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE onoff) {
170
+ static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) {
206
171
  #ifdef HAVE_CURLMOPT_PIPELINING
207
172
  ruby_curl_multi *rbcm;
208
173
 
174
+ long value;
175
+
176
+ if (method == Qtrue) {
177
+ value = 1;
178
+ } else if (method == Qfalse) {
179
+ value = 0;
180
+ } else {
181
+ value = NUM2LONG(method);
182
+ }
183
+
209
184
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
210
- curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, onoff == Qtrue ? 1 : 0);
185
+ curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, value);
211
186
  #endif
212
- return onoff;
187
+ return method == Qtrue ? 1 : 0;
213
188
  }
214
189
 
215
190
  /*
@@ -243,9 +218,8 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
243
218
  * If this number is not correct, the next call to curl_multi_perform will correct it. */
244
219
  rbcm->running++;
245
220
 
246
- rb_hash_aset( rbcm->requests, easy, easy );
247
-
248
- rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
221
+ /* track a reference to associated multi handle */
222
+ rbce->multi = self;
249
223
 
250
224
  return self;
251
225
  }
@@ -264,25 +238,20 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
264
238
  *
265
239
  * Will raise an exception if the easy handle is not found
266
240
  */
267
- VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
241
+ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
268
242
  ruby_curl_multi *rbcm;
269
- ruby_curl_easy *rbce;
270
243
 
271
244
  Data_Get_Struct(self, ruby_curl_multi, rbcm);
272
245
 
273
- Data_Get_Struct(easy, ruby_curl_easy, rbce);
274
-
275
- rb_curl_multi_remove(rbcm,easy);
246
+ rb_curl_multi_remove(rbcm, rb_easy_handle);
276
247
 
277
248
  return self;
278
249
  }
279
250
  static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
280
251
  CURLMcode result;
281
252
  ruby_curl_easy *rbce;
282
- VALUE r;
283
253
 
284
254
  Data_Get_Struct(easy, ruby_curl_easy, rbce);
285
-
286
255
  result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
287
256
  if (result != 0) {
288
257
  raise_curl_multi_error_exception(result);
@@ -291,43 +260,21 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
291
260
  rbcm->active--;
292
261
 
293
262
  ruby_curl_easy_cleanup( easy, rbce );
294
-
295
- // active should equal INT2FIX(RHASH(rbcm->requests)->tbl->num_entries)
296
- r = rb_hash_delete( rbcm->requests, easy );
297
- if( r != easy || r == Qnil ) {
298
- rb_warn("Possibly lost track of Curl::Easy VALUE, it may not be reclaimed by GC");
299
- }
300
263
  }
301
264
 
302
- /* Hash#foreach callback for ruby_curl_multi_cancel */
303
- static int ruby_curl_multi_cancel_callback(VALUE key, VALUE value, ruby_curl_multi *rbcm) {
304
- rb_curl_multi_remove(rbcm, value);
305
-
306
- return ST_CONTINUE;
265
+ // on_success, on_failure, on_complete
266
+ static VALUE call_status_handler1(VALUE ary) {
267
+ return rb_funcall(rb_ary_entry(ary, 0), idCall, 1, rb_ary_entry(ary, 1));
307
268
  }
308
-
309
- /*
310
- * call-seq:
311
- * multi.cancel!
312
- *
313
- * Cancels all requests currently being made on this Curl::Multi handle.
314
- */
315
- static VALUE ruby_curl_multi_cancel(VALUE self) {
316
- ruby_curl_multi *rbcm;
317
-
318
- Data_Get_Struct(self, ruby_curl_multi, rbcm);
319
-
320
- rb_hash_foreach( rbcm->requests, ruby_curl_multi_cancel_callback, (VALUE)rbcm );
321
-
322
- /* for chaining */
323
- return self;
269
+ static VALUE call_status_handler2(VALUE ary) {
270
+ return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2));
324
271
  }
325
272
 
326
273
  static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
327
-
328
274
  long response_code = -1;
329
275
  VALUE easy;
330
276
  ruby_curl_easy *rbce = NULL;
277
+ VALUE callargs, val = Qtrue;
331
278
 
332
279
  CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
333
280
 
@@ -335,7 +282,8 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
335
282
 
336
283
  rbce->last_result = result; /* save the last easy result code */
337
284
 
338
- 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);
339
287
 
340
288
  /* after running a request cleanup the headers, these are set before each request */
341
289
  if (rbce->curl_headers) {
@@ -348,42 +296,80 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
348
296
  }
349
297
 
350
298
  if (!rb_easy_nil("complete_proc")) {
351
- rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
299
+ callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), easy);
300
+ rbce->callback_active = 1;
301
+ val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
302
+ rbce->callback_active = 0;
303
+ //rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
352
304
  }
353
305
 
354
306
  curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
355
307
 
356
308
  if (result != 0) {
357
309
  if (!rb_easy_nil("failure_proc")) {
358
- rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
310
+ callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
311
+ rbce->callback_active = 1;
312
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
313
+ rbce->callback_active = 0;
314
+ //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
359
315
  }
360
316
  }
361
317
  else if (!rb_easy_nil("success_proc") &&
362
318
  ((response_code >= 200 && response_code < 300) || response_code == 0)) {
363
319
  /* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
364
- rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
320
+ callargs = rb_ary_new3(2, rb_easy_get("success_proc"), easy);
321
+ rbce->callback_active = 1;
322
+ val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
323
+ rbce->callback_active = 0;
324
+ //rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
325
+ }
326
+ else if (!rb_easy_nil("redirect_proc") &&
327
+ (response_code >= 300 && response_code < 400)) {
328
+ rbce->callback_active = 1;
329
+ callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
330
+ rbce->callback_active = 0;
331
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
332
+ }
333
+ else if (!rb_easy_nil("missing_proc") &&
334
+ (response_code >= 400 && response_code < 500)) {
335
+ rbce->callback_active = 1;
336
+ callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), easy, rb_curl_easy_error(result));
337
+ rbce->callback_active = 0;
338
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
365
339
  }
366
340
  else if (!rb_easy_nil("failure_proc") &&
367
- (response_code >= 300 && response_code <= 999)) {
368
- rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
341
+ (response_code >= 500 && response_code <= 999)) {
342
+ callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
343
+ rbce->callback_active = 1;
344
+ val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
345
+ rbce->callback_active = 0;
346
+ //rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
369
347
  }
370
348
 
371
349
  }
372
350
 
373
351
  static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
374
- int msgs_left, result;
375
- CURLMsg *msg;
376
- CURL *easy_handle;
377
-
378
- /* check for finished easy handles and remove from the multi handle */
379
- while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
380
- if (msg->msg == CURLMSG_DONE) {
381
- easy_handle = msg->easy_handle;
382
- result = msg->data.result;
383
- if (easy_handle) {
384
- rb_curl_mutli_handle_complete(self, easy_handle, result);
385
- }
386
- }
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);
387
373
  }
388
374
  }
389
375
 
@@ -391,14 +377,32 @@ static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
391
377
  static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
392
378
  CURLMcode mcode;
393
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
+ */
394
387
  do {
395
388
  mcode = curl_multi_perform(multi_handle, still_running);
396
389
  } while (mcode == CURLM_CALL_MULTI_PERFORM);
397
390
 
391
+ /*
392
+ * Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
393
+ */
394
+
398
395
  if (mcode != CURLM_OK) {
399
396
  raise_curl_multi_error_exception(mcode);
400
397
  }
401
-
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
+ */
402
406
  }
403
407
 
404
408
  #ifdef _WIN32
@@ -430,6 +434,20 @@ void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set)
430
434
  }
431
435
  #endif
432
436
 
437
+ #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
438
+ struct _select_set {
439
+ int maxfd;
440
+ fd_set *fdread, *fdwrite, *fdexcep;
441
+ struct timeval *tv;
442
+ };
443
+
444
+ static VALUE curb_select(void *args) {
445
+ struct _select_set* set = args;
446
+ int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
447
+ return INT2FIX(rc);
448
+ }
449
+ #endif
450
+
433
451
  /*
434
452
  * call-seq:
435
453
  * multi = Curl::Multi.new
@@ -448,15 +466,18 @@ void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set)
448
466
  VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
449
467
  CURLMcode mcode;
450
468
  ruby_curl_multi *rbcm;
451
- int maxfd, rc;
469
+ int maxfd, rc = -1;
452
470
  fd_set fdread, fdwrite, fdexcep;
453
471
  #ifdef _WIN32
454
472
  fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
455
473
  #endif
456
-
457
474
  long timeout_milliseconds;
458
475
  struct timeval tv = {0, 0};
476
+ struct timeval tv_100ms = {0, 100000};
459
477
  VALUE block = Qnil;
478
+ #if defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
479
+ struct _select_set fdset_args;
480
+ #endif
460
481
 
461
482
  rb_scan_args(argc, argv, "0&", &block);
462
483
 
@@ -464,8 +485,23 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
464
485
 
465
486
  timeout_milliseconds = cCurlMutiDefaulttimeout;
466
487
 
488
+ // Run curl_multi_perform for the first time to get the ball rolling
467
489
  rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
468
-
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.
496
+ rb_curl_multi_read_info( self, rbcm->handle );
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
+
469
505
  do {
470
506
  while (rbcm->running) {
471
507
 
@@ -505,13 +541,39 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
505
541
  raise_curl_multi_error_exception(mcode);
506
542
  }
507
543
 
544
+ if (maxfd == -1) {
545
+ /* libcurl recommends sleeping for 100ms */
546
+ rb_thread_wait_for(tv_100ms);
547
+ rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
548
+ rb_curl_multi_read_info( self, rbcm->handle );
549
+ if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
550
+ continue;
551
+ }
552
+
508
553
  #ifdef _WIN32
509
554
  create_crt_fd(&fdread, &crt_fdread);
510
555
  create_crt_fd(&fdwrite, &crt_fdwrite);
511
556
  create_crt_fd(&fdexcep, &crt_fdexcep);
512
557
  #endif
513
558
 
559
+
560
+ #if (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
561
+ fdset_args.maxfd = maxfd+1;
562
+ fdset_args.fdread = &fdread;
563
+ fdset_args.fdwrite = &fdwrite;
564
+ fdset_args.fdexcep = &fdexcep;
565
+ fdset_args.tv = &tv;
566
+ #endif
567
+
568
+ #ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
569
+ rc = (int)(VALUE) rb_thread_call_without_gvl((void *(*)(void *))curb_select, &fdset_args, RUBY_UBF_IO, 0);
570
+ #elif HAVE_RB_THREAD_BLOCKING_REGION
571
+ rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
572
+ #elif HAVE_RB_THREAD_FD_SELECT
573
+ rc = rb_thread_fd_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
574
+ #else
514
575
  rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
576
+ #endif
515
577
 
516
578
  #ifdef _WIN32
517
579
  cleanup_crt_fd(&fdread, &crt_fdread);
@@ -521,8 +583,10 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
521
583
 
522
584
  switch(rc) {
523
585
  case -1:
524
- rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
525
- break;
586
+ if(errno != EINTR) {
587
+ rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
588
+ break;
589
+ }
526
590
  case 0: /* timeout */
527
591
  default: /* action */
528
592
  rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
@@ -536,30 +600,44 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
536
600
 
537
601
  rb_curl_multi_read_info( self, rbcm->handle );
538
602
  if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
539
-
603
+ if (cCurlMutiAutoClose == 1) {
604
+ rb_funcall(self, rb_intern("close"), 0);
605
+ }
540
606
  return Qtrue;
541
607
  }
542
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
+
543
625
  /* =================== INIT LIB =====================*/
544
626
  void init_curb_multi() {
545
627
  idCall = rb_intern("call");
546
-
547
628
  cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
548
629
 
549
630
  /* Class methods */
550
631
  rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
551
632
  rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
552
633
  rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
553
-
554
- /* "Attributes" */
555
- rb_define_method(cCurlMulti, "requests", ruby_curl_multi_requests, 0);
556
- rb_define_method(cCurlMulti, "idle?", ruby_curl_multi_idle, 0);
557
-
558
- /* Instnace methods */
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);
636
+ /* Instance methods */
559
637
  rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
560
638
  rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
561
- rb_define_method(cCurlMulti, "add", ruby_curl_multi_add, 1);
562
- rb_define_method(cCurlMulti, "remove", ruby_curl_multi_remove, 1);
563
- 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);
564
641
  rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
642
+ rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
565
643
  }
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
 
data/ext/curb_upload.c CHANGED
@@ -56,7 +56,7 @@ VALUE ruby_curl_upload_stream_get(VALUE self) {
56
56
  VALUE ruby_curl_upload_offset_set(VALUE self, VALUE offset) {
57
57
  ruby_curl_upload *rbcu;
58
58
  Data_Get_Struct(self, ruby_curl_upload, rbcu);
59
- rbcu->offset = FIX2LONG(offset);
59
+ rbcu->offset = NUM2LONG(offset);
60
60
  return offset;
61
61
  }
62
62
  /*
@@ -66,7 +66,7 @@ VALUE ruby_curl_upload_offset_set(VALUE self, VALUE offset) {
66
66
  VALUE ruby_curl_upload_offset_get(VALUE self) {
67
67
  ruby_curl_upload *rbcu;
68
68
  Data_Get_Struct(self, ruby_curl_upload, rbcu);
69
- return INT2FIX(rbcu->offset);
69
+ return LONG2NUM(rbcu->offset);
70
70
  }
71
71
 
72
72
  /* =================== INIT LIB =====================*/