curb 1.3.7 → 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 +38 -1
- data/Rakefile +12 -1
- data/ext/curb.c +17 -4
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +71 -31
- data/ext/curb_easy.h +1 -0
- data/ext/curb_multi.c +428 -95
- data/ext/curb_multi.h +4 -0
- data/ext/curb_postfield.c +31 -31
- data/ext/extconf.rb +6 -0
- data/lib/curl/easy.rb +18 -9
- data/lib/curl.rb +49 -12
- data/tests/helper.rb +122 -3
- data/tests/tc_curl_easy.rb +59 -4
- data/tests/tc_curl_easy_cookielist.rb +8 -4
- 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_postfield.rb +39 -0
- data/tests/tc_fiber_scheduler.rb +101 -2
- data/tests/tc_ractor.rb +94 -0
- metadata +8 -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
|
|
|
@@ -62,7 +63,40 @@
|
|
|
62
63
|
#include <fcntl.h>
|
|
63
64
|
#endif
|
|
64
65
|
|
|
65
|
-
|
|
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)
|
|
66
100
|
struct wait_args {
|
|
67
101
|
CURLM *handle;
|
|
68
102
|
long timeout_ms;
|
|
@@ -76,10 +110,16 @@ static void *curl_multi_wait_wrapper(void *p) {
|
|
|
76
110
|
#endif
|
|
77
111
|
|
|
78
112
|
static VALUE cRubyFiber;
|
|
113
|
+
static VALUE cRubyRactor;
|
|
79
114
|
static ID id_fiber_blocking_p;
|
|
80
115
|
static ID id_fiber_scheduler;
|
|
81
116
|
static ID id_kernel_sleep;
|
|
82
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;
|
|
83
123
|
|
|
84
124
|
/*
|
|
85
125
|
* Ruby 3.0 introduced the Fiber scheduler at the Ruby level, but did not ship
|
|
@@ -163,6 +203,7 @@ static ID id_deferred_exception_ivar;
|
|
|
163
203
|
static ID id_deferred_exception_source_id_ivar;
|
|
164
204
|
static ID id_native_safety_signatures_ivar;
|
|
165
205
|
static ID id_socket_io_cache_ivar;
|
|
206
|
+
static ID id_socket_poller_io_ivar;
|
|
166
207
|
|
|
167
208
|
#ifdef RDOC_NEVER_DEFINED
|
|
168
209
|
mCurl = rb_define_module("Curl");
|
|
@@ -173,6 +214,32 @@ VALUE cCurlMulti;
|
|
|
173
214
|
static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
|
|
174
215
|
static char cCurlMutiAutoClose = 0;
|
|
175
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
|
+
|
|
176
243
|
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result);
|
|
177
244
|
static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
|
|
178
245
|
static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
|
|
@@ -563,12 +630,18 @@ static VALUE ruby_curl_multi_initialize(VALUE self) {
|
|
|
563
630
|
* call-seq:
|
|
564
631
|
* Curl::Multi.default_timeout = 4 => 4
|
|
565
632
|
*
|
|
566
|
-
* Set the
|
|
567
|
-
*
|
|
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.
|
|
568
636
|
*
|
|
569
637
|
*/
|
|
570
638
|
VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
571
|
-
|
|
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
|
+
}
|
|
572
645
|
return timeout;
|
|
573
646
|
}
|
|
574
647
|
|
|
@@ -576,23 +649,29 @@ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
|
576
649
|
* call-seq:
|
|
577
650
|
* Curl::Multi.default_timeout = 4 => 4
|
|
578
651
|
*
|
|
579
|
-
* Get the
|
|
652
|
+
* Get the current Ractor's default timeout for Curl::Multi handles.
|
|
580
653
|
*
|
|
581
654
|
*/
|
|
582
655
|
VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
|
|
583
|
-
return LONG2NUM(
|
|
656
|
+
return LONG2NUM(curb_multi_default_timeout());
|
|
584
657
|
}
|
|
585
658
|
|
|
586
659
|
/*
|
|
587
660
|
* call-seq:
|
|
588
661
|
* Curl::Multi.autoclose = true => true
|
|
589
662
|
*
|
|
590
|
-
* Automatically close open connections after each request
|
|
591
|
-
* 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.
|
|
592
666
|
*
|
|
593
667
|
*/
|
|
594
668
|
VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
|
|
595
|
-
|
|
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
|
+
}
|
|
596
675
|
return onoff;
|
|
597
676
|
}
|
|
598
677
|
|
|
@@ -600,11 +679,11 @@ VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
|
|
|
600
679
|
* call-seq:
|
|
601
680
|
* Curl::Multi.autoclose => true|false
|
|
602
681
|
*
|
|
603
|
-
* Get the
|
|
682
|
+
* Get the current Ractor's default autoclose setting for Curl::Multi handles.
|
|
604
683
|
*
|
|
605
684
|
*/
|
|
606
685
|
VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
|
|
607
|
-
return
|
|
686
|
+
return curb_multi_autoclose_enabled() ? Qtrue : Qfalse;
|
|
608
687
|
}
|
|
609
688
|
|
|
610
689
|
/*
|
|
@@ -739,7 +818,7 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
|
739
818
|
}
|
|
740
819
|
}
|
|
741
820
|
|
|
742
|
-
rbce->multi_attachment_generation
|
|
821
|
+
rbce->multi_attachment_generation = ++rbcm->attachment_sequence;
|
|
743
822
|
st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy);
|
|
744
823
|
|
|
745
824
|
/* track a reference to associated multi handle */
|
|
@@ -859,42 +938,17 @@ struct multi_complete_callback_args {
|
|
|
859
938
|
ruby_curl_multi *rbcm;
|
|
860
939
|
ruby_curl_easy *rbce;
|
|
861
940
|
int result;
|
|
862
|
-
|
|
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;
|
|
863
944
|
};
|
|
864
945
|
|
|
865
|
-
static int snapshot_attached_easy_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
866
|
-
st_table *snapshot = (st_table *)arg;
|
|
867
|
-
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
868
|
-
|
|
869
|
-
if (!rbce) {
|
|
870
|
-
return ST_CONTINUE;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
st_insert(snapshot, key, (st_data_t)rbce->multi_attachment_generation);
|
|
874
|
-
return ST_CONTINUE;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
static st_table *capture_attached_easy_snapshot(ruby_curl_multi *rbcm) {
|
|
878
|
-
st_table *snapshot = st_init_numtable();
|
|
879
|
-
|
|
880
|
-
if (!snapshot) {
|
|
881
|
-
rb_raise(rb_eNoMemError, "Failed to allocate multi callback snapshot table");
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
if (rbcm && rbcm->attached) {
|
|
885
|
-
st_foreach(rbcm->attached, snapshot_attached_easy_i, (st_data_t)snapshot);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
return snapshot;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
946
|
struct collect_new_attached_easies_ctx {
|
|
892
|
-
|
|
947
|
+
unsigned long attachment_sequence_before;
|
|
893
948
|
VALUE easies;
|
|
894
949
|
};
|
|
895
950
|
|
|
896
951
|
static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
897
|
-
st_data_t existing = 0;
|
|
898
952
|
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
899
953
|
struct collect_new_attached_easies_ctx *ctx = (struct collect_new_attached_easies_ctx *)arg;
|
|
900
954
|
|
|
@@ -902,24 +956,25 @@ static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t
|
|
|
902
956
|
return ST_CONTINUE;
|
|
903
957
|
}
|
|
904
958
|
|
|
905
|
-
if (
|
|
906
|
-
existing != (st_data_t)rbce->multi_attachment_generation) {
|
|
959
|
+
if (rbce->multi_attachment_generation > ctx->attachment_sequence_before) {
|
|
907
960
|
rb_ary_push(ctx->easies, (VALUE)val);
|
|
908
961
|
}
|
|
909
962
|
|
|
910
963
|
return ST_CONTINUE;
|
|
911
964
|
}
|
|
912
965
|
|
|
913
|
-
|
|
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) {
|
|
914
969
|
struct collect_new_attached_easies_ctx ctx;
|
|
915
970
|
VALUE easies = rb_ary_new();
|
|
916
971
|
long index;
|
|
917
972
|
|
|
918
|
-
if (!rbcm || !
|
|
973
|
+
if (!rbcm || !rbcm->attached) {
|
|
919
974
|
return;
|
|
920
975
|
}
|
|
921
976
|
|
|
922
|
-
ctx.
|
|
977
|
+
ctx.attachment_sequence_before = attachment_sequence_before;
|
|
923
978
|
ctx.easies = easies;
|
|
924
979
|
st_foreach(rbcm->attached, collect_new_attached_easies_i, (st_data_t)&ctx);
|
|
925
980
|
|
|
@@ -956,7 +1011,7 @@ static void stash_and_raise_status_callback_error_if_unmasked(struct multi_compl
|
|
|
956
1011
|
}
|
|
957
1012
|
|
|
958
1013
|
stash_multi_exception_if_unset(args->self, exception, args->easy);
|
|
959
|
-
|
|
1014
|
+
rb_curl_multi_remove_easies_attached_since(args->self, args->rbcm, args->attachment_sequence_before);
|
|
960
1015
|
rb_exc_raise(exception);
|
|
961
1016
|
}
|
|
962
1017
|
|
|
@@ -1059,11 +1114,6 @@ static VALUE rb_curl_multi_finish_completion_callbacks(VALUE argp) {
|
|
|
1059
1114
|
args->rbce->multi = Qnil;
|
|
1060
1115
|
}
|
|
1061
1116
|
|
|
1062
|
-
if (args->attached_snapshot) {
|
|
1063
|
-
st_free_table(args->attached_snapshot);
|
|
1064
|
-
args->attached_snapshot = NULL;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
1117
|
return Qnil;
|
|
1068
1118
|
}
|
|
1069
1119
|
|
|
@@ -1124,7 +1174,7 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
|
1124
1174
|
rbcm,
|
|
1125
1175
|
rbce,
|
|
1126
1176
|
result,
|
|
1127
|
-
|
|
1177
|
+
rbcm->attachment_sequence
|
|
1128
1178
|
};
|
|
1129
1179
|
rb_ensure(rb_curl_multi_run_completion_callbacks, (VALUE)&args,
|
|
1130
1180
|
rb_curl_multi_finish_completion_callbacks, (VALUE)&args);
|
|
@@ -1171,21 +1221,14 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
|
1171
1221
|
CURLMcode mcode;
|
|
1172
1222
|
|
|
1173
1223
|
/*
|
|
1174
|
-
*
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1178
|
-
* here as long as mcode is CURLM_CALL_MULTIPERFORM.
|
|
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.
|
|
1179
1228
|
*/
|
|
1180
|
-
|
|
1181
|
-
mcode = curl_multi_perform(multi_handle, still_running);
|
|
1182
|
-
} while (mcode == CURLM_CALL_MULTI_PERFORM);
|
|
1229
|
+
mcode = curl_multi_perform(multi_handle, still_running);
|
|
1183
1230
|
|
|
1184
|
-
|
|
1185
|
-
* Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
|
|
1186
|
-
*/
|
|
1187
|
-
|
|
1188
|
-
if (mcode != CURLM_OK) {
|
|
1231
|
+
if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
|
|
1189
1232
|
raise_curl_multi_error_exception(mcode);
|
|
1190
1233
|
}
|
|
1191
1234
|
|
|
@@ -1200,10 +1243,36 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
|
1200
1243
|
|
|
1201
1244
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
1202
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
|
+
|
|
1203
1270
|
typedef struct {
|
|
1204
1271
|
st_table *sock_map; /* key: int fd, value: int 'what' (CURL_POLL_*) */
|
|
1205
1272
|
long long timeout_deadline_ms; /* absolute deadline for CURL_SOCKET_TIMEOUT */
|
|
1206
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 */
|
|
1207
1276
|
} multi_socket_ctx;
|
|
1208
1277
|
|
|
1209
1278
|
static long long multi_socket_current_time_ms(void) {
|
|
@@ -1294,12 +1363,77 @@ static int multi_socket_cselect_flags_for_wait_events(int events) {
|
|
|
1294
1363
|
return flags;
|
|
1295
1364
|
}
|
|
1296
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
|
+
|
|
1297
1427
|
static void multi_socket_forget_fd(multi_socket_ctx *ctx, int fd) {
|
|
1298
1428
|
st_data_t key = (st_data_t)fd;
|
|
1299
1429
|
st_data_t rec;
|
|
1300
1430
|
|
|
1301
1431
|
if (!ctx) return;
|
|
1302
|
-
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
|
+
}
|
|
1303
1437
|
if (!NIL_P(ctx->io_cache)) rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1304
1438
|
}
|
|
1305
1439
|
|
|
@@ -1329,10 +1463,14 @@ static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, v
|
|
|
1329
1463
|
} else {
|
|
1330
1464
|
/* store current interest mask for this fd */
|
|
1331
1465
|
st_data_t key = (st_data_t)fd;
|
|
1332
|
-
st_data_t old_what;
|
|
1333
|
-
|
|
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)) {
|
|
1334
1469
|
rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1335
1470
|
}
|
|
1471
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1472
|
+
multi_socket_poller_update(ctx, fd, tracked ? (int)old_what : 0, what);
|
|
1473
|
+
#endif
|
|
1336
1474
|
st_insert(ctx->sock_map, (st_data_t)fd, (st_data_t)what);
|
|
1337
1475
|
#if CURB_SOCKET_DEBUG
|
|
1338
1476
|
{
|
|
@@ -1412,13 +1550,6 @@ static int st_pick_one_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
|
1412
1550
|
s->found = 1;
|
|
1413
1551
|
return ST_STOP;
|
|
1414
1552
|
}
|
|
1415
|
-
struct counter_state { int count; };
|
|
1416
|
-
static int st_count_i(st_data_t k, st_data_t v, st_data_t argp) {
|
|
1417
|
-
(void)k; (void)v;
|
|
1418
|
-
struct counter_state *c = (struct counter_state *)argp;
|
|
1419
|
-
c->count++;
|
|
1420
|
-
return ST_CONTINUE;
|
|
1421
|
-
}
|
|
1422
1553
|
|
|
1423
1554
|
static const char *multi_socket_io_mode_for_curl_poll(int what) {
|
|
1424
1555
|
if (what == CURL_POLL_IN) return "r";
|
|
@@ -1443,6 +1574,112 @@ static VALUE multi_socket_io_for_fd_protected(VALUE argp) {
|
|
|
1443
1574
|
return multi_socket_io_for_fd(a->ctx, a->fd, a->what);
|
|
1444
1575
|
}
|
|
1445
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
|
+
|
|
1446
1683
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1447
1684
|
struct build_io_select_arrays_args {
|
|
1448
1685
|
multi_socket_ctx *ctx;
|
|
@@ -1526,7 +1763,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1526
1763
|
|
|
1527
1764
|
while (rbcm->running) {
|
|
1528
1765
|
struct timeval tv = {0, 0};
|
|
1529
|
-
long wait_ms =
|
|
1766
|
+
long wait_ms = curb_multi_default_timeout();
|
|
1530
1767
|
|
|
1531
1768
|
if (multi_socket_timer_due(ctx)) {
|
|
1532
1769
|
ctx->timeout_deadline_ms = -1;
|
|
@@ -1558,13 +1795,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1558
1795
|
if (st.found) { wait_fd = st.fd; wait_what = st.what; }
|
|
1559
1796
|
}
|
|
1560
1797
|
|
|
1561
|
-
|
|
1562
|
-
int count_tracked = 0;
|
|
1563
|
-
if (ctx->sock_map) {
|
|
1564
|
-
struct counter_state cs = { 0 };
|
|
1565
|
-
st_foreach(ctx->sock_map, st_count_i, (st_data_t)&cs);
|
|
1566
|
-
count_tracked = cs.count;
|
|
1567
|
-
}
|
|
1798
|
+
int count_tracked = ctx->sock_map ? (int)ctx->sock_map->num_entries : 0;
|
|
1568
1799
|
|
|
1569
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);
|
|
1570
1801
|
|
|
@@ -1573,7 +1804,22 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1573
1804
|
int ready_flags = 0;
|
|
1574
1805
|
|
|
1575
1806
|
int handled_wait = 0;
|
|
1576
|
-
|
|
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) {
|
|
1577
1823
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1578
1824
|
{
|
|
1579
1825
|
VALUE scheduler = curb_fiber_scheduler_current();
|
|
@@ -1843,6 +2089,9 @@ static VALUE ruby_curl_multi_socket_drive_body(VALUE argp) {
|
|
|
1843
2089
|
return Qtrue;
|
|
1844
2090
|
}
|
|
1845
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
|
+
}
|
|
1846
2095
|
static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
1847
2096
|
struct socket_cleanup_args *c = (struct socket_cleanup_args *)argp;
|
|
1848
2097
|
if (c->rbcm && c->rbcm->handle) {
|
|
@@ -1860,10 +2109,27 @@ static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
|
1860
2109
|
rb_hash_clear(c->ctx->io_cache);
|
|
1861
2110
|
}
|
|
1862
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
|
+
}
|
|
1863
2126
|
}
|
|
1864
2127
|
if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_io_cache_ivar)) {
|
|
1865
2128
|
rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_io_cache_ivar));
|
|
1866
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
|
+
}
|
|
1867
2133
|
return Qnil;
|
|
1868
2134
|
}
|
|
1869
2135
|
|
|
@@ -1882,7 +2148,17 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1882
2148
|
ctx.sock_map = st_init_numtable();
|
|
1883
2149
|
ctx.timeout_deadline_ms = -1;
|
|
1884
2150
|
ctx.io_cache = rb_hash_new();
|
|
2151
|
+
ctx.poller_fd = -1;
|
|
2152
|
+
ctx.poller_io = Qnil;
|
|
1885
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
|
|
1886
2162
|
|
|
1887
2163
|
/* install socket/timer callbacks */
|
|
1888
2164
|
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
|
|
@@ -1896,7 +2172,7 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1896
2172
|
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
|
1897
2173
|
|
|
1898
2174
|
/* finalize */
|
|
1899
|
-
if (
|
|
2175
|
+
if (curb_multi_autoclose_enabled()) {
|
|
1900
2176
|
rbcm->allow_close_during_perform = 1;
|
|
1901
2177
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
1902
2178
|
rbcm->allow_close_during_perform = 0;
|
|
@@ -1976,17 +2252,19 @@ static void *curb_select_without_gvl(void *args) {
|
|
|
1976
2252
|
static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
1977
2253
|
CURLMcode mcode;
|
|
1978
2254
|
ruby_curl_multi *rbcm;
|
|
2255
|
+
long timeout_milliseconds;
|
|
2256
|
+
VALUE block = Qnil;
|
|
2257
|
+
#ifndef CURB_USE_MULTI_POLL
|
|
1979
2258
|
int maxfd, rc = -1;
|
|
1980
2259
|
fd_set fdread, fdwrite, fdexcep;
|
|
1981
2260
|
#ifdef _WIN32
|
|
1982
2261
|
fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
|
|
1983
2262
|
#endif
|
|
1984
|
-
long timeout_milliseconds;
|
|
1985
2263
|
struct timeval tv = {0, 0};
|
|
1986
2264
|
struct timeval tv_100ms = {0, 100000};
|
|
1987
|
-
VALUE block = Qnil;
|
|
1988
2265
|
#if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
|
|
1989
2266
|
struct _select_set fdset_args;
|
|
2267
|
+
#endif
|
|
1990
2268
|
#endif
|
|
1991
2269
|
|
|
1992
2270
|
rb_scan_args(argc, argv, "0&", &block);
|
|
@@ -1997,7 +2275,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1997
2275
|
clear_multi_deferred_exception_source_id_if_any(self);
|
|
1998
2276
|
}
|
|
1999
2277
|
|
|
2000
|
-
timeout_milliseconds =
|
|
2278
|
+
timeout_milliseconds = curb_multi_default_timeout();
|
|
2001
2279
|
|
|
2002
2280
|
// Run curl_multi_perform for the first time to get the ball rolling
|
|
2003
2281
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
@@ -2037,12 +2315,36 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2037
2315
|
continue;
|
|
2038
2316
|
}
|
|
2039
2317
|
|
|
2040
|
-
if (timeout_milliseconds < 0 || timeout_milliseconds >
|
|
2041
|
-
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 */
|
|
2042
2320
|
/* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
|
|
2043
2321
|
}
|
|
2044
2322
|
|
|
2045
|
-
#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)
|
|
2046
2348
|
{
|
|
2047
2349
|
struct wait_args wait_args;
|
|
2048
2350
|
wait_args.handle = rbcm->handle;
|
|
@@ -2076,8 +2378,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2076
2378
|
}
|
|
2077
2379
|
#else
|
|
2078
2380
|
|
|
2079
|
-
|
|
2080
|
-
|
|
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;
|
|
2081
2385
|
|
|
2082
2386
|
FD_ZERO(&fdread);
|
|
2083
2387
|
FD_ZERO(&fdwrite);
|
|
@@ -2177,7 +2481,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2177
2481
|
rb_curl_multi_yield_if_given(self, block);
|
|
2178
2482
|
} while( rbcm->running );
|
|
2179
2483
|
|
|
2180
|
-
if (
|
|
2484
|
+
if (curb_multi_autoclose_enabled()) {
|
|
2181
2485
|
rbcm->allow_close_during_perform = 1;
|
|
2182
2486
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
2183
2487
|
rbcm->allow_close_during_perform = 0;
|
|
@@ -2244,6 +2548,19 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
2244
2548
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
|
|
2245
2549
|
}
|
|
2246
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
|
+
|
|
2247
2564
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2248
2565
|
VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
|
|
2249
2566
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
|
|
@@ -2309,6 +2626,20 @@ void init_curb_multi() {
|
|
|
2309
2626
|
idCall = rb_intern("call");
|
|
2310
2627
|
cRubyFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
|
2311
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);
|
|
2312
2643
|
id_fiber_blocking_p = rb_intern("blocking?");
|
|
2313
2644
|
id_fiber_scheduler = rb_intern("scheduler");
|
|
2314
2645
|
id_kernel_sleep = rb_intern("kernel_sleep");
|
|
@@ -2317,6 +2648,7 @@ void init_curb_multi() {
|
|
|
2317
2648
|
id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id");
|
|
2318
2649
|
id_native_safety_signatures_ivar = rb_intern("@__curb_native_safety_signatures");
|
|
2319
2650
|
id_socket_io_cache_ivar = rb_intern("@__curb_socket_io_cache");
|
|
2651
|
+
id_socket_poller_io_ivar = rb_intern("@__curb_socket_poller_io");
|
|
2320
2652
|
cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
|
|
2321
2653
|
|
|
2322
2654
|
rb_define_alloc_func(cCurlMulti, ruby_curl_multi_alloc);
|
|
@@ -2343,6 +2675,7 @@ void init_curb_multi() {
|
|
|
2343
2675
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2344
2676
|
rb_define_private_method(cCurlMulti, "_socket_perform", ruby_curl_multi_socket_perform, -1);
|
|
2345
2677
|
#endif
|
|
2678
|
+
rb_define_private_method(rb_singleton_class(cCurlMulti), "_socket_poller", ruby_curl_multi_socket_poller, 0);
|
|
2346
2679
|
rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
|
|
2347
2680
|
rb_define_private_method(cCurlMulti, "_mark_closed", ruby_curl_multi_mark_closed, 0);
|
|
2348
2681
|
}
|