curb 1.1.0 → 1.2.1
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.
- checksums.yaml +4 -4
- data/README.md +75 -2
- data/ext/curb.c +11 -4
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +236 -18
- data/ext/curb_easy.h +1 -0
- data/ext/curb_multi.c +595 -20
- data/ext/curb_multi.h +4 -1
- data/ext/curb_postfield.c +6 -0
- data/ext/curb_upload.c +3 -0
- data/ext/extconf.rb +215 -17
- data/lib/curl/easy.rb +56 -5
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/helper.rb +33 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl_download.rb +2 -1
- data/tests/tc_curl_easy.rb +65 -6
- data/tests/tc_curl_multi.rb +31 -0
- data/tests/tc_fiber_scheduler.rb +190 -0
- metadata +10 -6
- data/tests/bug_issue277.rb +0 -32
- data/tests/bug_resolve.rb +0 -15
data/ext/curb_multi.c
CHANGED
@@ -5,6 +5,9 @@
|
|
5
5
|
*/
|
6
6
|
#include "curb_config.h"
|
7
7
|
#include <ruby.h>
|
8
|
+
#ifdef HAVE_RUBY_IO_H
|
9
|
+
#include <ruby/io.h>
|
10
|
+
#endif
|
8
11
|
#ifdef HAVE_RUBY_ST_H
|
9
12
|
#include <ruby/st.h>
|
10
13
|
#else
|
@@ -14,6 +17,9 @@
|
|
14
17
|
#ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
|
15
18
|
#include <ruby/thread.h>
|
16
19
|
#endif
|
20
|
+
#ifdef HAVE_RUBY_FIBER_SCHEDULER_H
|
21
|
+
#include <ruby/fiber/scheduler.h>
|
22
|
+
#endif
|
17
23
|
|
18
24
|
#include "curb_easy.h"
|
19
25
|
#include "curb_errors.h"
|
@@ -21,13 +27,25 @@
|
|
21
27
|
#include "curb_multi.h"
|
22
28
|
|
23
29
|
#include <errno.h>
|
30
|
+
#include <stdarg.h>
|
31
|
+
|
32
|
+
/*
|
33
|
+
* Optional socket-action debug logging. Enabled by defining CURB_SOCKET_DEBUG=1
|
34
|
+
* at compile time (e.g. via environment variable passed to extconf.rb).
|
35
|
+
*/
|
36
|
+
#ifndef CURB_SOCKET_DEBUG
|
37
|
+
#define CURB_SOCKET_DEBUG 0
|
38
|
+
#endif
|
39
|
+
#if !CURB_SOCKET_DEBUG
|
40
|
+
#define curb_debugf(...) ((void)0)
|
41
|
+
#endif
|
24
42
|
|
25
43
|
#ifdef _WIN32
|
26
44
|
// for O_RDWR and O_BINARY
|
27
45
|
#include <fcntl.h>
|
28
46
|
#endif
|
29
47
|
|
30
|
-
#
|
48
|
+
#if 0 /* disabled curl_multi_wait in favor of scheduler-aware fdsets */
|
31
49
|
#include <stdint.h> /* for intptr_t */
|
32
50
|
|
33
51
|
struct wait_args {
|
@@ -58,6 +76,10 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
|
|
58
76
|
static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
|
59
77
|
static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
|
60
78
|
|
79
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg);
|
80
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm);
|
81
|
+
static void curl_multi_mark(void *ptr);
|
82
|
+
|
61
83
|
static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
62
84
|
// TODO: we could have an option to enable exception reporting
|
63
85
|
/* VALUE ret = rb_funcall(exception, rb_intern("message"), 0);
|
@@ -79,8 +101,67 @@ static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
|
79
101
|
return exception;
|
80
102
|
}
|
81
103
|
|
104
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg) {
|
105
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)arg;
|
106
|
+
VALUE easy = (VALUE)val;
|
107
|
+
ruby_curl_easy *rbce = NULL;
|
108
|
+
|
109
|
+
if (RB_TYPE_P(easy, T_DATA)) {
|
110
|
+
Data_Get_Struct(easy, ruby_curl_easy, rbce);
|
111
|
+
}
|
112
|
+
|
113
|
+
if (!rbce) {
|
114
|
+
return ST_CONTINUE;
|
115
|
+
}
|
116
|
+
|
117
|
+
if (rbcm && rbcm->handle && rbce->curl) {
|
118
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
119
|
+
}
|
120
|
+
|
121
|
+
rbce->multi = Qnil;
|
122
|
+
|
123
|
+
return ST_CONTINUE;
|
124
|
+
}
|
125
|
+
|
126
|
+
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
127
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
128
|
+
|
129
|
+
if (!rbcm || !rbce || !rbcm->attached) {
|
130
|
+
return;
|
131
|
+
}
|
132
|
+
|
133
|
+
st_data_t key = (st_data_t)rbce;
|
134
|
+
st_delete(rbcm->attached, &key, NULL);
|
135
|
+
}
|
136
|
+
|
137
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm) {
|
138
|
+
if (!rbcm || !rbcm->attached) {
|
139
|
+
return;
|
140
|
+
}
|
141
|
+
|
142
|
+
st_table *attached = rbcm->attached;
|
143
|
+
rbcm->attached = NULL;
|
144
|
+
|
145
|
+
st_foreach(attached, detach_easy_entry, (st_data_t)rbcm);
|
146
|
+
|
147
|
+
st_free_table(attached);
|
148
|
+
|
149
|
+
rbcm->active = 0;
|
150
|
+
rbcm->running = 0;
|
151
|
+
}
|
152
|
+
|
82
153
|
void curl_multi_free(ruby_curl_multi *rbcm) {
|
83
|
-
|
154
|
+
if (!rbcm) {
|
155
|
+
return;
|
156
|
+
}
|
157
|
+
|
158
|
+
rb_curl_multi_detach_all(rbcm);
|
159
|
+
|
160
|
+
if (rbcm->handle) {
|
161
|
+
curl_multi_cleanup(rbcm->handle);
|
162
|
+
rbcm->handle = NULL;
|
163
|
+
}
|
164
|
+
|
84
165
|
free(rbcm);
|
85
166
|
}
|
86
167
|
|
@@ -92,6 +173,18 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
92
173
|
|
93
174
|
rbcm->active = 0;
|
94
175
|
rbcm->running = 0;
|
176
|
+
|
177
|
+
if (rbcm->attached) {
|
178
|
+
st_free_table(rbcm->attached);
|
179
|
+
rbcm->attached = NULL;
|
180
|
+
}
|
181
|
+
|
182
|
+
rbcm->attached = st_init_numtable();
|
183
|
+
if (!rbcm->attached) {
|
184
|
+
curl_multi_cleanup(rbcm->handle);
|
185
|
+
rbcm->handle = NULL;
|
186
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
187
|
+
}
|
95
188
|
}
|
96
189
|
|
97
190
|
/*
|
@@ -102,6 +195,11 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
102
195
|
*/
|
103
196
|
VALUE ruby_curl_multi_new(VALUE klass) {
|
104
197
|
ruby_curl_multi *rbcm = ALLOC(ruby_curl_multi);
|
198
|
+
if (!rbcm) {
|
199
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Multi");
|
200
|
+
}
|
201
|
+
|
202
|
+
MEMZERO(rbcm, ruby_curl_multi, 1);
|
105
203
|
|
106
204
|
ruby_curl_multi_init(rbcm);
|
107
205
|
|
@@ -110,8 +208,8 @@ VALUE ruby_curl_multi_new(VALUE klass) {
|
|
110
208
|
* If your structure references other Ruby objects, then your mark function needs to
|
111
209
|
* identify these objects using rb_gc_mark(value). If the structure doesn't reference
|
112
210
|
* other Ruby objects, you can simply pass 0 as a function pointer.
|
113
|
-
|
114
|
-
return Data_Wrap_Struct(klass,
|
211
|
+
*/
|
212
|
+
return Data_Wrap_Struct(klass, curl_multi_mark, curl_multi_free, rbcm);
|
115
213
|
}
|
116
214
|
|
117
215
|
/*
|
@@ -271,6 +369,17 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
271
369
|
* If this number is not correct, the next call to curl_multi_perform will correct it. */
|
272
370
|
rbcm->running++;
|
273
371
|
|
372
|
+
if (!rbcm->attached) {
|
373
|
+
rbcm->attached = st_init_numtable();
|
374
|
+
if (!rbcm->attached) {
|
375
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
376
|
+
ruby_curl_easy_cleanup(easy, rbce);
|
377
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
378
|
+
}
|
379
|
+
}
|
380
|
+
|
381
|
+
st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy);
|
382
|
+
|
274
383
|
/* track a reference to associated multi handle */
|
275
384
|
rbce->multi = self;
|
276
385
|
|
@@ -311,9 +420,13 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
|
|
311
420
|
raise_curl_multi_error_exception(result);
|
312
421
|
}
|
313
422
|
|
314
|
-
rbcm->active
|
423
|
+
if (rbcm->active > 0) {
|
424
|
+
rbcm->active--;
|
425
|
+
}
|
315
426
|
|
316
427
|
ruby_curl_easy_cleanup( easy, rbce );
|
428
|
+
|
429
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
317
430
|
}
|
318
431
|
|
319
432
|
// on_success, on_failure, on_complete
|
@@ -324,6 +437,23 @@ static VALUE call_status_handler2(VALUE ary) {
|
|
324
437
|
return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2));
|
325
438
|
}
|
326
439
|
|
440
|
+
static void flush_stderr_if_any(ruby_curl_easy *rbce) {
|
441
|
+
VALUE stderr_io = rb_easy_get("stderr_io");
|
442
|
+
if (stderr_io != Qnil) {
|
443
|
+
/* Flush via Ruby IO API */
|
444
|
+
rb_funcall(stderr_io, rb_intern("flush"), 0);
|
445
|
+
#ifdef HAVE_RUBY_IO_H
|
446
|
+
/* Additionally flush underlying FILE* to be extra safe. */
|
447
|
+
rb_io_t *open_f_ptr;
|
448
|
+
if (RB_TYPE_P(stderr_io, T_FILE)) {
|
449
|
+
GetOpenFile(stderr_io, open_f_ptr);
|
450
|
+
FILE *fp = rb_io_stdio_file(open_f_ptr);
|
451
|
+
if (fp) fflush(fp);
|
452
|
+
}
|
453
|
+
#endif
|
454
|
+
}
|
455
|
+
}
|
456
|
+
|
327
457
|
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
|
328
458
|
long response_code = -1;
|
329
459
|
VALUE easy;
|
@@ -336,6 +466,10 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
336
466
|
|
337
467
|
rbce->last_result = result; /* save the last easy result code */
|
338
468
|
|
469
|
+
/* Ensure any verbose output redirected via CURLOPT_STDERR is flushed
|
470
|
+
* before we tear down handler state. */
|
471
|
+
flush_stderr_if_any(rbce);
|
472
|
+
|
339
473
|
// remove the easy handle from multi on completion so it can be reused again
|
340
474
|
rb_funcall(self, rb_intern("remove"), 1, easy);
|
341
475
|
|
@@ -345,6 +479,9 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
345
479
|
rbce->curl_headers = NULL;
|
346
480
|
}
|
347
481
|
|
482
|
+
/* Flush again after removal to cover any last buffered data. */
|
483
|
+
flush_stderr_if_any(rbce);
|
484
|
+
|
348
485
|
if (ecode != 0) {
|
349
486
|
raise_curl_easy_error_exception(ecode);
|
350
487
|
}
|
@@ -361,7 +498,7 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
361
498
|
|
362
499
|
#ifdef HAVE_CURLINFO_RESPONSE_CODE
|
363
500
|
curl_easy_getinfo(rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
364
|
-
#else
|
501
|
+
#else /* use fdsets path for waiting */
|
365
502
|
// old libcurl
|
366
503
|
curl_easy_getinfo(rbce->curl, CURLINFO_HTTP_CODE, &response_code);
|
367
504
|
#endif
|
@@ -386,11 +523,16 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
386
523
|
CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
|
387
524
|
|
388
525
|
} else if (!rb_easy_nil("redirect_proc") && ((response_code >= 300 && response_code < 400) || redirect_count > 0) ) {
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
526
|
+
/* Skip on_redirect callback if follow_location is false AND max_redirects is 0 */
|
527
|
+
if (!rbce->follow_location && rbce->max_redirs == 0) {
|
528
|
+
// Do nothing - skip the callback
|
529
|
+
} else {
|
530
|
+
rbce->callback_active = 1;
|
531
|
+
callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
|
532
|
+
rbce->callback_active = 0;
|
533
|
+
rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
|
534
|
+
CURB_CHECK_RB_CALLBACK_RAISE(did_raise);
|
535
|
+
}
|
394
536
|
} else if (!rb_easy_nil("missing_proc") &&
|
395
537
|
(response_code >= 400 && response_code < 500)) {
|
396
538
|
rbce->callback_active = 1;
|
@@ -466,6 +608,367 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
466
608
|
*/
|
467
609
|
}
|
468
610
|
|
611
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
612
|
+
/* ---- socket-action implementation (scheduler-friendly) ---- */
|
613
|
+
typedef struct {
|
614
|
+
st_table *sock_map; /* key: int fd, value: int 'what' (CURL_POLL_*) */
|
615
|
+
long timeout_ms; /* last timeout set by libcurl timer callback */
|
616
|
+
} multi_socket_ctx;
|
617
|
+
|
618
|
+
#if CURB_SOCKET_DEBUG
|
619
|
+
static void curb_debugf(const char *fmt, ...) {
|
620
|
+
va_list ap;
|
621
|
+
va_start(ap, fmt);
|
622
|
+
vfprintf(stderr, fmt, ap);
|
623
|
+
fputc('\n', stderr);
|
624
|
+
fflush(stderr);
|
625
|
+
va_end(ap);
|
626
|
+
}
|
627
|
+
|
628
|
+
static const char *poll_what_str(int what, char *buf, size_t n) {
|
629
|
+
/* what is one of CURL_POLL_*, not a bitmask except INOUT */
|
630
|
+
if (what == CURL_POLL_REMOVE) snprintf(buf, n, "REMOVE");
|
631
|
+
else if (what == CURL_POLL_IN) snprintf(buf, n, "IN");
|
632
|
+
else if (what == CURL_POLL_OUT) snprintf(buf, n, "OUT");
|
633
|
+
else if (what == CURL_POLL_INOUT) snprintf(buf, n, "INOUT");
|
634
|
+
else snprintf(buf, n, "WHAT=%d", what);
|
635
|
+
return buf;
|
636
|
+
}
|
637
|
+
|
638
|
+
static const char *cselect_flags_str(int flags, char *buf, size_t n) {
|
639
|
+
char tmp[32]; tmp[0] = 0;
|
640
|
+
int off = 0;
|
641
|
+
if (flags & CURL_CSELECT_IN) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sIN", off?"|":"");
|
642
|
+
if (flags & CURL_CSELECT_OUT) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sOUT", off?"|":"");
|
643
|
+
if (flags & CURL_CSELECT_ERR) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sERR", off?"|":"");
|
644
|
+
if (off == 0) snprintf(tmp, sizeof(tmp), "0");
|
645
|
+
snprintf(buf, n, "%s", tmp);
|
646
|
+
return buf;
|
647
|
+
}
|
648
|
+
#else
|
649
|
+
#define poll_what_str(...) ""
|
650
|
+
#define cselect_flags_str(...) ""
|
651
|
+
#endif
|
652
|
+
|
653
|
+
/* Protected call to rb_fiber_scheduler_io_wait to avoid unwinding into C on TypeError. */
|
654
|
+
struct fiber_io_wait_args { VALUE scheduler; VALUE io; int events; VALUE timeout; };
|
655
|
+
static VALUE fiber_io_wait_protected(VALUE argp) {
|
656
|
+
struct fiber_io_wait_args *a = (struct fiber_io_wait_args *)argp;
|
657
|
+
return rb_fiber_scheduler_io_wait(a->scheduler, a->io, a->events, a->timeout);
|
658
|
+
}
|
659
|
+
|
660
|
+
static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp) {
|
661
|
+
multi_socket_ctx *ctx = (multi_socket_ctx *)userp;
|
662
|
+
(void)easy; (void)socketp;
|
663
|
+
int fd = (int)s;
|
664
|
+
|
665
|
+
if (!ctx || !ctx->sock_map) return 0;
|
666
|
+
|
667
|
+
if (what == CURL_POLL_REMOVE) {
|
668
|
+
st_data_t k = (st_data_t)fd;
|
669
|
+
st_data_t rec;
|
670
|
+
st_delete(ctx->sock_map, &k, &rec);
|
671
|
+
{
|
672
|
+
char b[16];
|
673
|
+
curb_debugf("[curb.socket] sock_cb fd=%d what=%s (removed)", fd, poll_what_str(what, b, sizeof(b)));
|
674
|
+
}
|
675
|
+
} else {
|
676
|
+
/* store current interest mask for this fd */
|
677
|
+
st_insert(ctx->sock_map, (st_data_t)fd, (st_data_t)what);
|
678
|
+
{
|
679
|
+
char b[16];
|
680
|
+
curb_debugf("[curb.socket] sock_cb fd=%d what=%s (tracked)", fd, poll_what_str(what, b, sizeof(b)));
|
681
|
+
}
|
682
|
+
}
|
683
|
+
return 0;
|
684
|
+
}
|
685
|
+
|
686
|
+
static int multi_timer_cb(CURLM *multi, long timeout_ms, void *userp) {
|
687
|
+
(void)multi;
|
688
|
+
multi_socket_ctx *ctx = (multi_socket_ctx *)userp;
|
689
|
+
if (ctx) ctx->timeout_ms = timeout_ms;
|
690
|
+
curb_debugf("[curb.socket] timer_cb timeout_ms=%ld", timeout_ms);
|
691
|
+
return 0;
|
692
|
+
}
|
693
|
+
|
694
|
+
struct build_fdset_args { rb_fdset_t *r; rb_fdset_t *w; rb_fdset_t *e; int maxfd; };
|
695
|
+
static int rb_fdset_from_sockmap_i(st_data_t key, st_data_t val, st_data_t argp) {
|
696
|
+
struct build_fdset_args *a = (struct build_fdset_args *)argp;
|
697
|
+
int fd = (int)key;
|
698
|
+
int what = (int)val;
|
699
|
+
if (what & CURL_POLL_IN) rb_fd_set(fd, a->r);
|
700
|
+
if (what & CURL_POLL_OUT) rb_fd_set(fd, a->w);
|
701
|
+
rb_fd_set(fd, a->e);
|
702
|
+
if (fd > a->maxfd) a->maxfd = fd;
|
703
|
+
return ST_CONTINUE;
|
704
|
+
}
|
705
|
+
static void rb_fdset_from_sockmap(st_table *map, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, int *maxfd_out) {
|
706
|
+
if (!map) { *maxfd_out = -1; return; }
|
707
|
+
struct build_fdset_args a; a.r = rfds; a.w = wfds; a.e = efds; a.maxfd = -1;
|
708
|
+
st_foreach(map, rb_fdset_from_sockmap_i, (st_data_t)&a);
|
709
|
+
*maxfd_out = a.maxfd;
|
710
|
+
}
|
711
|
+
|
712
|
+
struct dispatch_args { CURLM *mh; int *running; CURLMcode mrc; rb_fdset_t *r; rb_fdset_t *w; rb_fdset_t *e; };
|
713
|
+
static int dispatch_ready_fd_i(st_data_t key, st_data_t val, st_data_t argp) {
|
714
|
+
(void)val;
|
715
|
+
struct dispatch_args *dp = (struct dispatch_args *)argp;
|
716
|
+
int fd = (int)key;
|
717
|
+
int flags = 0;
|
718
|
+
if (rb_fd_isset(fd, dp->r)) flags |= CURL_CSELECT_IN;
|
719
|
+
if (rb_fd_isset(fd, dp->w)) flags |= CURL_CSELECT_OUT;
|
720
|
+
if (rb_fd_isset(fd, dp->e)) flags |= CURL_CSELECT_ERR;
|
721
|
+
if (flags) {
|
722
|
+
dp->mrc = curl_multi_socket_action(dp->mh, (curl_socket_t)fd, flags, dp->running);
|
723
|
+
if (dp->mrc != CURLM_OK) return ST_STOP;
|
724
|
+
}
|
725
|
+
return ST_CONTINUE;
|
726
|
+
}
|
727
|
+
|
728
|
+
/* Helpers used with st_foreach to avoid compiler-specific nested functions. */
|
729
|
+
struct pick_one_state { int fd; int what; int found; };
|
730
|
+
static int st_pick_one_i(st_data_t key, st_data_t val, st_data_t argp) {
|
731
|
+
struct pick_one_state *s = (struct pick_one_state *)argp;
|
732
|
+
s->fd = (int)key;
|
733
|
+
s->what = (int)val;
|
734
|
+
s->found = 1;
|
735
|
+
return ST_STOP;
|
736
|
+
}
|
737
|
+
struct counter_state { int count; };
|
738
|
+
static int st_count_i(st_data_t k, st_data_t v, st_data_t argp) {
|
739
|
+
(void)k; (void)v;
|
740
|
+
struct counter_state *c = (struct counter_state *)argp;
|
741
|
+
c->count++;
|
742
|
+
return ST_CONTINUE;
|
743
|
+
}
|
744
|
+
|
745
|
+
static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) {
|
746
|
+
/* prime the state: let libcurl act on timeouts to setup sockets */
|
747
|
+
CURLMcode mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
748
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
749
|
+
curb_debugf("[curb.socket] drive: initial socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
750
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
751
|
+
if (block != Qnil) rb_funcall(block, rb_intern("call"), 1, self);
|
752
|
+
|
753
|
+
while (rbcm->running) {
|
754
|
+
struct timeval tv = {0, 0};
|
755
|
+
if (ctx->timeout_ms < 0) {
|
756
|
+
tv.tv_sec = cCurlMutiDefaulttimeout / 1000;
|
757
|
+
tv.tv_usec = (cCurlMutiDefaulttimeout % 1000) * 1000;
|
758
|
+
} else {
|
759
|
+
long t = ctx->timeout_ms;
|
760
|
+
if (t > cCurlMutiDefaulttimeout) t = cCurlMutiDefaulttimeout;
|
761
|
+
if (t < 0) t = 0;
|
762
|
+
tv.tv_sec = t / 1000;
|
763
|
+
tv.tv_usec = (t % 1000) * 1000;
|
764
|
+
}
|
765
|
+
|
766
|
+
/* Find a representative fd to wait on (if any). */
|
767
|
+
int wait_fd = -1;
|
768
|
+
int wait_what = 0;
|
769
|
+
if (ctx->sock_map) {
|
770
|
+
struct pick_one_state st = { -1, 0, 0 };
|
771
|
+
st_foreach(ctx->sock_map, st_pick_one_i, (st_data_t)&st);
|
772
|
+
if (st.found) { wait_fd = st.fd; wait_what = st.what; }
|
773
|
+
}
|
774
|
+
|
775
|
+
/* Count tracked fds for logging */
|
776
|
+
int count_tracked = 0;
|
777
|
+
if (ctx->sock_map) {
|
778
|
+
struct counter_state cs = { 0 };
|
779
|
+
st_foreach(ctx->sock_map, st_count_i, (st_data_t)&cs);
|
780
|
+
count_tracked = cs.count;
|
781
|
+
}
|
782
|
+
|
783
|
+
curb_debugf("[curb.socket] wait phase: tracked_fds=%d fd=%d what=%d tv=%ld.%06ld", count_tracked, wait_fd, wait_what, (long)tv.tv_sec, (long)tv.tv_usec);
|
784
|
+
|
785
|
+
int did_timeout = 0;
|
786
|
+
int any_ready = 0;
|
787
|
+
|
788
|
+
int handled_wait = 0;
|
789
|
+
if (count_tracked > 1) {
|
790
|
+
/* Multi-fd wait using scheduler-aware rb_thread_fd_select. */
|
791
|
+
rb_fdset_t rfds, wfds, efds;
|
792
|
+
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
793
|
+
int maxfd = -1;
|
794
|
+
struct build_fdset_args a2; a2.r = &rfds; a2.w = &wfds; a2.e = &efds; a2.maxfd = -1;
|
795
|
+
st_foreach(ctx->sock_map, rb_fdset_from_sockmap_i, (st_data_t)&a2);
|
796
|
+
maxfd = a2.maxfd;
|
797
|
+
int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv);
|
798
|
+
curb_debugf("[curb.socket] rb_thread_fd_select(multi) rc=%d maxfd=%d", rc, maxfd);
|
799
|
+
if (rc < 0) {
|
800
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
801
|
+
if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
|
802
|
+
continue;
|
803
|
+
}
|
804
|
+
any_ready = (rc > 0);
|
805
|
+
did_timeout = (rc == 0);
|
806
|
+
if (any_ready) {
|
807
|
+
struct dispatch_args d; d.mh = rbcm->handle; d.running = &rbcm->running; d.mrc = CURLM_OK; d.r = &rfds; d.w = &wfds; d.e = &efds;
|
808
|
+
st_foreach(ctx->sock_map, dispatch_ready_fd_i, (st_data_t)&d);
|
809
|
+
if (d.mrc != CURLM_OK) {
|
810
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
811
|
+
raise_curl_multi_error_exception(d.mrc);
|
812
|
+
}
|
813
|
+
}
|
814
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
815
|
+
handled_wait = 1;
|
816
|
+
} else if (count_tracked == 1) {
|
817
|
+
#if defined(HAVE_RB_WAIT_FOR_SINGLE_FD)
|
818
|
+
if (wait_fd >= 0) {
|
819
|
+
int ev = 0;
|
820
|
+
if (wait_what == CURL_POLL_IN) ev = RB_WAITFD_IN;
|
821
|
+
else if (wait_what == CURL_POLL_OUT) ev = RB_WAITFD_OUT;
|
822
|
+
else if (wait_what == CURL_POLL_INOUT) ev = RB_WAITFD_IN|RB_WAITFD_OUT;
|
823
|
+
int rc = rb_wait_for_single_fd(wait_fd, ev, &tv);
|
824
|
+
curb_debugf("[curb.socket] rb_wait_for_single_fd rc=%d fd=%d ev=%d", rc, wait_fd, ev);
|
825
|
+
if (rc < 0) {
|
826
|
+
if (errno != EINTR) rb_raise(rb_eRuntimeError, "wait_for_single_fd(): %s", strerror(errno));
|
827
|
+
continue;
|
828
|
+
}
|
829
|
+
any_ready = (rc != 0);
|
830
|
+
did_timeout = (rc == 0);
|
831
|
+
handled_wait = 1;
|
832
|
+
}
|
833
|
+
#endif
|
834
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
835
|
+
if (!handled_wait) {
|
836
|
+
VALUE scheduler = rb_fiber_scheduler_current();
|
837
|
+
if (scheduler != Qnil) {
|
838
|
+
int events = 0;
|
839
|
+
if (wait_fd >= 0) {
|
840
|
+
if (wait_what == CURL_POLL_IN) events = RB_WAITFD_IN;
|
841
|
+
else if (wait_what == CURL_POLL_OUT) events = RB_WAITFD_OUT;
|
842
|
+
else if (wait_what == CURL_POLL_INOUT) events = RB_WAITFD_IN|RB_WAITFD_OUT;
|
843
|
+
else events = RB_WAITFD_IN|RB_WAITFD_OUT;
|
844
|
+
}
|
845
|
+
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
846
|
+
VALUE timeout = rb_float_new(timeout_s);
|
847
|
+
if (wait_fd < 0) {
|
848
|
+
rb_thread_wait_for(tv);
|
849
|
+
did_timeout = 1;
|
850
|
+
} else {
|
851
|
+
const char *mode = (wait_what == CURL_POLL_IN) ? "r" : (wait_what == CURL_POLL_OUT) ? "w" : "r+";
|
852
|
+
VALUE io = rb_funcall(rb_cIO, rb_intern("for_fd"), 2, INT2NUM(wait_fd), rb_str_new_cstr(mode));
|
853
|
+
rb_funcall(io, rb_intern("autoclose="), 1, Qfalse);
|
854
|
+
struct fiber_io_wait_args args = { scheduler, io, events, timeout };
|
855
|
+
int state = 0;
|
856
|
+
VALUE ready = rb_protect(fiber_io_wait_protected, (VALUE)&args, &state);
|
857
|
+
if (state) {
|
858
|
+
did_timeout = 1; any_ready = 0;
|
859
|
+
} else {
|
860
|
+
any_ready = (ready != Qfalse);
|
861
|
+
did_timeout = !any_ready;
|
862
|
+
}
|
863
|
+
}
|
864
|
+
handled_wait = 1;
|
865
|
+
}
|
866
|
+
}
|
867
|
+
#endif
|
868
|
+
if (!handled_wait) {
|
869
|
+
/* Fallback: single-fd select. */
|
870
|
+
rb_fdset_t rfds, wfds, efds;
|
871
|
+
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
872
|
+
int maxfd = -1;
|
873
|
+
if (wait_fd >= 0) {
|
874
|
+
if (wait_what == CURL_POLL_IN || wait_what == CURL_POLL_INOUT) rb_fd_set(wait_fd, &rfds);
|
875
|
+
if (wait_what == CURL_POLL_OUT || wait_what == CURL_POLL_INOUT) rb_fd_set(wait_fd, &wfds);
|
876
|
+
rb_fd_set(wait_fd, &efds);
|
877
|
+
maxfd = wait_fd;
|
878
|
+
}
|
879
|
+
int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv);
|
880
|
+
curb_debugf("[curb.socket] rb_thread_fd_select(single) rc=%d fd=%d", rc, wait_fd);
|
881
|
+
if (rc < 0) {
|
882
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
883
|
+
if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
|
884
|
+
continue;
|
885
|
+
}
|
886
|
+
any_ready = (rc > 0);
|
887
|
+
did_timeout = (rc == 0);
|
888
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
889
|
+
}
|
890
|
+
} else { /* count_tracked == 0 */
|
891
|
+
rb_thread_wait_for(tv);
|
892
|
+
did_timeout = 1;
|
893
|
+
}
|
894
|
+
|
895
|
+
if (did_timeout) {
|
896
|
+
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
897
|
+
curb_debugf("[curb.socket] socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
898
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
899
|
+
} else if (any_ready) {
|
900
|
+
if (count_tracked == 1 && wait_fd >= 0) {
|
901
|
+
int flags = 0;
|
902
|
+
if (wait_what == CURL_POLL_IN || wait_what == CURL_POLL_INOUT) flags |= CURL_CSELECT_IN;
|
903
|
+
if (wait_what == CURL_POLL_OUT || wait_what == CURL_POLL_INOUT) flags |= CURL_CSELECT_OUT;
|
904
|
+
flags |= CURL_CSELECT_ERR;
|
905
|
+
char b[32];
|
906
|
+
curb_debugf("[curb.socket] socket_action fd=%d flags=%s", wait_fd, cselect_flags_str(flags, b, sizeof(b)));
|
907
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)wait_fd, flags, &rbcm->running);
|
908
|
+
curb_debugf("[curb.socket] socket_action -> mrc=%d running=%d", mrc, rbcm->running);
|
909
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
910
|
+
}
|
911
|
+
}
|
912
|
+
|
913
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
914
|
+
curb_debugf("[curb.socket] processed completions; running=%d", rbcm->running);
|
915
|
+
if (block != Qnil) rb_funcall(block, rb_intern("call"), 1, self);
|
916
|
+
}
|
917
|
+
}
|
918
|
+
|
919
|
+
struct socket_drive_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; VALUE block; };
|
920
|
+
static VALUE ruby_curl_multi_socket_drive_body(VALUE argp) {
|
921
|
+
struct socket_drive_args *a = (struct socket_drive_args *)argp;
|
922
|
+
rb_curl_multi_socket_drive(a->self, a->rbcm, a->ctx, a->block);
|
923
|
+
return Qtrue;
|
924
|
+
}
|
925
|
+
struct socket_cleanup_args { ruby_curl_multi *rbcm; multi_socket_ctx *ctx; };
|
926
|
+
static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
927
|
+
struct socket_cleanup_args *c = (struct socket_cleanup_args *)argp;
|
928
|
+
if (c->rbcm && c->rbcm->handle) {
|
929
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_SOCKETFUNCTION, NULL);
|
930
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_SOCKETDATA, NULL);
|
931
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_TIMERFUNCTION, NULL);
|
932
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_TIMERDATA, NULL);
|
933
|
+
}
|
934
|
+
if (c->ctx && c->ctx->sock_map) {
|
935
|
+
st_free_table(c->ctx->sock_map);
|
936
|
+
c->ctx->sock_map = NULL;
|
937
|
+
}
|
938
|
+
return Qnil;
|
939
|
+
}
|
940
|
+
|
941
|
+
VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
|
942
|
+
ruby_curl_multi *rbcm;
|
943
|
+
VALUE block = Qnil;
|
944
|
+
rb_scan_args(argc, argv, "0&", &block);
|
945
|
+
|
946
|
+
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
947
|
+
|
948
|
+
multi_socket_ctx ctx;
|
949
|
+
ctx.sock_map = st_init_numtable();
|
950
|
+
ctx.timeout_ms = -1;
|
951
|
+
|
952
|
+
/* install socket/timer callbacks */
|
953
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
|
954
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETDATA, &ctx);
|
955
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
|
956
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_TIMERDATA, &ctx);
|
957
|
+
|
958
|
+
/* run using socket action loop with ensure-cleanup */
|
959
|
+
struct socket_drive_args body_args = { self, rbcm, &ctx, block };
|
960
|
+
struct socket_cleanup_args ensure_args = { rbcm, &ctx };
|
961
|
+
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
962
|
+
|
963
|
+
/* finalize */
|
964
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
965
|
+
if (block != Qnil) rb_funcall(block, rb_intern("call"), 1, self);
|
966
|
+
if (cCurlMutiAutoClose == 1) rb_funcall(self, rb_intern("close"), 0);
|
967
|
+
|
968
|
+
return Qtrue;
|
969
|
+
}
|
970
|
+
#endif /* socket-action implementation */
|
971
|
+
|
469
972
|
#ifdef _WIN32
|
470
973
|
void create_crt_fd(fd_set *os_set, fd_set *crt_set)
|
471
974
|
{
|
@@ -588,23 +1091,38 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
588
1091
|
/* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
|
589
1092
|
}
|
590
1093
|
|
591
|
-
#
|
1094
|
+
#if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT)
|
592
1095
|
{
|
593
1096
|
struct wait_args wait_args;
|
594
1097
|
wait_args.handle = rbcm->handle;
|
595
1098
|
wait_args.timeout_ms = timeout_milliseconds;
|
596
1099
|
wait_args.numfds = 0;
|
1100
|
+
/*
|
1101
|
+
* When a Fiber scheduler is available (Ruby >= 3.x), rb_thread_fd_select
|
1102
|
+
* integrates with it. If we have rb_thread_fd_select available at build
|
1103
|
+
* time, we avoid curl_multi_wait entirely (see preprocessor guard above)
|
1104
|
+
* and use the fdset branch below. Otherwise, we use curl_multi_wait and
|
1105
|
+
* release the GVL so Ruby threads can continue to run.
|
1106
|
+
*/
|
1107
|
+
CURLMcode wait_rc;
|
597
1108
|
#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
598
|
-
|
599
|
-
|
1109
|
+
wait_rc = (CURLMcode)(intptr_t)rb_thread_call_without_gvl(
|
1110
|
+
curl_multi_wait_wrapper, &wait_args, RUBY_UBF_IO, NULL
|
1111
|
+
);
|
600
1112
|
#else
|
601
|
-
|
1113
|
+
wait_rc = curl_multi_wait(rbcm->handle, NULL, 0, timeout_milliseconds, &wait_args.numfds);
|
602
1114
|
#endif
|
603
1115
|
if (wait_rc != CURLM_OK) {
|
604
1116
|
raise_curl_multi_error_exception(wait_rc);
|
605
1117
|
}
|
606
1118
|
if (wait_args.numfds == 0) {
|
1119
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
1120
|
+
struct timeval tv_sleep = tv_100ms;
|
1121
|
+
/* Sleep in a scheduler-aware way. */
|
1122
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
1123
|
+
#else
|
607
1124
|
rb_thread_wait_for(tv_100ms);
|
1125
|
+
#endif
|
608
1126
|
}
|
609
1127
|
/* Process pending transfers after waiting */
|
610
1128
|
rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
|
@@ -628,7 +1146,12 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
628
1146
|
|
629
1147
|
if (maxfd == -1) {
|
630
1148
|
/* libcurl recommends sleeping for 100ms */
|
1149
|
+
#if HAVE_RB_THREAD_FD_SELECT
|
1150
|
+
struct timeval tv_sleep = tv_100ms;
|
1151
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
1152
|
+
#else
|
631
1153
|
rb_thread_wait_for(tv_100ms);
|
1154
|
+
#endif
|
632
1155
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
633
1156
|
rb_curl_multi_read_info( self, rbcm->handle );
|
634
1157
|
if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
|
@@ -650,12 +1173,37 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
650
1173
|
fdset_args.tv = &tv;
|
651
1174
|
#endif
|
652
1175
|
|
653
|
-
#
|
1176
|
+
#if HAVE_RB_THREAD_FD_SELECT
|
1177
|
+
/* Prefer scheduler-aware waiting when available. Build rb_fdset_t sets. */
|
1178
|
+
{
|
1179
|
+
rb_fdset_t rfds, wfds, efds;
|
1180
|
+
rb_fd_init(&rfds);
|
1181
|
+
rb_fd_init(&wfds);
|
1182
|
+
rb_fd_init(&efds);
|
1183
|
+
#ifdef _WIN32
|
1184
|
+
/* On Windows, iterate explicit fd arrays for CRT fds. */
|
1185
|
+
int i;
|
1186
|
+
for (i = 0; i < crt_fdread.fd_count; i++) rb_fd_set(crt_fdread.fd_array[i], &rfds);
|
1187
|
+
for (i = 0; i < crt_fdwrite.fd_count; i++) rb_fd_set(crt_fdwrite.fd_array[i], &wfds);
|
1188
|
+
for (i = 0; i < crt_fdexcep.fd_count; i++) rb_fd_set(crt_fdexcep.fd_array[i], &efds);
|
1189
|
+
rc = rb_thread_fd_select(0, &rfds, &wfds, &efds, &tv);
|
1190
|
+
#else
|
1191
|
+
int fd;
|
1192
|
+
for (fd = 0; fd <= maxfd; fd++) {
|
1193
|
+
if (FD_ISSET(fd, &fdread)) rb_fd_set(fd, &rfds);
|
1194
|
+
if (FD_ISSET(fd, &fdwrite)) rb_fd_set(fd, &wfds);
|
1195
|
+
if (FD_ISSET(fd, &fdexcep)) rb_fd_set(fd, &efds);
|
1196
|
+
}
|
1197
|
+
rc = rb_thread_fd_select(maxfd+1, &rfds, &wfds, &efds, &tv);
|
1198
|
+
#endif
|
1199
|
+
rb_fd_term(&rfds);
|
1200
|
+
rb_fd_term(&wfds);
|
1201
|
+
rb_fd_term(&efds);
|
1202
|
+
}
|
1203
|
+
#elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
654
1204
|
rc = (int)(VALUE) rb_thread_call_without_gvl((void *(*)(void *))curb_select, &fdset_args, RUBY_UBF_IO, 0);
|
655
1205
|
#elif HAVE_RB_THREAD_BLOCKING_REGION
|
656
1206
|
rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
|
657
|
-
#elif HAVE_RB_THREAD_FD_SELECT
|
658
|
-
rc = rb_thread_fd_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
|
659
1207
|
#else
|
660
1208
|
rc = rb_thread_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
|
661
1209
|
#endif
|
@@ -679,7 +1227,7 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
679
1227
|
if (block != Qnil) { rb_funcall(block, rb_intern("call"), 1, self); }
|
680
1228
|
break;
|
681
1229
|
}
|
682
|
-
#endif /*
|
1230
|
+
#endif /* disabled curl_multi_wait: use fdsets */
|
683
1231
|
}
|
684
1232
|
|
685
1233
|
} while( rbcm->running );
|
@@ -702,11 +1250,32 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
702
1250
|
VALUE ruby_curl_multi_close(VALUE self) {
|
703
1251
|
ruby_curl_multi *rbcm;
|
704
1252
|
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
705
|
-
|
1253
|
+
rb_curl_multi_detach_all(rbcm);
|
1254
|
+
|
1255
|
+
if (rbcm->handle) {
|
1256
|
+
curl_multi_cleanup(rbcm->handle);
|
1257
|
+
rbcm->handle = NULL;
|
1258
|
+
}
|
1259
|
+
|
706
1260
|
ruby_curl_multi_init(rbcm);
|
707
1261
|
return self;
|
708
1262
|
}
|
709
1263
|
|
1264
|
+
/* GC mark: keep attached easy VALUEs alive while associated. */
|
1265
|
+
static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) {
|
1266
|
+
VALUE easy = (VALUE)val;
|
1267
|
+
if (!NIL_P(easy)) rb_gc_mark(easy);
|
1268
|
+
return ST_CONTINUE;
|
1269
|
+
}
|
1270
|
+
|
1271
|
+
static void curl_multi_mark(void *ptr) {
|
1272
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
|
1273
|
+
if (!rbcm) return;
|
1274
|
+
if (rbcm->attached) {
|
1275
|
+
st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0);
|
1276
|
+
}
|
1277
|
+
}
|
1278
|
+
|
710
1279
|
|
711
1280
|
/* =================== INIT LIB =====================*/
|
712
1281
|
void init_curb_multi() {
|
@@ -727,6 +1296,12 @@ void init_curb_multi() {
|
|
727
1296
|
rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
|
728
1297
|
rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
|
729
1298
|
rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
|
1299
|
+
/* Prefer a socket-action based perform when supported and scheduler-aware. */
|
1300
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
1301
|
+
extern VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self);
|
1302
|
+
rb_define_method(cCurlMulti, "perform", ruby_curl_multi_socket_perform, -1);
|
1303
|
+
#else
|
730
1304
|
rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
|
1305
|
+
#endif
|
731
1306
|
rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
|
732
1307
|
}
|