curb 1.3.5 → 1.3.7
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 +90 -13
- data/Rakefile +8 -3
- data/doc.rb +48 -8
- data/ext/curb.c +33 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +1471 -87
- data/ext/curb_easy.h +26 -0
- data/ext/curb_errors.c +2 -0
- data/ext/curb_errors.h +1 -0
- data/ext/curb_multi.c +257 -130
- data/ext/curb_multi.h +1 -0
- data/ext/extconf.rb +12 -0
- data/lib/curl/download.rb +160 -0
- data/lib/curl/easy.rb +113 -13
- data/lib/curl/multi.rb +172 -39
- data/lib/curl.rb +471 -11
- data/tests/bug_poison.rb +29 -0
- data/tests/helper.rb +178 -1
- data/tests/io_select_less_scheduler_probe.rb +105 -0
- data/tests/tc_curl_download.rb +86 -0
- data/tests/tc_curl_easy.rb +77 -0
- data/tests/tc_curl_easy_resolve.rb +2 -0
- data/tests/tc_curl_maxfilesize.rb +201 -1
- data/tests/tc_curl_multi.rb +258 -0
- data/tests/tc_curl_native_coverage.rb +1 -0
- data/tests/tc_curl_network_policy.rb +1475 -0
- data/tests/tc_curl_protocols.rb +351 -0
- data/tests/tc_fiber_scheduler.rb +373 -4
- data/tests/tc_ftp_options.rb +58 -0
- data/tests/tc_gc_compact.rb +53 -0
- data/tests/tc_test_server_methods.rb +15 -0
- metadata +9 -2
data/ext/curb_easy.h
CHANGED
|
@@ -11,6 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
#include <curl/easy.h>
|
|
13
13
|
|
|
14
|
+
#define CURB_NETWORK_POLICY_NONE 0
|
|
15
|
+
#define CURB_NETWORK_POLICY_PUBLIC 1
|
|
16
|
+
|
|
17
|
+
#define CURB_CIDR_FAMILY_IPV4 4
|
|
18
|
+
#define CURB_CIDR_FAMILY_IPV6 6
|
|
19
|
+
|
|
20
|
+
typedef struct {
|
|
21
|
+
unsigned char family;
|
|
22
|
+
unsigned char prefix_bits;
|
|
23
|
+
unsigned char address[16];
|
|
24
|
+
} curb_cidr_rule;
|
|
25
|
+
|
|
14
26
|
#ifdef CURL_VERSION_SSL
|
|
15
27
|
#if LIBCURL_VERSION_NUM >= 0x070b00
|
|
16
28
|
# if LIBCURL_VERSION_NUM <= 0x071004
|
|
@@ -38,6 +50,7 @@ typedef struct {
|
|
|
38
50
|
|
|
39
51
|
/* Buffer for error details from CURLOPT_ERRORBUFFER */
|
|
40
52
|
char err_buf[CURL_ERROR_SIZE];
|
|
53
|
+
char unsafe_destination_error[CURL_ERROR_SIZE];
|
|
41
54
|
|
|
42
55
|
VALUE self; /* owning Ruby object */
|
|
43
56
|
VALUE opts; /* rather then allocate everything we might need to store, allocate a Hash and only store objects we actually use... */
|
|
@@ -67,6 +80,7 @@ typedef struct {
|
|
|
67
80
|
long ftp_filemethod;
|
|
68
81
|
long http_version;
|
|
69
82
|
unsigned short resolve_mode;
|
|
83
|
+
unsigned short network_policy;
|
|
70
84
|
|
|
71
85
|
/* bool flags */
|
|
72
86
|
char proxy_tunnel;
|
|
@@ -83,13 +97,25 @@ typedef struct {
|
|
|
83
97
|
char cookielist_engine_enabled; /* track if CURLOPT_COOKIELIST was used with a non-command to enable engine */
|
|
84
98
|
char ignore_content_length;
|
|
85
99
|
char callback_active;
|
|
100
|
+
char unsafe_destination_blocked;
|
|
101
|
+
char allow_proxy;
|
|
102
|
+
char allow_unix_socket;
|
|
103
|
+
char forbid_reuse_set;
|
|
104
|
+
unsigned int native_active;
|
|
105
|
+
long forbid_reuse;
|
|
86
106
|
|
|
87
107
|
struct curl_slist *curl_headers;
|
|
88
108
|
struct curl_slist *curl_proxy_headers;
|
|
89
109
|
struct curl_slist *curl_ftp_commands;
|
|
90
110
|
struct curl_slist *curl_resolve;
|
|
111
|
+
struct curl_slist *curl_connect_to;
|
|
112
|
+
curb_cidr_rule *network_allowed_cidr_rules;
|
|
113
|
+
char **network_allowed_hosts;
|
|
91
114
|
|
|
92
115
|
unsigned long multi_attachment_generation;
|
|
116
|
+
curl_off_t downloaded_body_bytes;
|
|
117
|
+
size_t network_allowed_cidr_rule_count;
|
|
118
|
+
size_t network_allowed_host_count;
|
|
93
119
|
int last_result; /* last result code from multi loop */
|
|
94
120
|
|
|
95
121
|
} ruby_curl_easy;
|
data/ext/curb_errors.c
CHANGED
|
@@ -110,6 +110,7 @@ VALUE eCurlErrSSLShutdownFailed;
|
|
|
110
110
|
VALUE eCurlErrAgain;
|
|
111
111
|
VALUE eCurlErrSSLCRLBadfile;
|
|
112
112
|
VALUE eCurlErrSSLIssuerError;
|
|
113
|
+
VALUE eCurlErrUnsafeDestination;
|
|
113
114
|
|
|
114
115
|
/* multi errors */
|
|
115
116
|
VALUE mCurlErrFailedInit;
|
|
@@ -699,6 +700,7 @@ void init_curb_errors() {
|
|
|
699
700
|
eCurlErrSSLCacertBadfile = rb_define_class_under(mCurlErr, "SSLCaertBadFile", eCurlErrError);
|
|
700
701
|
eCurlErrSSLCRLBadfile = rb_define_class_under(mCurlErr, "SSLCRLBadfile", eCurlErrError);
|
|
701
702
|
eCurlErrSSLIssuerError = rb_define_class_under(mCurlErr, "SSLIssuerError", eCurlErrError);
|
|
703
|
+
eCurlErrUnsafeDestination = rb_define_class_under(mCurlErr, "UnsafeDestinationError", eCurlErrError);
|
|
702
704
|
eCurlErrSSLShutdownFailed = rb_define_class_under(mCurlErr, "SSLShutdownFailed", eCurlErrError);
|
|
703
705
|
eCurlErrSSH = rb_define_class_under(mCurlErr, "SSH", eCurlErrError);
|
|
704
706
|
|
data/ext/curb_errors.h
CHANGED
|
@@ -106,6 +106,7 @@ extern VALUE eCurlErrSSLShutdownFailed;
|
|
|
106
106
|
extern VALUE eCurlErrAgain;
|
|
107
107
|
extern VALUE eCurlErrSSLCRLBadfile;
|
|
108
108
|
extern VALUE eCurlErrSSLIssuerError;
|
|
109
|
+
extern VALUE eCurlErrUnsafeDestination;
|
|
109
110
|
|
|
110
111
|
/* multi errors */
|
|
111
112
|
extern VALUE mCurlErrFailedInit;
|
data/ext/curb_multi.c
CHANGED
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
#define CURB_MAYBE_UNUSED_DECL
|
|
53
53
|
#endif
|
|
54
54
|
|
|
55
|
+
/* Older rubies do not define UNDEF_P. */
|
|
56
|
+
#ifndef UNDEF_P
|
|
57
|
+
#define UNDEF_P(obj) ((VALUE)(obj) == Qundef)
|
|
58
|
+
#endif
|
|
59
|
+
|
|
55
60
|
#ifdef _WIN32
|
|
56
61
|
// for O_RDWR and O_BINARY
|
|
57
62
|
#include <fcntl.h>
|
|
@@ -70,10 +75,93 @@ static void *curl_multi_wait_wrapper(void *p) {
|
|
|
70
75
|
}
|
|
71
76
|
#endif
|
|
72
77
|
|
|
78
|
+
static VALUE cRubyFiber;
|
|
79
|
+
static ID id_fiber_blocking_p;
|
|
80
|
+
static ID id_fiber_scheduler;
|
|
81
|
+
static ID id_kernel_sleep;
|
|
82
|
+
static ID id_io_wait;
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* Ruby 3.0 introduced the Fiber scheduler at the Ruby level, but did not ship
|
|
86
|
+
* the public ruby/fiber/scheduler.h C API. Use the public C entry points when
|
|
87
|
+
* available and otherwise dispatch through Fiber and the scheduler object.
|
|
88
|
+
*/
|
|
89
|
+
static VALUE curb_fiber_scheduler_current(void) {
|
|
90
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
91
|
+
return rb_fiber_scheduler_current();
|
|
92
|
+
#else
|
|
93
|
+
VALUE scheduler;
|
|
94
|
+
|
|
95
|
+
if (!rb_respond_to(cRubyFiber, id_fiber_scheduler)) return Qnil;
|
|
96
|
+
|
|
97
|
+
scheduler = rb_funcall(cRubyFiber, id_fiber_scheduler, 0);
|
|
98
|
+
if (NIL_P(scheduler)) return Qnil;
|
|
99
|
+
|
|
100
|
+
/* Fiber.scheduler returns the thread scheduler even from a blocking fiber,
|
|
101
|
+
* while rb_fiber_scheduler_current returns nil there. Match the latter so
|
|
102
|
+
* ordinary blocking fibers keep using the legacy multi loop. */
|
|
103
|
+
if (rb_respond_to(cRubyFiber, id_fiber_blocking_p) &&
|
|
104
|
+
RTEST(rb_funcall(cRubyFiber, id_fiber_blocking_p, 0))) {
|
|
105
|
+
return Qnil;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return scheduler;
|
|
109
|
+
#endif
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static VALUE curb_fiber_scheduler_kernel_sleep(VALUE scheduler, VALUE timeout) {
|
|
113
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_KERNEL_SLEEP)
|
|
114
|
+
return rb_fiber_scheduler_kernel_sleep(scheduler, timeout);
|
|
115
|
+
#else
|
|
116
|
+
return rb_funcall(scheduler, id_kernel_sleep, 1, timeout);
|
|
117
|
+
#endif
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static VALUE curb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout) {
|
|
121
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT)
|
|
122
|
+
return rb_fiber_scheduler_io_wait(scheduler, io, events, timeout);
|
|
123
|
+
#else
|
|
124
|
+
return rb_funcall(scheduler, id_io_wait, 3, io, events, timeout);
|
|
125
|
+
#endif
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
* Sleep for the given interval without stalling sibling fibers: when the
|
|
130
|
+
* current fiber runs under a fiber scheduler, wait via the scheduler's
|
|
131
|
+
* kernel_sleep hook. rb_thread_fd_select(0, ...) and rb_thread_wait_for are
|
|
132
|
+
* thread-level waits that never consult the scheduler, so they are only used
|
|
133
|
+
* when no scheduler is active.
|
|
134
|
+
*/
|
|
135
|
+
static void curb_multi_scheduler_sleep(struct timeval *tv) {
|
|
136
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
137
|
+
if (scheduler != Qnil) {
|
|
138
|
+
curb_fiber_scheduler_kernel_sleep(scheduler, rb_float_new((double)tv->tv_sec + ((double)tv->tv_usec / 1e6)));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
142
|
+
{
|
|
143
|
+
struct timeval tv_sleep = *tv;
|
|
144
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
145
|
+
}
|
|
146
|
+
#else
|
|
147
|
+
rb_thread_wait_for(*tv);
|
|
148
|
+
#endif
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Yield a nonblocking fiber without changing legacy behavior when no
|
|
152
|
+
* scheduler is active. */
|
|
153
|
+
static void curb_multi_scheduler_yield(void) {
|
|
154
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
155
|
+
if (scheduler != Qnil) {
|
|
156
|
+
curb_fiber_scheduler_kernel_sleep(scheduler, INT2FIX(0));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
73
160
|
extern VALUE mCurl;
|
|
74
161
|
static VALUE idCall;
|
|
75
162
|
static ID id_deferred_exception_ivar;
|
|
76
163
|
static ID id_deferred_exception_source_id_ivar;
|
|
164
|
+
static ID id_native_safety_signatures_ivar;
|
|
77
165
|
static ID id_socket_io_cache_ivar;
|
|
78
166
|
|
|
79
167
|
#ifdef RDOC_NEVER_DEFINED
|
|
@@ -286,6 +374,33 @@ void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
|
|
286
374
|
st_delete(rbcm->attached, &key, NULL);
|
|
287
375
|
}
|
|
288
376
|
|
|
377
|
+
CURLMcode rb_curl_multi_detach_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
|
378
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
|
379
|
+
st_data_t key;
|
|
380
|
+
|
|
381
|
+
if (!rbcm || !rbce || !rbcm->attached) {
|
|
382
|
+
return CURLM_OK;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
key = (st_data_t)rbce;
|
|
386
|
+
if (!st_delete(rbcm->attached, &key, NULL)) {
|
|
387
|
+
return CURLM_OK;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (rbcm->handle && rbce->curl) {
|
|
391
|
+
CURLMcode result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
392
|
+
if (result != CURLM_OK) {
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (rbcm->active > 0) {
|
|
398
|
+
rbcm->active--;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return CURLM_OK;
|
|
402
|
+
}
|
|
403
|
+
|
|
289
404
|
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm) {
|
|
290
405
|
if (!rbcm || !rbcm->attached) {
|
|
291
406
|
return;
|
|
@@ -314,6 +429,8 @@ static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce) {
|
|
|
314
429
|
|
|
315
430
|
static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
|
|
316
431
|
VALUE requests;
|
|
432
|
+
VALUE object_id;
|
|
433
|
+
VALUE safety_signatures;
|
|
317
434
|
|
|
318
435
|
if (NIL_P(self) || NIL_P(easy)) {
|
|
319
436
|
return;
|
|
@@ -324,7 +441,17 @@ static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
|
|
|
324
441
|
return;
|
|
325
442
|
}
|
|
326
443
|
|
|
327
|
-
|
|
444
|
+
object_id = rb_obj_id(easy);
|
|
445
|
+
rb_hash_delete(requests, object_id);
|
|
446
|
+
|
|
447
|
+
if (!rb_ivar_defined(self, id_native_safety_signatures_ivar)) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
safety_signatures = rb_ivar_get(self, id_native_safety_signatures_ivar);
|
|
452
|
+
if (RB_TYPE_P(safety_signatures, T_HASH)) {
|
|
453
|
+
rb_hash_delete(safety_signatures, object_id);
|
|
454
|
+
}
|
|
328
455
|
}
|
|
329
456
|
|
|
330
457
|
/* TypedData-compatible free function */
|
|
@@ -1132,30 +1259,6 @@ static const char *cselect_flags_str(int flags, char *buf, size_t n) {
|
|
|
1132
1259
|
#define cselect_flags_str(...) ""
|
|
1133
1260
|
#endif
|
|
1134
1261
|
|
|
1135
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1136
|
-
/* Protected call to rb_fiber_scheduler_io_wait to avoid unwinding into C on TypeError. */
|
|
1137
|
-
struct fiber_io_wait_args { VALUE scheduler; VALUE io; VALUE events; VALUE timeout; };
|
|
1138
|
-
static VALUE fiber_io_wait_protected(VALUE argp) {
|
|
1139
|
-
struct fiber_io_wait_args *a = (struct fiber_io_wait_args *)argp;
|
|
1140
|
-
return rb_fiber_scheduler_io_wait(a->scheduler, a->io, a->events, a->timeout);
|
|
1141
|
-
}
|
|
1142
|
-
#endif
|
|
1143
|
-
|
|
1144
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1145
|
-
struct fiber_io_select_args {
|
|
1146
|
-
VALUE scheduler;
|
|
1147
|
-
VALUE readables;
|
|
1148
|
-
VALUE writables;
|
|
1149
|
-
VALUE exceptables;
|
|
1150
|
-
VALUE timeout;
|
|
1151
|
-
};
|
|
1152
|
-
|
|
1153
|
-
static VALUE fiber_io_select_protected(VALUE argp) {
|
|
1154
|
-
struct fiber_io_select_args *a = (struct fiber_io_select_args *)argp;
|
|
1155
|
-
return rb_fiber_scheduler_io_select(a->scheduler, a->readables, a->writables, a->exceptables, a->timeout);
|
|
1156
|
-
}
|
|
1157
|
-
#endif
|
|
1158
|
-
|
|
1159
1262
|
#if defined(RB_INTEGER_TYPE_P)
|
|
1160
1263
|
#define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value)
|
|
1161
1264
|
#else
|
|
@@ -1347,6 +1450,7 @@ struct build_io_select_arrays_args {
|
|
|
1347
1450
|
VALUE writables;
|
|
1348
1451
|
VALUE exceptables;
|
|
1349
1452
|
int failed;
|
|
1453
|
+
int state;
|
|
1350
1454
|
};
|
|
1351
1455
|
|
|
1352
1456
|
static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
@@ -1365,12 +1469,7 @@ static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp
|
|
|
1365
1469
|
io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1366
1470
|
if (io_state || NIL_P(io)) {
|
|
1367
1471
|
if (io_state) {
|
|
1368
|
-
|
|
1369
|
-
VALUE err = rb_errinfo();
|
|
1370
|
-
VALUE msg = rb_obj_as_string(err);
|
|
1371
|
-
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1372
|
-
#endif
|
|
1373
|
-
rb_set_errinfo(Qnil);
|
|
1472
|
+
a->state = io_state;
|
|
1374
1473
|
}
|
|
1375
1474
|
a->failed = 1;
|
|
1376
1475
|
return ST_STOP;
|
|
@@ -1415,23 +1514,31 @@ static int collect_io_select_ready_i(st_data_t key, st_data_t val, st_data_t arg
|
|
|
1415
1514
|
#endif
|
|
1416
1515
|
|
|
1417
1516
|
static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) {
|
|
1418
|
-
|
|
1419
|
-
CURLMcode mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1420
|
-
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1421
|
-
curb_debugf("[curb.socket] drive: initial socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1422
|
-
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1423
|
-
rb_curl_multi_yield_if_given(self, block);
|
|
1517
|
+
CURLMcode mrc;
|
|
1424
1518
|
|
|
1425
|
-
|
|
1519
|
+
do {
|
|
1520
|
+
/* Prime the state: let libcurl act on timeouts to set up sockets. */
|
|
1521
|
+
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1522
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1523
|
+
curb_debugf("[curb.socket] drive: initial socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1524
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1525
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1526
|
+
|
|
1527
|
+
while (rbcm->running) {
|
|
1426
1528
|
struct timeval tv = {0, 0};
|
|
1427
1529
|
long wait_ms = cCurlMutiDefaulttimeout;
|
|
1428
1530
|
|
|
1429
1531
|
if (multi_socket_timer_due(ctx)) {
|
|
1532
|
+
ctx->timeout_deadline_ms = -1;
|
|
1430
1533
|
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1431
1534
|
curb_debugf("[curb.socket] socket_action timeout(due) -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1432
1535
|
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1433
1536
|
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1434
1537
|
rb_curl_multi_yield_if_given(self, block);
|
|
1538
|
+
if (rbcm->running && multi_socket_timer_due(ctx)) {
|
|
1539
|
+
/* A repeatedly rearmed 0ms timer must still let sibling fibers run. */
|
|
1540
|
+
curb_multi_scheduler_yield();
|
|
1541
|
+
}
|
|
1435
1542
|
continue;
|
|
1436
1543
|
}
|
|
1437
1544
|
|
|
@@ -1469,26 +1576,24 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1469
1576
|
if (count_tracked > 1) {
|
|
1470
1577
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1471
1578
|
{
|
|
1472
|
-
VALUE scheduler =
|
|
1579
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1473
1580
|
if (scheduler != Qnil) {
|
|
1474
1581
|
VALUE readables = rb_ary_new();
|
|
1475
1582
|
VALUE writables = rb_ary_new();
|
|
1476
1583
|
VALUE exceptables = rb_ary_new();
|
|
1477
|
-
struct build_io_select_arrays_args build_args = { ctx, readables, writables, exceptables, 0 };
|
|
1584
|
+
struct build_io_select_arrays_args build_args = { ctx, readables, writables, exceptables, 0, 0 };
|
|
1478
1585
|
st_foreach(ctx->sock_map, build_io_select_arrays_i, (st_data_t)&build_args);
|
|
1586
|
+
if (build_args.state) rb_jump_tag(build_args.state);
|
|
1479
1587
|
if (!build_args.failed) {
|
|
1480
1588
|
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1481
1589
|
VALUE timeout = rb_float_new(timeout_s);
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
curb_debugf("[curb.socket] scheduler io_select failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1490
|
-
#endif
|
|
1491
|
-
rb_set_errinfo(Qnil);
|
|
1590
|
+
VALUE ready = rb_fiber_scheduler_io_select(scheduler, readables, writables, exceptables, timeout);
|
|
1591
|
+
if (UNDEF_P(ready)) {
|
|
1592
|
+
/* The scheduler does not implement the optional io_select hook:
|
|
1593
|
+
* rb_fiber_scheduler_io_select returns Qundef immediately.
|
|
1594
|
+
* Leave handled_wait unset so the io_wait fallback below can
|
|
1595
|
+
* perform a cooperative wait. */
|
|
1596
|
+
curb_debugf("[curb.socket] scheduler io_select not implemented; falling back to io_wait");
|
|
1492
1597
|
} else {
|
|
1493
1598
|
handled_wait = 1;
|
|
1494
1599
|
any_ready = RB_TYPE_P(ready, T_ARRAY);
|
|
@@ -1527,8 +1632,36 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1527
1632
|
}
|
|
1528
1633
|
}
|
|
1529
1634
|
#endif
|
|
1635
|
+
/* Ruby versions without the public io_select C API, and newer
|
|
1636
|
+
* schedulers that omit the optional hook, wait cooperatively on one
|
|
1637
|
+
* representative descriptor, then use a zero-time select below to
|
|
1638
|
+
* collect readiness across the whole set. */
|
|
1639
|
+
if (!handled_wait && wait_fd >= 0) {
|
|
1640
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1641
|
+
if (scheduler != Qnil) {
|
|
1642
|
+
if (!multi_socket_fd_valid_p(wait_fd)) {
|
|
1643
|
+
multi_socket_forget_fd(ctx, wait_fd);
|
|
1644
|
+
did_timeout = 1;
|
|
1645
|
+
handled_wait = 1;
|
|
1646
|
+
} else {
|
|
1647
|
+
struct io_for_fd_args io_args = { ctx, wait_fd, wait_what };
|
|
1648
|
+
int io_state = 0;
|
|
1649
|
+
VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1650
|
+
if (io_state) rb_jump_tag(io_state);
|
|
1651
|
+
if (!NIL_P(io)) {
|
|
1652
|
+
int events = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
1653
|
+
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1654
|
+
curb_fiber_scheduler_io_wait(scheduler, io, INT2NUM(events), rb_float_new(timeout_s));
|
|
1655
|
+
tv.tv_sec = 0;
|
|
1656
|
+
tv.tv_usec = 0;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1530
1661
|
if (!handled_wait) {
|
|
1531
|
-
/* Multi-fd
|
|
1662
|
+
/* Multi-fd select. After the scheduler io_wait fallback above, tv is
|
|
1663
|
+
* zero and this only polls the complete descriptor set. Without a
|
|
1664
|
+
* scheduler it performs the normal GVL-releasing wait. */
|
|
1532
1665
|
rb_fdset_t rfds, wfds, efds;
|
|
1533
1666
|
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
|
1534
1667
|
int maxfd = -1;
|
|
@@ -1567,9 +1700,8 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1567
1700
|
handled_wait = 1;
|
|
1568
1701
|
}
|
|
1569
1702
|
} else if (count_tracked == 1) {
|
|
1570
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1571
1703
|
{
|
|
1572
|
-
VALUE scheduler =
|
|
1704
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1573
1705
|
if (scheduler != Qnil) {
|
|
1574
1706
|
int scheduler_wait_handled = 0;
|
|
1575
1707
|
int events = 0;
|
|
@@ -1579,11 +1711,9 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1579
1711
|
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1580
1712
|
VALUE timeout = rb_float_new(timeout_s);
|
|
1581
1713
|
if (wait_fd < 0) {
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
rb_thread_wait_for(tv);
|
|
1586
|
-
#endif
|
|
1714
|
+
/* No pollable fd yet: sleep via the scheduler's kernel_sleep so
|
|
1715
|
+
* sibling fibers keep running while libcurl gets ready. */
|
|
1716
|
+
curb_multi_scheduler_sleep(&tv);
|
|
1587
1717
|
did_timeout = multi_socket_timer_due(ctx);
|
|
1588
1718
|
scheduler_wait_handled = 1;
|
|
1589
1719
|
} else if (!multi_socket_fd_valid_p(wait_fd)) {
|
|
@@ -1594,44 +1724,25 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1594
1724
|
struct io_for_fd_args io_args = { ctx, wait_fd, wait_what };
|
|
1595
1725
|
int io_state = 0;
|
|
1596
1726
|
VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1597
|
-
if (io_state
|
|
1598
|
-
|
|
1599
|
-
#if CURB_SOCKET_DEBUG
|
|
1600
|
-
VALUE err = rb_errinfo();
|
|
1601
|
-
VALUE msg = rb_obj_as_string(err);
|
|
1602
|
-
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1603
|
-
#endif
|
|
1604
|
-
rb_set_errinfo(Qnil);
|
|
1605
|
-
}
|
|
1727
|
+
if (io_state) rb_jump_tag(io_state);
|
|
1728
|
+
if (NIL_P(io)) {
|
|
1606
1729
|
any_ready = 0;
|
|
1607
1730
|
} else {
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
scheduler_wait_handled = 1;
|
|
1621
|
-
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1622
|
-
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1623
|
-
if (any_ready) {
|
|
1624
|
-
if (ready == Qtrue) {
|
|
1625
|
-
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1626
|
-
} else if (CURB_INTEGER_P(ready)) {
|
|
1627
|
-
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1628
|
-
if (ready_flags == 0) {
|
|
1629
|
-
any_ready = 0;
|
|
1630
|
-
did_timeout = multi_socket_timer_due(ctx);
|
|
1631
|
-
}
|
|
1632
|
-
} else {
|
|
1633
|
-
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1731
|
+
VALUE ready = curb_fiber_scheduler_io_wait(scheduler, io, INT2NUM(events), timeout);
|
|
1732
|
+
scheduler_wait_handled = 1;
|
|
1733
|
+
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1734
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1735
|
+
if (any_ready) {
|
|
1736
|
+
if (ready == Qtrue) {
|
|
1737
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1738
|
+
} else if (CURB_INTEGER_P(ready)) {
|
|
1739
|
+
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1740
|
+
if (ready_flags == 0) {
|
|
1741
|
+
any_ready = 0;
|
|
1742
|
+
did_timeout = multi_socket_timer_due(ctx);
|
|
1634
1743
|
}
|
|
1744
|
+
} else {
|
|
1745
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1635
1746
|
}
|
|
1636
1747
|
}
|
|
1637
1748
|
}
|
|
@@ -1639,7 +1750,6 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1639
1750
|
if (scheduler_wait_handled) handled_wait = 1;
|
|
1640
1751
|
}
|
|
1641
1752
|
}
|
|
1642
|
-
#endif
|
|
1643
1753
|
#if defined(HAVE_RB_WAIT_FOR_SINGLE_FD)
|
|
1644
1754
|
if (!handled_wait && wait_fd >= 0) {
|
|
1645
1755
|
int ev = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
@@ -1683,15 +1793,18 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1683
1793
|
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1684
1794
|
}
|
|
1685
1795
|
} else { /* count_tracked == 0 */
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1796
|
+
/* No sockets exposed yet (e.g. libcurl's threaded resolver doing DNS):
|
|
1797
|
+
* sleep via the scheduler's kernel_sleep when one is active so sibling
|
|
1798
|
+
* fibers keep running, otherwise a plain thread wait. */
|
|
1799
|
+
curb_multi_scheduler_sleep(&tv);
|
|
1800
|
+
/* libcurl can report active work without a socket callback or deadline;
|
|
1801
|
+
* drive the timeout socket after the sleep so the state machine does
|
|
1802
|
+
* not stall indefinitely. */
|
|
1803
|
+
did_timeout = 1;
|
|
1692
1804
|
}
|
|
1693
1805
|
|
|
1694
1806
|
if (did_timeout) {
|
|
1807
|
+
ctx->timeout_deadline_ms = -1;
|
|
1695
1808
|
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1696
1809
|
curb_debugf("[curb.socket] socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1697
1810
|
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
@@ -1714,7 +1827,13 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1714
1827
|
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1715
1828
|
curb_debugf("[curb.socket] processed completions; running=%d", rbcm->running);
|
|
1716
1829
|
rb_curl_multi_yield_if_given(self, block);
|
|
1717
|
-
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/* Match the legacy perform contract: the block gets a final idle yield,
|
|
1833
|
+
* and work queued from that yield is driven before perform returns. */
|
|
1834
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1835
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1836
|
+
} while (rbcm->running);
|
|
1718
1837
|
}
|
|
1719
1838
|
|
|
1720
1839
|
struct socket_drive_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; VALUE block; };
|
|
@@ -1777,8 +1896,6 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1777
1896
|
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
|
1778
1897
|
|
|
1779
1898
|
/* finalize */
|
|
1780
|
-
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1781
|
-
rb_curl_multi_yield_if_given(self, block);
|
|
1782
1899
|
if (cCurlMutiAutoClose == 1) {
|
|
1783
1900
|
rbcm->allow_close_during_perform = 1;
|
|
1784
1901
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
@@ -1914,11 +2031,9 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1914
2031
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
1915
2032
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
1916
2033
|
rb_curl_multi_yield_if_given(self, block);
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
}
|
|
1921
|
-
#endif
|
|
2034
|
+
/* Yield the fiber so repeated immediate curl timeouts cannot starve
|
|
2035
|
+
* sibling work on this thread. */
|
|
2036
|
+
curb_multi_scheduler_yield();
|
|
1922
2037
|
continue;
|
|
1923
2038
|
}
|
|
1924
2039
|
|
|
@@ -1934,11 +2049,11 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1934
2049
|
wait_args.timeout_ms = timeout_milliseconds;
|
|
1935
2050
|
wait_args.numfds = 0;
|
|
1936
2051
|
/*
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
2052
|
+
* Wait via curl_multi_wait with the GVL released so other Ruby
|
|
2053
|
+
* threads can continue to run. Like rb_thread_fd_select, this wait
|
|
2054
|
+
* does not consult the fiber scheduler; scheduler-aware waiting is
|
|
2055
|
+
* provided by the socket-action loop, which perform routes to when a
|
|
2056
|
+
* scheduler is active (see ruby_curl_multi_perform).
|
|
1942
2057
|
*/
|
|
1943
2058
|
CURLMcode wait_rc;
|
|
1944
2059
|
#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
@@ -1952,13 +2067,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1952
2067
|
raise_curl_multi_error_exception(wait_rc);
|
|
1953
2068
|
}
|
|
1954
2069
|
if (wait_args.numfds == 0) {
|
|
1955
|
-
|
|
1956
|
-
struct timeval tv_sleep = tv_100ms;
|
|
1957
|
-
/* Sleep in a scheduler-aware way. */
|
|
1958
|
-
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
1959
|
-
#else
|
|
1960
|
-
rb_thread_wait_for(tv_100ms);
|
|
1961
|
-
#endif
|
|
2070
|
+
curb_multi_scheduler_sleep(&tv_100ms);
|
|
1962
2071
|
}
|
|
1963
2072
|
/* Process pending transfers after waiting */
|
|
1964
2073
|
rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
|
|
@@ -1982,12 +2091,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1982
2091
|
|
|
1983
2092
|
if (maxfd == -1) {
|
|
1984
2093
|
/* libcurl recommends sleeping for 100ms */
|
|
1985
|
-
|
|
1986
|
-
struct timeval tv_sleep = tv_100ms;
|
|
1987
|
-
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
1988
|
-
#else
|
|
1989
|
-
rb_thread_wait_for(tv_100ms);
|
|
1990
|
-
#endif
|
|
2094
|
+
curb_multi_scheduler_sleep(&tv_100ms);
|
|
1991
2095
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
1992
2096
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
1993
2097
|
rb_curl_multi_yield_if_given(self, block);
|
|
@@ -2010,7 +2114,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2010
2114
|
#endif
|
|
2011
2115
|
|
|
2012
2116
|
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
2013
|
-
/*
|
|
2117
|
+
/* Wait with the GVL released. Note rb_thread_fd_select does not consult
|
|
2118
|
+
* the fiber scheduler — under a scheduler, perform routes to the
|
|
2119
|
+
* socket-action loop instead (see ruby_curl_multi_perform). Build
|
|
2120
|
+
* rb_fdset_t sets. */
|
|
2014
2121
|
{
|
|
2015
2122
|
rb_fdset_t rfds, wfds, efds;
|
|
2016
2123
|
rb_fd_init(&rfds);
|
|
@@ -2122,6 +2229,18 @@ static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE sel
|
|
|
2122
2229
|
}
|
|
2123
2230
|
|
|
2124
2231
|
VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
2232
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2233
|
+
/*
|
|
2234
|
+
* The legacy fdset loop waits with rb_thread_fd_select, which releases the
|
|
2235
|
+
* GVL but never consults the fiber scheduler, stalling every other fiber on
|
|
2236
|
+
* this thread for the duration of the transfers. When the calling fiber
|
|
2237
|
+
* runs under a fiber scheduler, use the socket-action drive loop, which
|
|
2238
|
+
* waits through the scheduler's io_wait/io_select/kernel_sleep hooks.
|
|
2239
|
+
*/
|
|
2240
|
+
if (curb_fiber_scheduler_current() != Qnil) {
|
|
2241
|
+
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
|
|
2242
|
+
}
|
|
2243
|
+
#endif
|
|
2125
2244
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
|
|
2126
2245
|
}
|
|
2127
2246
|
|
|
@@ -2188,8 +2307,15 @@ static void curl_multi_mark(void *ptr) {
|
|
|
2188
2307
|
/* =================== INIT LIB =====================*/
|
|
2189
2308
|
void init_curb_multi() {
|
|
2190
2309
|
idCall = rb_intern("call");
|
|
2310
|
+
cRubyFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
|
2311
|
+
rb_global_variable(&cRubyFiber);
|
|
2312
|
+
id_fiber_blocking_p = rb_intern("blocking?");
|
|
2313
|
+
id_fiber_scheduler = rb_intern("scheduler");
|
|
2314
|
+
id_kernel_sleep = rb_intern("kernel_sleep");
|
|
2315
|
+
id_io_wait = rb_intern("io_wait");
|
|
2191
2316
|
id_deferred_exception_ivar = rb_intern("@__curb_deferred_exception");
|
|
2192
2317
|
id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id");
|
|
2318
|
+
id_native_safety_signatures_ivar = rb_intern("@__curb_native_safety_signatures");
|
|
2193
2319
|
id_socket_io_cache_ivar = rb_intern("@__curb_socket_io_cache");
|
|
2194
2320
|
cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
|
|
2195
2321
|
|
|
@@ -2208,9 +2334,10 @@ void init_curb_multi() {
|
|
|
2208
2334
|
rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
|
|
2209
2335
|
rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
|
|
2210
2336
|
/*
|
|
2211
|
-
*
|
|
2212
|
-
*
|
|
2213
|
-
*
|
|
2337
|
+
* perform uses the legacy fdset loop by default, but routes to the
|
|
2338
|
+
* socket-action drive loop when the calling fiber runs under a fiber
|
|
2339
|
+
* scheduler so sibling fibers keep running during transfers. The
|
|
2340
|
+
* socket-action path is also exposed directly as _socket_perform.
|
|
2214
2341
|
*/
|
|
2215
2342
|
rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
|
|
2216
2343
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|