curb 1.3.6 → 1.4.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.
- checksums.yaml +4 -4
- data/README.md +71 -14
- data/Rakefile +12 -1
- data/ext/curb.c +26 -4
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +178 -77
- data/ext/curb_easy.h +1 -0
- data/ext/curb_multi.c +639 -225
- data/ext/curb_multi.h +4 -0
- data/ext/curb_postfield.c +31 -31
- data/ext/extconf.rb +10 -0
- data/lib/curl/easy.rb +18 -9
- data/lib/curl.rb +49 -12
- data/tests/helper.rb +300 -4
- data/tests/io_select_less_scheduler_probe.rb +105 -0
- data/tests/tc_curl_easy.rb +60 -4
- data/tests/tc_curl_easy_cookielist.rb +8 -4
- data/tests/tc_curl_easy_resolve.rb +2 -0
- data/tests/tc_curl_multi.rb +29 -0
- data/tests/tc_curl_multi_poll.rb +223 -0
- data/tests/tc_curl_multi_socket_poller.rb +374 -0
- data/tests/tc_curl_native_coverage.rb +1 -0
- data/tests/tc_curl_postfield.rb +39 -0
- data/tests/tc_fiber_scheduler.rb +433 -6
- data/tests/tc_ftp_options.rb +58 -0
- data/tests/tc_gc_compact.rb +53 -0
- data/tests/tc_ractor.rb +94 -0
- data/tests/tc_test_server_methods.rb +15 -0
- metadata +10 -2
data/ext/curb_multi.c
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
#include <sys/time.h>
|
|
33
33
|
#include <time.h>
|
|
34
34
|
#endif
|
|
35
|
+
#include <limits.h>
|
|
35
36
|
#include <stdint.h>
|
|
36
37
|
#include <stdarg.h>
|
|
37
38
|
|
|
@@ -52,12 +53,50 @@
|
|
|
52
53
|
#define CURB_MAYBE_UNUSED_DECL
|
|
53
54
|
#endif
|
|
54
55
|
|
|
56
|
+
/* Older rubies do not define UNDEF_P. */
|
|
57
|
+
#ifndef UNDEF_P
|
|
58
|
+
#define UNDEF_P(obj) ((VALUE)(obj) == Qundef)
|
|
59
|
+
#endif
|
|
60
|
+
|
|
55
61
|
#ifdef _WIN32
|
|
56
62
|
// for O_RDWR and O_BINARY
|
|
57
63
|
#include <fcntl.h>
|
|
58
64
|
#endif
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
/*
|
|
67
|
+
* Prefer curl_multi_poll for the blocking perform loop: it uses poll(), so it
|
|
68
|
+
* has no FD_SETSIZE limit (select-based waiting cannot see sockets numbered
|
|
69
|
+
* above 1024 and degrades to fixed sleeps), and curl_multi_wakeup lets the
|
|
70
|
+
* unblocking function end the wait immediately for Thread#raise, Thread#kill
|
|
71
|
+
* and signal delivery.
|
|
72
|
+
*/
|
|
73
|
+
#if defined(HAVE_CURL_MULTI_POLL) && defined(HAVE_CURL_MULTI_WAKEUP) && defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
74
|
+
#define CURB_USE_MULTI_POLL 1
|
|
75
|
+
#endif
|
|
76
|
+
|
|
77
|
+
#ifdef CURB_USE_MULTI_POLL
|
|
78
|
+
struct multi_poll_args {
|
|
79
|
+
CURLM *handle;
|
|
80
|
+
int timeout_ms;
|
|
81
|
+
int numfds;
|
|
82
|
+
CURLMcode code;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
static void *curb_multi_poll_without_gvl(void *p) {
|
|
86
|
+
struct multi_poll_args *args = p;
|
|
87
|
+
args->code = curl_multi_poll(args->handle, NULL, 0, args->timeout_ms, &args->numfds);
|
|
88
|
+
return NULL;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Unblocking function: curl_multi_wakeup is safe to call from another thread
|
|
92
|
+
* while curl_multi_poll is blocked on the same handle. A wakeup that arrives
|
|
93
|
+
* after the poll returned only makes the next poll return early once. */
|
|
94
|
+
static void curb_multi_poll_ubf(void *handle) {
|
|
95
|
+
curl_multi_wakeup((CURLM *)handle);
|
|
96
|
+
}
|
|
97
|
+
#endif
|
|
98
|
+
|
|
99
|
+
#if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT) && !defined(CURB_USE_MULTI_POLL)
|
|
61
100
|
struct wait_args {
|
|
62
101
|
CURLM *handle;
|
|
63
102
|
long timeout_ms;
|
|
@@ -70,12 +109,101 @@ static void *curl_multi_wait_wrapper(void *p) {
|
|
|
70
109
|
}
|
|
71
110
|
#endif
|
|
72
111
|
|
|
112
|
+
static VALUE cRubyFiber;
|
|
113
|
+
static VALUE cRubyRactor;
|
|
114
|
+
static ID id_fiber_blocking_p;
|
|
115
|
+
static ID id_fiber_scheduler;
|
|
116
|
+
static ID id_kernel_sleep;
|
|
117
|
+
static ID id_io_wait;
|
|
118
|
+
static ID id_ractor_current;
|
|
119
|
+
static ID id_ractor_aref;
|
|
120
|
+
static ID id_ractor_aset;
|
|
121
|
+
static ID id_ractor_default_timeout_key;
|
|
122
|
+
static ID id_ractor_autoclose_key;
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
* Ruby 3.0 introduced the Fiber scheduler at the Ruby level, but did not ship
|
|
126
|
+
* the public ruby/fiber/scheduler.h C API. Use the public C entry points when
|
|
127
|
+
* available and otherwise dispatch through Fiber and the scheduler object.
|
|
128
|
+
*/
|
|
129
|
+
static VALUE curb_fiber_scheduler_current(void) {
|
|
130
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
131
|
+
return rb_fiber_scheduler_current();
|
|
132
|
+
#else
|
|
133
|
+
VALUE scheduler;
|
|
134
|
+
|
|
135
|
+
if (!rb_respond_to(cRubyFiber, id_fiber_scheduler)) return Qnil;
|
|
136
|
+
|
|
137
|
+
scheduler = rb_funcall(cRubyFiber, id_fiber_scheduler, 0);
|
|
138
|
+
if (NIL_P(scheduler)) return Qnil;
|
|
139
|
+
|
|
140
|
+
/* Fiber.scheduler returns the thread scheduler even from a blocking fiber,
|
|
141
|
+
* while rb_fiber_scheduler_current returns nil there. Match the latter so
|
|
142
|
+
* ordinary blocking fibers keep using the legacy multi loop. */
|
|
143
|
+
if (rb_respond_to(cRubyFiber, id_fiber_blocking_p) &&
|
|
144
|
+
RTEST(rb_funcall(cRubyFiber, id_fiber_blocking_p, 0))) {
|
|
145
|
+
return Qnil;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return scheduler;
|
|
149
|
+
#endif
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static VALUE curb_fiber_scheduler_kernel_sleep(VALUE scheduler, VALUE timeout) {
|
|
153
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_KERNEL_SLEEP)
|
|
154
|
+
return rb_fiber_scheduler_kernel_sleep(scheduler, timeout);
|
|
155
|
+
#else
|
|
156
|
+
return rb_funcall(scheduler, id_kernel_sleep, 1, timeout);
|
|
157
|
+
#endif
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static VALUE curb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout) {
|
|
161
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT)
|
|
162
|
+
return rb_fiber_scheduler_io_wait(scheduler, io, events, timeout);
|
|
163
|
+
#else
|
|
164
|
+
return rb_funcall(scheduler, id_io_wait, 3, io, events, timeout);
|
|
165
|
+
#endif
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
* Sleep for the given interval without stalling sibling fibers: when the
|
|
170
|
+
* current fiber runs under a fiber scheduler, wait via the scheduler's
|
|
171
|
+
* kernel_sleep hook. rb_thread_fd_select(0, ...) and rb_thread_wait_for are
|
|
172
|
+
* thread-level waits that never consult the scheduler, so they are only used
|
|
173
|
+
* when no scheduler is active.
|
|
174
|
+
*/
|
|
175
|
+
static void curb_multi_scheduler_sleep(struct timeval *tv) {
|
|
176
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
177
|
+
if (scheduler != Qnil) {
|
|
178
|
+
curb_fiber_scheduler_kernel_sleep(scheduler, rb_float_new((double)tv->tv_sec + ((double)tv->tv_usec / 1e6)));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
182
|
+
{
|
|
183
|
+
struct timeval tv_sleep = *tv;
|
|
184
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
185
|
+
}
|
|
186
|
+
#else
|
|
187
|
+
rb_thread_wait_for(*tv);
|
|
188
|
+
#endif
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Yield a nonblocking fiber without changing legacy behavior when no
|
|
192
|
+
* scheduler is active. */
|
|
193
|
+
static void curb_multi_scheduler_yield(void) {
|
|
194
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
195
|
+
if (scheduler != Qnil) {
|
|
196
|
+
curb_fiber_scheduler_kernel_sleep(scheduler, INT2FIX(0));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
73
200
|
extern VALUE mCurl;
|
|
74
201
|
static VALUE idCall;
|
|
75
202
|
static ID id_deferred_exception_ivar;
|
|
76
203
|
static ID id_deferred_exception_source_id_ivar;
|
|
77
204
|
static ID id_native_safety_signatures_ivar;
|
|
78
205
|
static ID id_socket_io_cache_ivar;
|
|
206
|
+
static ID id_socket_poller_io_ivar;
|
|
79
207
|
|
|
80
208
|
#ifdef RDOC_NEVER_DEFINED
|
|
81
209
|
mCurl = rb_define_module("Curl");
|
|
@@ -86,6 +214,32 @@ VALUE cCurlMulti;
|
|
|
86
214
|
static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
|
|
87
215
|
static char cCurlMutiAutoClose = 0;
|
|
88
216
|
|
|
217
|
+
/* Ruby 3.0+ provides storage owned by the current Ractor. Keep configurable
|
|
218
|
+
* defaults there so separate Ractors never race on process-global C values.
|
|
219
|
+
* Rubies without Ractor retain the historical process-wide settings. */
|
|
220
|
+
static VALUE curb_multi_ractor_setting(ID key) {
|
|
221
|
+
VALUE current;
|
|
222
|
+
|
|
223
|
+
if (NIL_P(cRubyRactor)) return Qundef;
|
|
224
|
+
current = rb_funcall(cRubyRactor, id_ractor_current, 0);
|
|
225
|
+
return rb_funcall(current, id_ractor_aref, 1, ID2SYM(key));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static void curb_multi_set_ractor_setting(ID key, VALUE value) {
|
|
229
|
+
VALUE current = rb_funcall(cRubyRactor, id_ractor_current, 0);
|
|
230
|
+
rb_funcall(current, id_ractor_aset, 2, ID2SYM(key), value);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static long curb_multi_default_timeout(void) {
|
|
234
|
+
VALUE value = curb_multi_ractor_setting(id_ractor_default_timeout_key);
|
|
235
|
+
return (UNDEF_P(value) || NIL_P(value)) ? cCurlMutiDefaulttimeout : NUM2LONG(value);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static int curb_multi_autoclose_enabled(void) {
|
|
239
|
+
VALUE value = curb_multi_ractor_setting(id_ractor_autoclose_key);
|
|
240
|
+
return (UNDEF_P(value) || NIL_P(value)) ? cCurlMutiAutoClose == 1 : RTEST(value);
|
|
241
|
+
}
|
|
242
|
+
|
|
89
243
|
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result);
|
|
90
244
|
static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
|
|
91
245
|
static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
|
|
@@ -476,12 +630,18 @@ static VALUE ruby_curl_multi_initialize(VALUE self) {
|
|
|
476
630
|
* call-seq:
|
|
477
631
|
* Curl::Multi.default_timeout = 4 => 4
|
|
478
632
|
*
|
|
479
|
-
* Set the
|
|
480
|
-
*
|
|
633
|
+
* Set the default timeout for Curl::Multi handles in the current Ractor. On
|
|
634
|
+
* Rubies without Ractor this remains process-wide. This value is used when
|
|
635
|
+
* libcurl cannot determine a timeout value when calling curl_multi_timeout.
|
|
481
636
|
*
|
|
482
637
|
*/
|
|
483
638
|
VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
484
|
-
|
|
639
|
+
long value = NUM2LONG(timeout);
|
|
640
|
+
if (NIL_P(cRubyRactor)) {
|
|
641
|
+
cCurlMutiDefaulttimeout = value;
|
|
642
|
+
} else {
|
|
643
|
+
curb_multi_set_ractor_setting(id_ractor_default_timeout_key, LONG2NUM(value));
|
|
644
|
+
}
|
|
485
645
|
return timeout;
|
|
486
646
|
}
|
|
487
647
|
|
|
@@ -489,23 +649,29 @@ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
|
489
649
|
* call-seq:
|
|
490
650
|
* Curl::Multi.default_timeout = 4 => 4
|
|
491
651
|
*
|
|
492
|
-
* Get the
|
|
652
|
+
* Get the current Ractor's default timeout for Curl::Multi handles.
|
|
493
653
|
*
|
|
494
654
|
*/
|
|
495
655
|
VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
|
|
496
|
-
return LONG2NUM(
|
|
656
|
+
return LONG2NUM(curb_multi_default_timeout());
|
|
497
657
|
}
|
|
498
658
|
|
|
499
659
|
/*
|
|
500
660
|
* call-seq:
|
|
501
661
|
* Curl::Multi.autoclose = true => true
|
|
502
662
|
*
|
|
503
|
-
* Automatically close open connections after each request
|
|
504
|
-
* for reuse until the next
|
|
663
|
+
* Automatically close open connections after each request in the current
|
|
664
|
+
* Ractor. Otherwise, the connection will remain open for reuse until the next
|
|
665
|
+
* GC.
|
|
505
666
|
*
|
|
506
667
|
*/
|
|
507
668
|
VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
|
|
508
|
-
|
|
669
|
+
VALUE value = onoff == Qtrue ? Qtrue : Qfalse;
|
|
670
|
+
if (NIL_P(cRubyRactor)) {
|
|
671
|
+
cCurlMutiAutoClose = RTEST(value) ? 1 : 0;
|
|
672
|
+
} else {
|
|
673
|
+
curb_multi_set_ractor_setting(id_ractor_autoclose_key, value);
|
|
674
|
+
}
|
|
509
675
|
return onoff;
|
|
510
676
|
}
|
|
511
677
|
|
|
@@ -513,11 +679,11 @@ VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
|
|
|
513
679
|
* call-seq:
|
|
514
680
|
* Curl::Multi.autoclose => true|false
|
|
515
681
|
*
|
|
516
|
-
* Get the
|
|
682
|
+
* Get the current Ractor's default autoclose setting for Curl::Multi handles.
|
|
517
683
|
*
|
|
518
684
|
*/
|
|
519
685
|
VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
|
|
520
|
-
return
|
|
686
|
+
return curb_multi_autoclose_enabled() ? Qtrue : Qfalse;
|
|
521
687
|
}
|
|
522
688
|
|
|
523
689
|
/*
|
|
@@ -652,7 +818,7 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
|
652
818
|
}
|
|
653
819
|
}
|
|
654
820
|
|
|
655
|
-
rbce->multi_attachment_generation
|
|
821
|
+
rbce->multi_attachment_generation = ++rbcm->attachment_sequence;
|
|
656
822
|
st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy);
|
|
657
823
|
|
|
658
824
|
/* track a reference to associated multi handle */
|
|
@@ -772,42 +938,17 @@ struct multi_complete_callback_args {
|
|
|
772
938
|
ruby_curl_multi *rbcm;
|
|
773
939
|
ruby_curl_easy *rbce;
|
|
774
940
|
int result;
|
|
775
|
-
|
|
941
|
+
/* rbcm->attachment_sequence before the callbacks ran; easies stamped later
|
|
942
|
+
* were attached (or re-attached) by the callbacks. */
|
|
943
|
+
unsigned long attachment_sequence_before;
|
|
776
944
|
};
|
|
777
945
|
|
|
778
|
-
static int snapshot_attached_easy_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
779
|
-
st_table *snapshot = (st_table *)arg;
|
|
780
|
-
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
781
|
-
|
|
782
|
-
if (!rbce) {
|
|
783
|
-
return ST_CONTINUE;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
st_insert(snapshot, key, (st_data_t)rbce->multi_attachment_generation);
|
|
787
|
-
return ST_CONTINUE;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
static st_table *capture_attached_easy_snapshot(ruby_curl_multi *rbcm) {
|
|
791
|
-
st_table *snapshot = st_init_numtable();
|
|
792
|
-
|
|
793
|
-
if (!snapshot) {
|
|
794
|
-
rb_raise(rb_eNoMemError, "Failed to allocate multi callback snapshot table");
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
if (rbcm && rbcm->attached) {
|
|
798
|
-
st_foreach(rbcm->attached, snapshot_attached_easy_i, (st_data_t)snapshot);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
return snapshot;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
946
|
struct collect_new_attached_easies_ctx {
|
|
805
|
-
|
|
947
|
+
unsigned long attachment_sequence_before;
|
|
806
948
|
VALUE easies;
|
|
807
949
|
};
|
|
808
950
|
|
|
809
951
|
static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
810
|
-
st_data_t existing = 0;
|
|
811
952
|
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
812
953
|
struct collect_new_attached_easies_ctx *ctx = (struct collect_new_attached_easies_ctx *)arg;
|
|
813
954
|
|
|
@@ -815,24 +956,25 @@ static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t
|
|
|
815
956
|
return ST_CONTINUE;
|
|
816
957
|
}
|
|
817
958
|
|
|
818
|
-
if (
|
|
819
|
-
existing != (st_data_t)rbce->multi_attachment_generation) {
|
|
959
|
+
if (rbce->multi_attachment_generation > ctx->attachment_sequence_before) {
|
|
820
960
|
rb_ary_push(ctx->easies, (VALUE)val);
|
|
821
961
|
}
|
|
822
962
|
|
|
823
963
|
return ST_CONTINUE;
|
|
824
964
|
}
|
|
825
965
|
|
|
826
|
-
|
|
966
|
+
/* Only runs when a completion callback raised, so this walk over the
|
|
967
|
+
* attached table stays off the per-completion path. */
|
|
968
|
+
static void rb_curl_multi_remove_easies_attached_since(VALUE self, ruby_curl_multi *rbcm, unsigned long attachment_sequence_before) {
|
|
827
969
|
struct collect_new_attached_easies_ctx ctx;
|
|
828
970
|
VALUE easies = rb_ary_new();
|
|
829
971
|
long index;
|
|
830
972
|
|
|
831
|
-
if (!rbcm || !
|
|
973
|
+
if (!rbcm || !rbcm->attached) {
|
|
832
974
|
return;
|
|
833
975
|
}
|
|
834
976
|
|
|
835
|
-
ctx.
|
|
977
|
+
ctx.attachment_sequence_before = attachment_sequence_before;
|
|
836
978
|
ctx.easies = easies;
|
|
837
979
|
st_foreach(rbcm->attached, collect_new_attached_easies_i, (st_data_t)&ctx);
|
|
838
980
|
|
|
@@ -869,7 +1011,7 @@ static void stash_and_raise_status_callback_error_if_unmasked(struct multi_compl
|
|
|
869
1011
|
}
|
|
870
1012
|
|
|
871
1013
|
stash_multi_exception_if_unset(args->self, exception, args->easy);
|
|
872
|
-
|
|
1014
|
+
rb_curl_multi_remove_easies_attached_since(args->self, args->rbcm, args->attachment_sequence_before);
|
|
873
1015
|
rb_exc_raise(exception);
|
|
874
1016
|
}
|
|
875
1017
|
|
|
@@ -972,11 +1114,6 @@ static VALUE rb_curl_multi_finish_completion_callbacks(VALUE argp) {
|
|
|
972
1114
|
args->rbce->multi = Qnil;
|
|
973
1115
|
}
|
|
974
1116
|
|
|
975
|
-
if (args->attached_snapshot) {
|
|
976
|
-
st_free_table(args->attached_snapshot);
|
|
977
|
-
args->attached_snapshot = NULL;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
1117
|
return Qnil;
|
|
981
1118
|
}
|
|
982
1119
|
|
|
@@ -1037,7 +1174,7 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
|
1037
1174
|
rbcm,
|
|
1038
1175
|
rbce,
|
|
1039
1176
|
result,
|
|
1040
|
-
|
|
1177
|
+
rbcm->attachment_sequence
|
|
1041
1178
|
};
|
|
1042
1179
|
rb_ensure(rb_curl_multi_run_completion_callbacks, (VALUE)&args,
|
|
1043
1180
|
rb_curl_multi_finish_completion_callbacks, (VALUE)&args);
|
|
@@ -1084,21 +1221,14 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
|
1084
1221
|
CURLMcode mcode;
|
|
1085
1222
|
|
|
1086
1223
|
/*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
1090
|
-
*
|
|
1091
|
-
* here as long as mcode is CURLM_CALL_MULTIPERFORM.
|
|
1092
|
-
*/
|
|
1093
|
-
do {
|
|
1094
|
-
mcode = curl_multi_perform(multi_handle, still_running);
|
|
1095
|
-
} while (mcode == CURLM_CALL_MULTI_PERFORM);
|
|
1096
|
-
|
|
1097
|
-
/*
|
|
1098
|
-
* Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
|
|
1224
|
+
* Perform all pending actions on every easy handle attached to this multi
|
|
1225
|
+
* handle. libcurl stopped returning CURLM_CALL_MULTI_PERFORM here in 7.20
|
|
1226
|
+
* (curb supports 7.58+); should it appear, treat it as success: the drive
|
|
1227
|
+
* loop calls curl_multi_perform again on its next pass.
|
|
1099
1228
|
*/
|
|
1229
|
+
mcode = curl_multi_perform(multi_handle, still_running);
|
|
1100
1230
|
|
|
1101
|
-
if (mcode != CURLM_OK) {
|
|
1231
|
+
if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
|
|
1102
1232
|
raise_curl_multi_error_exception(mcode);
|
|
1103
1233
|
}
|
|
1104
1234
|
|
|
@@ -1113,10 +1243,36 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
|
1113
1243
|
|
|
1114
1244
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
1115
1245
|
/* ---- socket-action implementation (scheduler-friendly) ---- */
|
|
1246
|
+
|
|
1247
|
+
/*
|
|
1248
|
+
* Under a fiber scheduler, mirror libcurl's socket interest into a kernel
|
|
1249
|
+
* event queue (epoll or kqueue) and wait on that single descriptor with the
|
|
1250
|
+
* scheduler's io_wait hook. Every scheduler implements io_wait, so this avoids
|
|
1251
|
+
* the optional io_select hook (which Async implements by starting a thread
|
|
1252
|
+
* per call) and the one-descriptor fallback for schedulers without it.
|
|
1253
|
+
*/
|
|
1254
|
+
#if defined(HAVE_SYS_EPOLL_H) && defined(HAVE_EPOLL_CREATE1)
|
|
1255
|
+
#include <sys/epoll.h>
|
|
1256
|
+
#define CURB_SOCKET_POLLER 1
|
|
1257
|
+
#define CURB_SOCKET_POLLER_EPOLL 1
|
|
1258
|
+
#define CURB_SOCKET_POLLER_NAME "epoll"
|
|
1259
|
+
#elif defined(HAVE_SYS_EVENT_H) && defined(HAVE_KQUEUE)
|
|
1260
|
+
#include <sys/types.h>
|
|
1261
|
+
#include <sys/event.h>
|
|
1262
|
+
#define CURB_SOCKET_POLLER 1
|
|
1263
|
+
#define CURB_SOCKET_POLLER_KQUEUE 1
|
|
1264
|
+
#define CURB_SOCKET_POLLER_NAME "kqueue"
|
|
1265
|
+
#endif
|
|
1266
|
+
|
|
1267
|
+
/* Events harvested per poller read; more are collected on following rounds. */
|
|
1268
|
+
#define CURB_SOCKET_POLLER_BATCH 64
|
|
1269
|
+
|
|
1116
1270
|
typedef struct {
|
|
1117
1271
|
st_table *sock_map; /* key: int fd, value: int 'what' (CURL_POLL_*) */
|
|
1118
1272
|
long long timeout_deadline_ms; /* absolute deadline for CURL_SOCKET_TIMEOUT */
|
|
1119
1273
|
VALUE io_cache; /* fd -> IO wrapper for fiber-scheduler waits */
|
|
1274
|
+
int poller_fd; /* epoll/kqueue descriptor, or -1 when unused */
|
|
1275
|
+
VALUE poller_io; /* IO wrapper for poller_fd passed to io_wait */
|
|
1120
1276
|
} multi_socket_ctx;
|
|
1121
1277
|
|
|
1122
1278
|
static long long multi_socket_current_time_ms(void) {
|
|
@@ -1172,30 +1328,6 @@ static const char *cselect_flags_str(int flags, char *buf, size_t n) {
|
|
|
1172
1328
|
#define cselect_flags_str(...) ""
|
|
1173
1329
|
#endif
|
|
1174
1330
|
|
|
1175
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1176
|
-
/* Protected call to rb_fiber_scheduler_io_wait to avoid unwinding into C on TypeError. */
|
|
1177
|
-
struct fiber_io_wait_args { VALUE scheduler; VALUE io; VALUE events; VALUE timeout; };
|
|
1178
|
-
static VALUE fiber_io_wait_protected(VALUE argp) {
|
|
1179
|
-
struct fiber_io_wait_args *a = (struct fiber_io_wait_args *)argp;
|
|
1180
|
-
return rb_fiber_scheduler_io_wait(a->scheduler, a->io, a->events, a->timeout);
|
|
1181
|
-
}
|
|
1182
|
-
#endif
|
|
1183
|
-
|
|
1184
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1185
|
-
struct fiber_io_select_args {
|
|
1186
|
-
VALUE scheduler;
|
|
1187
|
-
VALUE readables;
|
|
1188
|
-
VALUE writables;
|
|
1189
|
-
VALUE exceptables;
|
|
1190
|
-
VALUE timeout;
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
static VALUE fiber_io_select_protected(VALUE argp) {
|
|
1194
|
-
struct fiber_io_select_args *a = (struct fiber_io_select_args *)argp;
|
|
1195
|
-
return rb_fiber_scheduler_io_select(a->scheduler, a->readables, a->writables, a->exceptables, a->timeout);
|
|
1196
|
-
}
|
|
1197
|
-
#endif
|
|
1198
|
-
|
|
1199
1331
|
#if defined(RB_INTEGER_TYPE_P)
|
|
1200
1332
|
#define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value)
|
|
1201
1333
|
#else
|
|
@@ -1231,12 +1363,77 @@ static int multi_socket_cselect_flags_for_wait_events(int events) {
|
|
|
1231
1363
|
return flags;
|
|
1232
1364
|
}
|
|
1233
1365
|
|
|
1366
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1367
|
+
static int multi_socket_poller_create(void) {
|
|
1368
|
+
#if defined(CURB_SOCKET_POLLER_EPOLL)
|
|
1369
|
+
return epoll_create1(EPOLL_CLOEXEC);
|
|
1370
|
+
#else
|
|
1371
|
+
int fd = kqueue();
|
|
1372
|
+
if (fd >= 0) fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
1373
|
+
return fd;
|
|
1374
|
+
#endif
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/*
|
|
1378
|
+
* Bring the poller's interest for fd from old_what to new_what, where each is
|
|
1379
|
+
* a CURL_POLL_IN/OUT/INOUT mask and 0 means "not registered". Registration is
|
|
1380
|
+
* level-triggered so readiness left unconsumed is reported again. Errors are
|
|
1381
|
+
* ignored: the kernel drops closed descriptors from both epoll and kqueue on
|
|
1382
|
+
* its own, so a stale delete or a re-add after fd reuse is harmless.
|
|
1383
|
+
*/
|
|
1384
|
+
static void multi_socket_poller_update(multi_socket_ctx *ctx, int fd, int old_what, int new_what) {
|
|
1385
|
+
if (!ctx || ctx->poller_fd < 0 || old_what == new_what) return;
|
|
1386
|
+
#if defined(CURB_SOCKET_POLLER_EPOLL)
|
|
1387
|
+
{
|
|
1388
|
+
struct epoll_event ev;
|
|
1389
|
+
memset(&ev, 0, sizeof(ev));
|
|
1390
|
+
ev.data.fd = fd;
|
|
1391
|
+
if (new_what & CURL_POLL_IN) ev.events |= EPOLLIN;
|
|
1392
|
+
if (new_what & CURL_POLL_OUT) ev.events |= EPOLLOUT;
|
|
1393
|
+
|
|
1394
|
+
if (new_what == 0) {
|
|
1395
|
+
epoll_ctl(ctx->poller_fd, EPOLL_CTL_DEL, fd, &ev);
|
|
1396
|
+
} else if (old_what == 0) {
|
|
1397
|
+
if (epoll_ctl(ctx->poller_fd, EPOLL_CTL_ADD, fd, &ev) != 0 && errno == EEXIST) {
|
|
1398
|
+
epoll_ctl(ctx->poller_fd, EPOLL_CTL_MOD, fd, &ev);
|
|
1399
|
+
}
|
|
1400
|
+
} else if (epoll_ctl(ctx->poller_fd, EPOLL_CTL_MOD, fd, &ev) != 0 && errno == ENOENT) {
|
|
1401
|
+
epoll_ctl(ctx->poller_fd, EPOLL_CTL_ADD, fd, &ev);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
#else
|
|
1405
|
+
{
|
|
1406
|
+
/* kqueue tracks read and write as separate filters. Apply each change on
|
|
1407
|
+
* its own so one failing change cannot skip the others. */
|
|
1408
|
+
struct kevent change;
|
|
1409
|
+
int old_read = (old_what & CURL_POLL_IN) != 0;
|
|
1410
|
+
int new_read = (new_what & CURL_POLL_IN) != 0;
|
|
1411
|
+
int old_write = (old_what & CURL_POLL_OUT) != 0;
|
|
1412
|
+
int new_write = (new_what & CURL_POLL_OUT) != 0;
|
|
1413
|
+
|
|
1414
|
+
if (old_read != new_read) {
|
|
1415
|
+
EV_SET(&change, fd, EVFILT_READ, new_read ? EV_ADD : EV_DELETE, 0, 0, NULL);
|
|
1416
|
+
kevent(ctx->poller_fd, &change, 1, NULL, 0, NULL);
|
|
1417
|
+
}
|
|
1418
|
+
if (old_write != new_write) {
|
|
1419
|
+
EV_SET(&change, fd, EVFILT_WRITE, new_write ? EV_ADD : EV_DELETE, 0, 0, NULL);
|
|
1420
|
+
kevent(ctx->poller_fd, &change, 1, NULL, 0, NULL);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
#endif
|
|
1424
|
+
}
|
|
1425
|
+
#endif
|
|
1426
|
+
|
|
1234
1427
|
static void multi_socket_forget_fd(multi_socket_ctx *ctx, int fd) {
|
|
1235
1428
|
st_data_t key = (st_data_t)fd;
|
|
1236
1429
|
st_data_t rec;
|
|
1237
1430
|
|
|
1238
1431
|
if (!ctx) return;
|
|
1239
|
-
if (ctx->sock_map
|
|
1432
|
+
if (ctx->sock_map && st_delete(ctx->sock_map, &key, &rec)) {
|
|
1433
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1434
|
+
multi_socket_poller_update(ctx, fd, (int)rec, 0);
|
|
1435
|
+
#endif
|
|
1436
|
+
}
|
|
1240
1437
|
if (!NIL_P(ctx->io_cache)) rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1241
1438
|
}
|
|
1242
1439
|
|
|
@@ -1266,10 +1463,14 @@ static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, v
|
|
|
1266
1463
|
} else {
|
|
1267
1464
|
/* store current interest mask for this fd */
|
|
1268
1465
|
st_data_t key = (st_data_t)fd;
|
|
1269
|
-
st_data_t old_what;
|
|
1270
|
-
|
|
1466
|
+
st_data_t old_what = 0;
|
|
1467
|
+
int tracked = st_lookup(ctx->sock_map, key, &old_what);
|
|
1468
|
+
if (tracked && (int)old_what != what && !NIL_P(ctx->io_cache)) {
|
|
1271
1469
|
rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1272
1470
|
}
|
|
1471
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1472
|
+
multi_socket_poller_update(ctx, fd, tracked ? (int)old_what : 0, what);
|
|
1473
|
+
#endif
|
|
1273
1474
|
st_insert(ctx->sock_map, (st_data_t)fd, (st_data_t)what);
|
|
1274
1475
|
#if CURB_SOCKET_DEBUG
|
|
1275
1476
|
{
|
|
@@ -1349,13 +1550,6 @@ static int st_pick_one_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
|
1349
1550
|
s->found = 1;
|
|
1350
1551
|
return ST_STOP;
|
|
1351
1552
|
}
|
|
1352
|
-
struct counter_state { int count; };
|
|
1353
|
-
static int st_count_i(st_data_t k, st_data_t v, st_data_t argp) {
|
|
1354
|
-
(void)k; (void)v;
|
|
1355
|
-
struct counter_state *c = (struct counter_state *)argp;
|
|
1356
|
-
c->count++;
|
|
1357
|
-
return ST_CONTINUE;
|
|
1358
|
-
}
|
|
1359
1553
|
|
|
1360
1554
|
static const char *multi_socket_io_mode_for_curl_poll(int what) {
|
|
1361
1555
|
if (what == CURL_POLL_IN) return "r";
|
|
@@ -1380,6 +1574,112 @@ static VALUE multi_socket_io_for_fd_protected(VALUE argp) {
|
|
|
1380
1574
|
return multi_socket_io_for_fd(a->ctx, a->fd, a->what);
|
|
1381
1575
|
}
|
|
1382
1576
|
|
|
1577
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1578
|
+
static VALUE multi_socket_poller_io(VALUE self, multi_socket_ctx *ctx) {
|
|
1579
|
+
if (NIL_P(ctx->poller_io)) {
|
|
1580
|
+
VALUE io = rb_funcall(rb_cIO, rb_intern("for_fd"), 2, INT2NUM(ctx->poller_fd), rb_str_new_cstr("r"));
|
|
1581
|
+
/* The drive loop's ensure closes poller_fd itself. */
|
|
1582
|
+
rb_funcall(io, rb_intern("autoclose="), 1, Qfalse);
|
|
1583
|
+
ctx->poller_io = io;
|
|
1584
|
+
rb_ivar_set(self, id_socket_poller_io_ivar, io);
|
|
1585
|
+
}
|
|
1586
|
+
return ctx->poller_io;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/*
|
|
1590
|
+
* Report readiness the way select() does: an error or hangup marks the socket
|
|
1591
|
+
* ready for whatever libcurl is waiting on, and libcurl then observes the
|
|
1592
|
+
* condition from the read or write itself. (CURL_CSELECT_ERR would make it
|
|
1593
|
+
* fail the transfer outright, even with response data still buffered.)
|
|
1594
|
+
*/
|
|
1595
|
+
static int multi_socket_poller_flags(int ready_in, int ready_out, int failed, int what) {
|
|
1596
|
+
int flags = 0;
|
|
1597
|
+
|
|
1598
|
+
if (ready_in) flags |= CURL_CSELECT_IN;
|
|
1599
|
+
if (ready_out) flags |= CURL_CSELECT_OUT;
|
|
1600
|
+
if (failed) flags |= multi_socket_cselect_flags_for_curl_poll(what);
|
|
1601
|
+
|
|
1602
|
+
return flags;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
static void multi_socket_poller_act(ruby_curl_multi *rbcm, multi_socket_ctx *ctx, int fd, int ready_in, int ready_out, int failed, int *dispatched) {
|
|
1606
|
+
st_data_t what;
|
|
1607
|
+
int flags;
|
|
1608
|
+
CURLMcode mrc;
|
|
1609
|
+
|
|
1610
|
+
/* An earlier action in this batch may have made libcurl drop the socket. */
|
|
1611
|
+
if (!st_lookup(ctx->sock_map, (st_data_t)fd, &what)) return;
|
|
1612
|
+
|
|
1613
|
+
flags = multi_socket_poller_flags(ready_in, ready_out, failed, (int)what);
|
|
1614
|
+
if (!flags) return;
|
|
1615
|
+
|
|
1616
|
+
#if CURB_SOCKET_DEBUG
|
|
1617
|
+
{
|
|
1618
|
+
char b[32];
|
|
1619
|
+
curb_debugf("[curb.socket] poller socket_action fd=%d flags=%s", fd, cselect_flags_str(flags, b, sizeof(b)));
|
|
1620
|
+
}
|
|
1621
|
+
#endif
|
|
1622
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)fd, flags, &rbcm->running);
|
|
1623
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1624
|
+
(*dispatched)++;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/* Collect pending poller events without blocking and hand each ready socket
|
|
1628
|
+
* to libcurl. Returns the number of socket actions performed. */
|
|
1629
|
+
static int multi_socket_poller_dispatch(ruby_curl_multi *rbcm, multi_socket_ctx *ctx) {
|
|
1630
|
+
int dispatched = 0;
|
|
1631
|
+
int rounds = 0;
|
|
1632
|
+
/* kqueue reports read and write readiness as separate events. */
|
|
1633
|
+
int max_rounds = (int)((ctx->sock_map->num_entries * 2) / CURB_SOCKET_POLLER_BATCH) + 1;
|
|
1634
|
+
int n, i;
|
|
1635
|
+
|
|
1636
|
+
do {
|
|
1637
|
+
#if defined(CURB_SOCKET_POLLER_EPOLL)
|
|
1638
|
+
struct epoll_event events[CURB_SOCKET_POLLER_BATCH];
|
|
1639
|
+
n = epoll_wait(ctx->poller_fd, events, CURB_SOCKET_POLLER_BATCH, 0);
|
|
1640
|
+
if (n < 0) {
|
|
1641
|
+
if (errno != EINTR) rb_sys_fail("epoll_wait");
|
|
1642
|
+
n = 0;
|
|
1643
|
+
}
|
|
1644
|
+
for (i = 0; i < n; i++) {
|
|
1645
|
+
uint32_t ev = events[i].events;
|
|
1646
|
+
multi_socket_poller_act(rbcm, ctx, events[i].data.fd,
|
|
1647
|
+
(ev & EPOLLIN) != 0, (ev & EPOLLOUT) != 0,
|
|
1648
|
+
(ev & (EPOLLERR | EPOLLHUP)) != 0, &dispatched);
|
|
1649
|
+
}
|
|
1650
|
+
#else
|
|
1651
|
+
struct kevent events[CURB_SOCKET_POLLER_BATCH];
|
|
1652
|
+
struct timespec no_wait = {0, 0};
|
|
1653
|
+
n = kevent(ctx->poller_fd, NULL, 0, events, CURB_SOCKET_POLLER_BATCH, &no_wait);
|
|
1654
|
+
if (n < 0) {
|
|
1655
|
+
if (errno != EINTR) rb_sys_fail("kevent");
|
|
1656
|
+
n = 0;
|
|
1657
|
+
}
|
|
1658
|
+
for (i = 0; i < n; i++) {
|
|
1659
|
+
if (events[i].flags & EV_ERROR) continue;
|
|
1660
|
+
multi_socket_poller_act(rbcm, ctx, (int)events[i].ident,
|
|
1661
|
+
events[i].filter == EVFILT_READ, events[i].filter == EVFILT_WRITE,
|
|
1662
|
+
0, &dispatched);
|
|
1663
|
+
}
|
|
1664
|
+
#endif
|
|
1665
|
+
} while (n == CURB_SOCKET_POLLER_BATCH && ++rounds < max_rounds);
|
|
1666
|
+
|
|
1667
|
+
return dispatched;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
/* Wait for any tracked socket through the scheduler's io_wait hook on the
|
|
1671
|
+
* poller descriptor, then dispatch whatever became ready. */
|
|
1672
|
+
static int multi_socket_poller_wait(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE scheduler, struct timeval *tv) {
|
|
1673
|
+
VALUE io = multi_socket_poller_io(self, ctx);
|
|
1674
|
+
double timeout_s = (double)tv->tv_sec + ((double)tv->tv_usec / 1e6);
|
|
1675
|
+
|
|
1676
|
+
/* The hook's return value varies across schedulers; the non-blocking
|
|
1677
|
+
* dispatch below is the source of truth for what is ready. */
|
|
1678
|
+
curb_fiber_scheduler_io_wait(scheduler, io, INT2NUM(RB_WAITFD_IN), rb_float_new(timeout_s));
|
|
1679
|
+
return multi_socket_poller_dispatch(rbcm, ctx);
|
|
1680
|
+
}
|
|
1681
|
+
#endif
|
|
1682
|
+
|
|
1383
1683
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1384
1684
|
struct build_io_select_arrays_args {
|
|
1385
1685
|
multi_socket_ctx *ctx;
|
|
@@ -1387,6 +1687,7 @@ struct build_io_select_arrays_args {
|
|
|
1387
1687
|
VALUE writables;
|
|
1388
1688
|
VALUE exceptables;
|
|
1389
1689
|
int failed;
|
|
1690
|
+
int state;
|
|
1390
1691
|
};
|
|
1391
1692
|
|
|
1392
1693
|
static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
@@ -1405,12 +1706,7 @@ static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp
|
|
|
1405
1706
|
io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1406
1707
|
if (io_state || NIL_P(io)) {
|
|
1407
1708
|
if (io_state) {
|
|
1408
|
-
|
|
1409
|
-
VALUE err = rb_errinfo();
|
|
1410
|
-
VALUE msg = rb_obj_as_string(err);
|
|
1411
|
-
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1412
|
-
#endif
|
|
1413
|
-
rb_set_errinfo(Qnil);
|
|
1709
|
+
a->state = io_state;
|
|
1414
1710
|
}
|
|
1415
1711
|
a->failed = 1;
|
|
1416
1712
|
return ST_STOP;
|
|
@@ -1455,16 +1751,19 @@ static int collect_io_select_ready_i(st_data_t key, st_data_t val, st_data_t arg
|
|
|
1455
1751
|
#endif
|
|
1456
1752
|
|
|
1457
1753
|
static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) {
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1754
|
+
CURLMcode mrc;
|
|
1755
|
+
|
|
1756
|
+
do {
|
|
1757
|
+
/* Prime the state: let libcurl act on timeouts to set up sockets. */
|
|
1758
|
+
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1759
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1760
|
+
curb_debugf("[curb.socket] drive: initial socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1761
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1762
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1464
1763
|
|
|
1465
|
-
|
|
1764
|
+
while (rbcm->running) {
|
|
1466
1765
|
struct timeval tv = {0, 0};
|
|
1467
|
-
long wait_ms =
|
|
1766
|
+
long wait_ms = curb_multi_default_timeout();
|
|
1468
1767
|
|
|
1469
1768
|
if (multi_socket_timer_due(ctx)) {
|
|
1470
1769
|
ctx->timeout_deadline_ms = -1;
|
|
@@ -1473,6 +1772,10 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1473
1772
|
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1474
1773
|
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1475
1774
|
rb_curl_multi_yield_if_given(self, block);
|
|
1775
|
+
if (rbcm->running && multi_socket_timer_due(ctx)) {
|
|
1776
|
+
/* A repeatedly rearmed 0ms timer must still let sibling fibers run. */
|
|
1777
|
+
curb_multi_scheduler_yield();
|
|
1778
|
+
}
|
|
1476
1779
|
continue;
|
|
1477
1780
|
}
|
|
1478
1781
|
|
|
@@ -1492,13 +1795,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1492
1795
|
if (st.found) { wait_fd = st.fd; wait_what = st.what; }
|
|
1493
1796
|
}
|
|
1494
1797
|
|
|
1495
|
-
|
|
1496
|
-
int count_tracked = 0;
|
|
1497
|
-
if (ctx->sock_map) {
|
|
1498
|
-
struct counter_state cs = { 0 };
|
|
1499
|
-
st_foreach(ctx->sock_map, st_count_i, (st_data_t)&cs);
|
|
1500
|
-
count_tracked = cs.count;
|
|
1501
|
-
}
|
|
1798
|
+
int count_tracked = ctx->sock_map ? (int)ctx->sock_map->num_entries : 0;
|
|
1502
1799
|
|
|
1503
1800
|
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);
|
|
1504
1801
|
|
|
@@ -1507,29 +1804,42 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1507
1804
|
int ready_flags = 0;
|
|
1508
1805
|
|
|
1509
1806
|
int handled_wait = 0;
|
|
1510
|
-
|
|
1807
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1808
|
+
if (count_tracked > 0 && ctx->poller_fd >= 0) {
|
|
1809
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1810
|
+
if (scheduler != Qnil) {
|
|
1811
|
+
/* Ready sockets are dispatched inside, so any_ready stays 0 and
|
|
1812
|
+
* the single-fd dispatch below is skipped. */
|
|
1813
|
+
int dispatched = multi_socket_poller_wait(self, rbcm, ctx, scheduler, &tv);
|
|
1814
|
+
curb_debugf("[curb.socket] poller wait dispatched=%d", dispatched);
|
|
1815
|
+
did_timeout = !dispatched && multi_socket_timer_due(ctx);
|
|
1816
|
+
handled_wait = 1;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
#endif
|
|
1820
|
+
if (handled_wait) {
|
|
1821
|
+
/* The poller already waited and dispatched. */
|
|
1822
|
+
} else if (count_tracked > 1) {
|
|
1511
1823
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1512
1824
|
{
|
|
1513
|
-
VALUE scheduler =
|
|
1825
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1514
1826
|
if (scheduler != Qnil) {
|
|
1515
1827
|
VALUE readables = rb_ary_new();
|
|
1516
1828
|
VALUE writables = rb_ary_new();
|
|
1517
1829
|
VALUE exceptables = rb_ary_new();
|
|
1518
|
-
struct build_io_select_arrays_args build_args = { ctx, readables, writables, exceptables, 0 };
|
|
1830
|
+
struct build_io_select_arrays_args build_args = { ctx, readables, writables, exceptables, 0, 0 };
|
|
1519
1831
|
st_foreach(ctx->sock_map, build_io_select_arrays_i, (st_data_t)&build_args);
|
|
1832
|
+
if (build_args.state) rb_jump_tag(build_args.state);
|
|
1520
1833
|
if (!build_args.failed) {
|
|
1521
1834
|
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1522
1835
|
VALUE timeout = rb_float_new(timeout_s);
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
curb_debugf("[curb.socket] scheduler io_select failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1531
|
-
#endif
|
|
1532
|
-
rb_set_errinfo(Qnil);
|
|
1836
|
+
VALUE ready = rb_fiber_scheduler_io_select(scheduler, readables, writables, exceptables, timeout);
|
|
1837
|
+
if (UNDEF_P(ready)) {
|
|
1838
|
+
/* The scheduler does not implement the optional io_select hook:
|
|
1839
|
+
* rb_fiber_scheduler_io_select returns Qundef immediately.
|
|
1840
|
+
* Leave handled_wait unset so the io_wait fallback below can
|
|
1841
|
+
* perform a cooperative wait. */
|
|
1842
|
+
curb_debugf("[curb.socket] scheduler io_select not implemented; falling back to io_wait");
|
|
1533
1843
|
} else {
|
|
1534
1844
|
handled_wait = 1;
|
|
1535
1845
|
any_ready = RB_TYPE_P(ready, T_ARRAY);
|
|
@@ -1568,8 +1878,36 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1568
1878
|
}
|
|
1569
1879
|
}
|
|
1570
1880
|
#endif
|
|
1881
|
+
/* Ruby versions without the public io_select C API, and newer
|
|
1882
|
+
* schedulers that omit the optional hook, wait cooperatively on one
|
|
1883
|
+
* representative descriptor, then use a zero-time select below to
|
|
1884
|
+
* collect readiness across the whole set. */
|
|
1885
|
+
if (!handled_wait && wait_fd >= 0) {
|
|
1886
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1887
|
+
if (scheduler != Qnil) {
|
|
1888
|
+
if (!multi_socket_fd_valid_p(wait_fd)) {
|
|
1889
|
+
multi_socket_forget_fd(ctx, wait_fd);
|
|
1890
|
+
did_timeout = 1;
|
|
1891
|
+
handled_wait = 1;
|
|
1892
|
+
} else {
|
|
1893
|
+
struct io_for_fd_args io_args = { ctx, wait_fd, wait_what };
|
|
1894
|
+
int io_state = 0;
|
|
1895
|
+
VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1896
|
+
if (io_state) rb_jump_tag(io_state);
|
|
1897
|
+
if (!NIL_P(io)) {
|
|
1898
|
+
int events = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
1899
|
+
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1900
|
+
curb_fiber_scheduler_io_wait(scheduler, io, INT2NUM(events), rb_float_new(timeout_s));
|
|
1901
|
+
tv.tv_sec = 0;
|
|
1902
|
+
tv.tv_usec = 0;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1571
1907
|
if (!handled_wait) {
|
|
1572
|
-
/* Multi-fd
|
|
1908
|
+
/* Multi-fd select. After the scheduler io_wait fallback above, tv is
|
|
1909
|
+
* zero and this only polls the complete descriptor set. Without a
|
|
1910
|
+
* scheduler it performs the normal GVL-releasing wait. */
|
|
1573
1911
|
rb_fdset_t rfds, wfds, efds;
|
|
1574
1912
|
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
|
1575
1913
|
int maxfd = -1;
|
|
@@ -1608,9 +1946,8 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1608
1946
|
handled_wait = 1;
|
|
1609
1947
|
}
|
|
1610
1948
|
} else if (count_tracked == 1) {
|
|
1611
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1612
1949
|
{
|
|
1613
|
-
VALUE scheduler =
|
|
1950
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1614
1951
|
if (scheduler != Qnil) {
|
|
1615
1952
|
int scheduler_wait_handled = 0;
|
|
1616
1953
|
int events = 0;
|
|
@@ -1620,11 +1957,9 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1620
1957
|
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1621
1958
|
VALUE timeout = rb_float_new(timeout_s);
|
|
1622
1959
|
if (wait_fd < 0) {
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
rb_thread_wait_for(tv);
|
|
1627
|
-
#endif
|
|
1960
|
+
/* No pollable fd yet: sleep via the scheduler's kernel_sleep so
|
|
1961
|
+
* sibling fibers keep running while libcurl gets ready. */
|
|
1962
|
+
curb_multi_scheduler_sleep(&tv);
|
|
1628
1963
|
did_timeout = multi_socket_timer_due(ctx);
|
|
1629
1964
|
scheduler_wait_handled = 1;
|
|
1630
1965
|
} else if (!multi_socket_fd_valid_p(wait_fd)) {
|
|
@@ -1635,44 +1970,25 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1635
1970
|
struct io_for_fd_args io_args = { ctx, wait_fd, wait_what };
|
|
1636
1971
|
int io_state = 0;
|
|
1637
1972
|
VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1638
|
-
if (io_state
|
|
1639
|
-
|
|
1640
|
-
#if CURB_SOCKET_DEBUG
|
|
1641
|
-
VALUE err = rb_errinfo();
|
|
1642
|
-
VALUE msg = rb_obj_as_string(err);
|
|
1643
|
-
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1644
|
-
#endif
|
|
1645
|
-
rb_set_errinfo(Qnil);
|
|
1646
|
-
}
|
|
1973
|
+
if (io_state) rb_jump_tag(io_state);
|
|
1974
|
+
if (NIL_P(io)) {
|
|
1647
1975
|
any_ready = 0;
|
|
1648
1976
|
} else {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
scheduler_wait_handled = 1;
|
|
1662
|
-
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1663
|
-
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1664
|
-
if (any_ready) {
|
|
1665
|
-
if (ready == Qtrue) {
|
|
1666
|
-
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1667
|
-
} else if (CURB_INTEGER_P(ready)) {
|
|
1668
|
-
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1669
|
-
if (ready_flags == 0) {
|
|
1670
|
-
any_ready = 0;
|
|
1671
|
-
did_timeout = multi_socket_timer_due(ctx);
|
|
1672
|
-
}
|
|
1673
|
-
} else {
|
|
1674
|
-
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1977
|
+
VALUE ready = curb_fiber_scheduler_io_wait(scheduler, io, INT2NUM(events), timeout);
|
|
1978
|
+
scheduler_wait_handled = 1;
|
|
1979
|
+
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1980
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1981
|
+
if (any_ready) {
|
|
1982
|
+
if (ready == Qtrue) {
|
|
1983
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1984
|
+
} else if (CURB_INTEGER_P(ready)) {
|
|
1985
|
+
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1986
|
+
if (ready_flags == 0) {
|
|
1987
|
+
any_ready = 0;
|
|
1988
|
+
did_timeout = multi_socket_timer_due(ctx);
|
|
1675
1989
|
}
|
|
1990
|
+
} else {
|
|
1991
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1676
1992
|
}
|
|
1677
1993
|
}
|
|
1678
1994
|
}
|
|
@@ -1680,7 +1996,6 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1680
1996
|
if (scheduler_wait_handled) handled_wait = 1;
|
|
1681
1997
|
}
|
|
1682
1998
|
}
|
|
1683
|
-
#endif
|
|
1684
1999
|
#if defined(HAVE_RB_WAIT_FOR_SINGLE_FD)
|
|
1685
2000
|
if (!handled_wait && wait_fd >= 0) {
|
|
1686
2001
|
int ev = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
@@ -1724,14 +2039,13 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1724
2039
|
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1725
2040
|
}
|
|
1726
2041
|
} else { /* count_tracked == 0 */
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
#endif
|
|
2042
|
+
/* No sockets exposed yet (e.g. libcurl's threaded resolver doing DNS):
|
|
2043
|
+
* sleep via the scheduler's kernel_sleep when one is active so sibling
|
|
2044
|
+
* fibers keep running, otherwise a plain thread wait. */
|
|
2045
|
+
curb_multi_scheduler_sleep(&tv);
|
|
1732
2046
|
/* libcurl can report active work without a socket callback or deadline;
|
|
1733
|
-
* drive the timeout socket after the
|
|
1734
|
-
*
|
|
2047
|
+
* drive the timeout socket after the sleep so the state machine does
|
|
2048
|
+
* not stall indefinitely. */
|
|
1735
2049
|
did_timeout = 1;
|
|
1736
2050
|
}
|
|
1737
2051
|
|
|
@@ -1759,7 +2073,13 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1759
2073
|
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1760
2074
|
curb_debugf("[curb.socket] processed completions; running=%d", rbcm->running);
|
|
1761
2075
|
rb_curl_multi_yield_if_given(self, block);
|
|
1762
|
-
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/* Match the legacy perform contract: the block gets a final idle yield,
|
|
2079
|
+
* and work queued from that yield is driven before perform returns. */
|
|
2080
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
2081
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
2082
|
+
} while (rbcm->running);
|
|
1763
2083
|
}
|
|
1764
2084
|
|
|
1765
2085
|
struct socket_drive_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; VALUE block; };
|
|
@@ -1769,6 +2089,9 @@ static VALUE ruby_curl_multi_socket_drive_body(VALUE argp) {
|
|
|
1769
2089
|
return Qtrue;
|
|
1770
2090
|
}
|
|
1771
2091
|
struct socket_cleanup_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; };
|
|
2092
|
+
static VALUE multi_socket_close_io(VALUE io) {
|
|
2093
|
+
return rb_funcall(io, rb_intern("close"), 0);
|
|
2094
|
+
}
|
|
1772
2095
|
static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
1773
2096
|
struct socket_cleanup_args *c = (struct socket_cleanup_args *)argp;
|
|
1774
2097
|
if (c->rbcm && c->rbcm->handle) {
|
|
@@ -1786,10 +2109,27 @@ static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
|
1786
2109
|
rb_hash_clear(c->ctx->io_cache);
|
|
1787
2110
|
}
|
|
1788
2111
|
c->ctx->io_cache = Qnil;
|
|
2112
|
+
if (!NIL_P(c->ctx->poller_io)) {
|
|
2113
|
+
/* Let Ruby and the scheduler drop the wrapper before the descriptor
|
|
2114
|
+
* goes away. autoclose is off, so this leaves poller_fd open. Keep any
|
|
2115
|
+
* exception already propagating out of the drive loop. */
|
|
2116
|
+
VALUE errinfo = rb_errinfo();
|
|
2117
|
+
int state = 0;
|
|
2118
|
+
rb_protect(multi_socket_close_io, c->ctx->poller_io, &state);
|
|
2119
|
+
rb_set_errinfo(errinfo);
|
|
2120
|
+
c->ctx->poller_io = Qnil;
|
|
2121
|
+
}
|
|
2122
|
+
if (c->ctx->poller_fd >= 0) {
|
|
2123
|
+
close(c->ctx->poller_fd);
|
|
2124
|
+
c->ctx->poller_fd = -1;
|
|
2125
|
+
}
|
|
1789
2126
|
}
|
|
1790
2127
|
if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_io_cache_ivar)) {
|
|
1791
2128
|
rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_io_cache_ivar));
|
|
1792
2129
|
}
|
|
2130
|
+
if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_poller_io_ivar)) {
|
|
2131
|
+
rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_poller_io_ivar));
|
|
2132
|
+
}
|
|
1793
2133
|
return Qnil;
|
|
1794
2134
|
}
|
|
1795
2135
|
|
|
@@ -1808,7 +2148,17 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1808
2148
|
ctx.sock_map = st_init_numtable();
|
|
1809
2149
|
ctx.timeout_deadline_ms = -1;
|
|
1810
2150
|
ctx.io_cache = rb_hash_new();
|
|
2151
|
+
ctx.poller_fd = -1;
|
|
2152
|
+
ctx.poller_io = Qnil;
|
|
1811
2153
|
rb_ivar_set(self, id_socket_io_cache_ivar, ctx.io_cache);
|
|
2154
|
+
#ifdef CURB_SOCKET_POLLER
|
|
2155
|
+
/* Only fiber-scheduler waits use the poller; thread-level waits keep using
|
|
2156
|
+
* rb_thread_fd_select. If it cannot be created the loop falls back to the
|
|
2157
|
+
* io_select/io_wait paths. */
|
|
2158
|
+
if (curb_fiber_scheduler_current() != Qnil) {
|
|
2159
|
+
ctx.poller_fd = multi_socket_poller_create();
|
|
2160
|
+
}
|
|
2161
|
+
#endif
|
|
1812
2162
|
|
|
1813
2163
|
/* install socket/timer callbacks */
|
|
1814
2164
|
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
|
|
@@ -1822,9 +2172,7 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1822
2172
|
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
|
1823
2173
|
|
|
1824
2174
|
/* finalize */
|
|
1825
|
-
|
|
1826
|
-
rb_curl_multi_yield_if_given(self, block);
|
|
1827
|
-
if (cCurlMutiAutoClose == 1) {
|
|
2175
|
+
if (curb_multi_autoclose_enabled()) {
|
|
1828
2176
|
rbcm->allow_close_during_perform = 1;
|
|
1829
2177
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
1830
2178
|
rbcm->allow_close_during_perform = 0;
|
|
@@ -1904,17 +2252,19 @@ static void *curb_select_without_gvl(void *args) {
|
|
|
1904
2252
|
static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
1905
2253
|
CURLMcode mcode;
|
|
1906
2254
|
ruby_curl_multi *rbcm;
|
|
2255
|
+
long timeout_milliseconds;
|
|
2256
|
+
VALUE block = Qnil;
|
|
2257
|
+
#ifndef CURB_USE_MULTI_POLL
|
|
1907
2258
|
int maxfd, rc = -1;
|
|
1908
2259
|
fd_set fdread, fdwrite, fdexcep;
|
|
1909
2260
|
#ifdef _WIN32
|
|
1910
2261
|
fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
|
|
1911
2262
|
#endif
|
|
1912
|
-
long timeout_milliseconds;
|
|
1913
2263
|
struct timeval tv = {0, 0};
|
|
1914
2264
|
struct timeval tv_100ms = {0, 100000};
|
|
1915
|
-
VALUE block = Qnil;
|
|
1916
2265
|
#if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
|
|
1917
2266
|
struct _select_set fdset_args;
|
|
2267
|
+
#endif
|
|
1918
2268
|
#endif
|
|
1919
2269
|
|
|
1920
2270
|
rb_scan_args(argc, argv, "0&", &block);
|
|
@@ -1925,7 +2275,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1925
2275
|
clear_multi_deferred_exception_source_id_if_any(self);
|
|
1926
2276
|
}
|
|
1927
2277
|
|
|
1928
|
-
timeout_milliseconds =
|
|
2278
|
+
timeout_milliseconds = curb_multi_default_timeout();
|
|
1929
2279
|
|
|
1930
2280
|
// Run curl_multi_perform for the first time to get the ball rolling
|
|
1931
2281
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
@@ -1959,31 +2309,53 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1959
2309
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
1960
2310
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
1961
2311
|
rb_curl_multi_yield_if_given(self, block);
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
}
|
|
1966
|
-
#endif
|
|
2312
|
+
/* Yield the fiber so repeated immediate curl timeouts cannot starve
|
|
2313
|
+
* sibling work on this thread. */
|
|
2314
|
+
curb_multi_scheduler_yield();
|
|
1967
2315
|
continue;
|
|
1968
2316
|
}
|
|
1969
2317
|
|
|
1970
|
-
if (timeout_milliseconds < 0 || timeout_milliseconds >
|
|
1971
|
-
timeout_milliseconds =
|
|
2318
|
+
if (timeout_milliseconds < 0 || timeout_milliseconds > curb_multi_default_timeout()) {
|
|
2319
|
+
timeout_milliseconds = curb_multi_default_timeout(); /* libcurl doesn't know how long to wait, use a default timeout */
|
|
1972
2320
|
/* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
|
|
1973
2321
|
}
|
|
1974
2322
|
|
|
1975
|
-
#if defined(
|
|
2323
|
+
#if defined(CURB_USE_MULTI_POLL)
|
|
2324
|
+
{
|
|
2325
|
+
struct multi_poll_args poll_args;
|
|
2326
|
+
poll_args.handle = rbcm->handle;
|
|
2327
|
+
poll_args.timeout_ms = timeout_milliseconds > INT_MAX ? INT_MAX : (int)timeout_milliseconds;
|
|
2328
|
+
poll_args.numfds = 0;
|
|
2329
|
+
poll_args.code = CURLM_OK;
|
|
2330
|
+
/*
|
|
2331
|
+
* Wait with the GVL released. curl_multi_poll sleeps for the full
|
|
2332
|
+
* timeout even when libcurl has no sockets to watch, so no separate
|
|
2333
|
+
* idle sleep is needed. Pending interrupts are checked when
|
|
2334
|
+
* rb_thread_call_without_gvl returns. Like rb_thread_fd_select, this
|
|
2335
|
+
* wait does not consult the fiber scheduler; perform routes to the
|
|
2336
|
+
* socket-action loop when a scheduler is active.
|
|
2337
|
+
*/
|
|
2338
|
+
rb_thread_call_without_gvl(curb_multi_poll_without_gvl, &poll_args,
|
|
2339
|
+
curb_multi_poll_ubf, rbcm->handle);
|
|
2340
|
+
if (poll_args.code != CURLM_OK) {
|
|
2341
|
+
raise_curl_multi_error_exception(poll_args.code);
|
|
2342
|
+
}
|
|
2343
|
+
rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
|
|
2344
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
2345
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
2346
|
+
}
|
|
2347
|
+
#elif defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT)
|
|
1976
2348
|
{
|
|
1977
2349
|
struct wait_args wait_args;
|
|
1978
2350
|
wait_args.handle = rbcm->handle;
|
|
1979
2351
|
wait_args.timeout_ms = timeout_milliseconds;
|
|
1980
2352
|
wait_args.numfds = 0;
|
|
1981
2353
|
/*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
*
|
|
1985
|
-
*
|
|
1986
|
-
*
|
|
2354
|
+
* Wait via curl_multi_wait with the GVL released so other Ruby
|
|
2355
|
+
* threads can continue to run. Like rb_thread_fd_select, this wait
|
|
2356
|
+
* does not consult the fiber scheduler; scheduler-aware waiting is
|
|
2357
|
+
* provided by the socket-action loop, which perform routes to when a
|
|
2358
|
+
* scheduler is active (see ruby_curl_multi_perform).
|
|
1987
2359
|
*/
|
|
1988
2360
|
CURLMcode wait_rc;
|
|
1989
2361
|
#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
@@ -1997,13 +2369,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1997
2369
|
raise_curl_multi_error_exception(wait_rc);
|
|
1998
2370
|
}
|
|
1999
2371
|
if (wait_args.numfds == 0) {
|
|
2000
|
-
|
|
2001
|
-
struct timeval tv_sleep = tv_100ms;
|
|
2002
|
-
/* Sleep in a scheduler-aware way. */
|
|
2003
|
-
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
2004
|
-
#else
|
|
2005
|
-
rb_thread_wait_for(tv_100ms);
|
|
2006
|
-
#endif
|
|
2372
|
+
curb_multi_scheduler_sleep(&tv_100ms);
|
|
2007
2373
|
}
|
|
2008
2374
|
/* Process pending transfers after waiting */
|
|
2009
2375
|
rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
|
|
@@ -2012,8 +2378,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2012
2378
|
}
|
|
2013
2379
|
#else
|
|
2014
2380
|
|
|
2015
|
-
|
|
2016
|
-
|
|
2381
|
+
/* Split into seconds and microseconds: a raw select() rejects a
|
|
2382
|
+
* tv_usec of 1000000 or more, which default_timeout > 1000 produced. */
|
|
2383
|
+
tv.tv_sec = timeout_milliseconds / 1000;
|
|
2384
|
+
tv.tv_usec = (timeout_milliseconds % 1000) * 1000;
|
|
2017
2385
|
|
|
2018
2386
|
FD_ZERO(&fdread);
|
|
2019
2387
|
FD_ZERO(&fdwrite);
|
|
@@ -2027,12 +2395,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2027
2395
|
|
|
2028
2396
|
if (maxfd == -1) {
|
|
2029
2397
|
/* libcurl recommends sleeping for 100ms */
|
|
2030
|
-
|
|
2031
|
-
struct timeval tv_sleep = tv_100ms;
|
|
2032
|
-
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
2033
|
-
#else
|
|
2034
|
-
rb_thread_wait_for(tv_100ms);
|
|
2035
|
-
#endif
|
|
2398
|
+
curb_multi_scheduler_sleep(&tv_100ms);
|
|
2036
2399
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
2037
2400
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
2038
2401
|
rb_curl_multi_yield_if_given(self, block);
|
|
@@ -2055,7 +2418,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2055
2418
|
#endif
|
|
2056
2419
|
|
|
2057
2420
|
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
2058
|
-
/*
|
|
2421
|
+
/* Wait with the GVL released. Note rb_thread_fd_select does not consult
|
|
2422
|
+
* the fiber scheduler — under a scheduler, perform routes to the
|
|
2423
|
+
* socket-action loop instead (see ruby_curl_multi_perform). Build
|
|
2424
|
+
* rb_fdset_t sets. */
|
|
2059
2425
|
{
|
|
2060
2426
|
rb_fdset_t rfds, wfds, efds;
|
|
2061
2427
|
rb_fd_init(&rfds);
|
|
@@ -2115,7 +2481,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2115
2481
|
rb_curl_multi_yield_if_given(self, block);
|
|
2116
2482
|
} while( rbcm->running );
|
|
2117
2483
|
|
|
2118
|
-
if (
|
|
2484
|
+
if (curb_multi_autoclose_enabled()) {
|
|
2119
2485
|
rbcm->allow_close_during_perform = 1;
|
|
2120
2486
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
2121
2487
|
rbcm->allow_close_during_perform = 0;
|
|
@@ -2167,9 +2533,34 @@ static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE sel
|
|
|
2167
2533
|
}
|
|
2168
2534
|
|
|
2169
2535
|
VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
2536
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2537
|
+
/*
|
|
2538
|
+
* The legacy fdset loop waits with rb_thread_fd_select, which releases the
|
|
2539
|
+
* GVL but never consults the fiber scheduler, stalling every other fiber on
|
|
2540
|
+
* this thread for the duration of the transfers. When the calling fiber
|
|
2541
|
+
* runs under a fiber scheduler, use the socket-action drive loop, which
|
|
2542
|
+
* waits through the scheduler's io_wait/io_select/kernel_sleep hooks.
|
|
2543
|
+
*/
|
|
2544
|
+
if (curb_fiber_scheduler_current() != Qnil) {
|
|
2545
|
+
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
|
|
2546
|
+
}
|
|
2547
|
+
#endif
|
|
2170
2548
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
|
|
2171
2549
|
}
|
|
2172
2550
|
|
|
2551
|
+
/*
|
|
2552
|
+
* Name of the kernel event queue the socket-action loop waits on under a
|
|
2553
|
+
* fiber scheduler ("epoll" or "kqueue"), or nil when this build has none.
|
|
2554
|
+
*/
|
|
2555
|
+
static VALUE ruby_curl_multi_socket_poller(VALUE klass) {
|
|
2556
|
+
(void)klass;
|
|
2557
|
+
#ifdef CURB_SOCKET_POLLER
|
|
2558
|
+
return rb_str_new_cstr(CURB_SOCKET_POLLER_NAME);
|
|
2559
|
+
#else
|
|
2560
|
+
return Qnil;
|
|
2561
|
+
#endif
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2173
2564
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2174
2565
|
VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
|
|
2175
2566
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
|
|
@@ -2233,10 +2624,31 @@ static void curl_multi_mark(void *ptr) {
|
|
|
2233
2624
|
/* =================== INIT LIB =====================*/
|
|
2234
2625
|
void init_curb_multi() {
|
|
2235
2626
|
idCall = rb_intern("call");
|
|
2627
|
+
cRubyFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
|
2628
|
+
rb_global_variable(&cRubyFiber);
|
|
2629
|
+
cRubyRactor = Qnil;
|
|
2630
|
+
id_ractor_current = rb_intern("current");
|
|
2631
|
+
id_ractor_aref = rb_intern("[]");
|
|
2632
|
+
id_ractor_aset = rb_intern("[]=");
|
|
2633
|
+
id_ractor_default_timeout_key = rb_intern("__curb_multi_default_timeout");
|
|
2634
|
+
id_ractor_autoclose_key = rb_intern("__curb_multi_autoclose");
|
|
2635
|
+
if (rb_const_defined(rb_cObject, rb_intern("Ractor"))) {
|
|
2636
|
+
VALUE ractor = rb_const_get(rb_cObject, rb_intern("Ractor"));
|
|
2637
|
+
VALUE current = rb_funcall(ractor, id_ractor_current, 0);
|
|
2638
|
+
if (rb_respond_to(current, id_ractor_aref) && rb_respond_to(current, id_ractor_aset)) {
|
|
2639
|
+
cRubyRactor = ractor;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
rb_global_variable(&cRubyRactor);
|
|
2643
|
+
id_fiber_blocking_p = rb_intern("blocking?");
|
|
2644
|
+
id_fiber_scheduler = rb_intern("scheduler");
|
|
2645
|
+
id_kernel_sleep = rb_intern("kernel_sleep");
|
|
2646
|
+
id_io_wait = rb_intern("io_wait");
|
|
2236
2647
|
id_deferred_exception_ivar = rb_intern("@__curb_deferred_exception");
|
|
2237
2648
|
id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id");
|
|
2238
2649
|
id_native_safety_signatures_ivar = rb_intern("@__curb_native_safety_signatures");
|
|
2239
2650
|
id_socket_io_cache_ivar = rb_intern("@__curb_socket_io_cache");
|
|
2651
|
+
id_socket_poller_io_ivar = rb_intern("@__curb_socket_poller_io");
|
|
2240
2652
|
cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
|
|
2241
2653
|
|
|
2242
2654
|
rb_define_alloc_func(cCurlMulti, ruby_curl_multi_alloc);
|
|
@@ -2254,14 +2666,16 @@ void init_curb_multi() {
|
|
|
2254
2666
|
rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
|
|
2255
2667
|
rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
|
|
2256
2668
|
/*
|
|
2257
|
-
*
|
|
2258
|
-
*
|
|
2259
|
-
*
|
|
2669
|
+
* perform uses the legacy fdset loop by default, but routes to the
|
|
2670
|
+
* socket-action drive loop when the calling fiber runs under a fiber
|
|
2671
|
+
* scheduler so sibling fibers keep running during transfers. The
|
|
2672
|
+
* socket-action path is also exposed directly as _socket_perform.
|
|
2260
2673
|
*/
|
|
2261
2674
|
rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
|
|
2262
2675
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2263
2676
|
rb_define_private_method(cCurlMulti, "_socket_perform", ruby_curl_multi_socket_perform, -1);
|
|
2264
2677
|
#endif
|
|
2678
|
+
rb_define_private_method(rb_singleton_class(cCurlMulti), "_socket_poller", ruby_curl_multi_socket_poller, 0);
|
|
2265
2679
|
rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
|
|
2266
2680
|
rb_define_private_method(cCurlMulti, "_mark_closed", ruby_curl_multi_mark_closed, 0);
|
|
2267
2681
|
}
|