curb 1.3.4 → 1.3.6
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 +57 -0
- data/Rakefile +24 -2
- data/doc.rb +48 -8
- data/ext/curb.c +24 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +1378 -55
- 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 +330 -74
- data/ext/curb_multi.h +1 -0
- data/ext/extconf.rb +9 -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/tc_curl_download.rb +86 -0
- data/tests/tc_curl_easy.rb +76 -0
- data/tests/tc_curl_maxfilesize.rb +201 -1
- data/tests/tc_curl_multi.rb +293 -0
- data/tests/tc_curl_network_policy.rb +1475 -0
- data/tests/tc_curl_protocols.rb +351 -0
- data/tests/tc_fiber_scheduler.rb +64 -5
- metadata +8 -3
data/ext/curb_multi.c
CHANGED
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
|
|
29
29
|
#include <errno.h>
|
|
30
30
|
#include <fcntl.h>
|
|
31
|
+
#ifndef _WIN32
|
|
32
|
+
#include <sys/time.h>
|
|
33
|
+
#include <time.h>
|
|
34
|
+
#endif
|
|
31
35
|
#include <stdint.h>
|
|
32
36
|
#include <stdarg.h>
|
|
33
37
|
|
|
@@ -70,6 +74,8 @@ extern VALUE mCurl;
|
|
|
70
74
|
static VALUE idCall;
|
|
71
75
|
static ID id_deferred_exception_ivar;
|
|
72
76
|
static ID id_deferred_exception_source_id_ivar;
|
|
77
|
+
static ID id_native_safety_signatures_ivar;
|
|
78
|
+
static ID id_socket_io_cache_ivar;
|
|
73
79
|
|
|
74
80
|
#ifdef RDOC_NEVER_DEFINED
|
|
75
81
|
mCurl = rb_define_module("Curl");
|
|
@@ -281,6 +287,33 @@ void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
|
|
281
287
|
st_delete(rbcm->attached, &key, NULL);
|
|
282
288
|
}
|
|
283
289
|
|
|
290
|
+
CURLMcode rb_curl_multi_detach_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
|
291
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
|
292
|
+
st_data_t key;
|
|
293
|
+
|
|
294
|
+
if (!rbcm || !rbce || !rbcm->attached) {
|
|
295
|
+
return CURLM_OK;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
key = (st_data_t)rbce;
|
|
299
|
+
if (!st_delete(rbcm->attached, &key, NULL)) {
|
|
300
|
+
return CURLM_OK;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (rbcm->handle && rbce->curl) {
|
|
304
|
+
CURLMcode result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
305
|
+
if (result != CURLM_OK) {
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (rbcm->active > 0) {
|
|
311
|
+
rbcm->active--;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return CURLM_OK;
|
|
315
|
+
}
|
|
316
|
+
|
|
284
317
|
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm) {
|
|
285
318
|
if (!rbcm || !rbcm->attached) {
|
|
286
319
|
return;
|
|
@@ -309,6 +342,8 @@ static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce) {
|
|
|
309
342
|
|
|
310
343
|
static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
|
|
311
344
|
VALUE requests;
|
|
345
|
+
VALUE object_id;
|
|
346
|
+
VALUE safety_signatures;
|
|
312
347
|
|
|
313
348
|
if (NIL_P(self) || NIL_P(easy)) {
|
|
314
349
|
return;
|
|
@@ -319,7 +354,17 @@ static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
|
|
|
319
354
|
return;
|
|
320
355
|
}
|
|
321
356
|
|
|
322
|
-
|
|
357
|
+
object_id = rb_obj_id(easy);
|
|
358
|
+
rb_hash_delete(requests, object_id);
|
|
359
|
+
|
|
360
|
+
if (!rb_ivar_defined(self, id_native_safety_signatures_ivar)) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
safety_signatures = rb_ivar_get(self, id_native_safety_signatures_ivar);
|
|
365
|
+
if (RB_TYPE_P(safety_signatures, T_HASH)) {
|
|
366
|
+
rb_hash_delete(safety_signatures, object_id);
|
|
367
|
+
}
|
|
323
368
|
}
|
|
324
369
|
|
|
325
370
|
/* TypedData-compatible free function */
|
|
@@ -1070,10 +1115,28 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
|
1070
1115
|
/* ---- socket-action implementation (scheduler-friendly) ---- */
|
|
1071
1116
|
typedef struct {
|
|
1072
1117
|
st_table *sock_map; /* key: int fd, value: int 'what' (CURL_POLL_*) */
|
|
1073
|
-
long
|
|
1118
|
+
long long timeout_deadline_ms; /* absolute deadline for CURL_SOCKET_TIMEOUT */
|
|
1074
1119
|
VALUE io_cache; /* fd -> IO wrapper for fiber-scheduler waits */
|
|
1075
1120
|
} multi_socket_ctx;
|
|
1076
1121
|
|
|
1122
|
+
static long long multi_socket_current_time_ms(void) {
|
|
1123
|
+
#if defined(CLOCK_MONOTONIC)
|
|
1124
|
+
struct timespec ts;
|
|
1125
|
+
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
|
1126
|
+
return ((long long)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
|
|
1127
|
+
}
|
|
1128
|
+
#endif
|
|
1129
|
+
|
|
1130
|
+
struct timeval tv;
|
|
1131
|
+
gettimeofday(&tv, NULL);
|
|
1132
|
+
return ((long long)tv.tv_sec * 1000) + (tv.tv_usec / 1000);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
static int multi_socket_timer_due(multi_socket_ctx *ctx) {
|
|
1136
|
+
return ctx && ctx->timeout_deadline_ms >= 0 &&
|
|
1137
|
+
multi_socket_current_time_ms() >= ctx->timeout_deadline_ms;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1077
1140
|
#if CURB_SOCKET_DEBUG
|
|
1078
1141
|
static void curb_debugf(const char *fmt, ...) {
|
|
1079
1142
|
va_list ap;
|
|
@@ -1118,6 +1181,21 @@ static VALUE fiber_io_wait_protected(VALUE argp) {
|
|
|
1118
1181
|
}
|
|
1119
1182
|
#endif
|
|
1120
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
|
+
|
|
1121
1199
|
#if defined(RB_INTEGER_TYPE_P)
|
|
1122
1200
|
#define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value)
|
|
1123
1201
|
#else
|
|
@@ -1187,6 +1265,11 @@ static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, v
|
|
|
1187
1265
|
#endif
|
|
1188
1266
|
} else {
|
|
1189
1267
|
/* store current interest mask for this fd */
|
|
1268
|
+
st_data_t key = (st_data_t)fd;
|
|
1269
|
+
st_data_t old_what;
|
|
1270
|
+
if (st_lookup(ctx->sock_map, key, &old_what) && (int)old_what != what && !NIL_P(ctx->io_cache)) {
|
|
1271
|
+
rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1272
|
+
}
|
|
1190
1273
|
st_insert(ctx->sock_map, (st_data_t)fd, (st_data_t)what);
|
|
1191
1274
|
#if CURB_SOCKET_DEBUG
|
|
1192
1275
|
{
|
|
@@ -1201,7 +1284,9 @@ static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, v
|
|
|
1201
1284
|
static int multi_timer_cb(CURLM *multi, long timeout_ms, void *userp) {
|
|
1202
1285
|
(void)multi;
|
|
1203
1286
|
multi_socket_ctx *ctx = (multi_socket_ctx *)userp;
|
|
1204
|
-
if (ctx)
|
|
1287
|
+
if (ctx) {
|
|
1288
|
+
ctx->timeout_deadline_ms = timeout_ms < 0 ? -1 : multi_socket_current_time_ms() + timeout_ms;
|
|
1289
|
+
}
|
|
1205
1290
|
curb_debugf("[curb.socket] timer_cb timeout_ms=%ld", timeout_ms);
|
|
1206
1291
|
return 0;
|
|
1207
1292
|
}
|
|
@@ -1272,23 +1357,103 @@ static int st_count_i(st_data_t k, st_data_t v, st_data_t argp) {
|
|
|
1272
1357
|
return ST_CONTINUE;
|
|
1273
1358
|
}
|
|
1274
1359
|
|
|
1275
|
-
static
|
|
1360
|
+
static const char *multi_socket_io_mode_for_curl_poll(int what) {
|
|
1361
|
+
if (what == CURL_POLL_IN) return "r";
|
|
1362
|
+
if (what == CURL_POLL_OUT) return "w";
|
|
1363
|
+
return "r+";
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
static VALUE multi_socket_io_for_fd(multi_socket_ctx *ctx, int fd, int what) {
|
|
1276
1367
|
VALUE key = INT2NUM(fd);
|
|
1277
1368
|
VALUE io = rb_hash_aref(ctx->io_cache, key);
|
|
1278
1369
|
if (NIL_P(io)) {
|
|
1279
|
-
io = rb_funcall(rb_cIO, rb_intern("for_fd"), 2, key, rb_str_new_cstr(
|
|
1370
|
+
io = rb_funcall(rb_cIO, rb_intern("for_fd"), 2, key, rb_str_new_cstr(multi_socket_io_mode_for_curl_poll(what)));
|
|
1280
1371
|
rb_funcall(io, rb_intern("autoclose="), 1, Qfalse);
|
|
1281
1372
|
rb_hash_aset(ctx->io_cache, key, io);
|
|
1282
1373
|
}
|
|
1283
1374
|
return io;
|
|
1284
1375
|
}
|
|
1285
1376
|
|
|
1286
|
-
struct io_for_fd_args { multi_socket_ctx *ctx; int fd; };
|
|
1377
|
+
struct io_for_fd_args { multi_socket_ctx *ctx; int fd; int what; };
|
|
1287
1378
|
static VALUE multi_socket_io_for_fd_protected(VALUE argp) {
|
|
1288
1379
|
struct io_for_fd_args *a = (struct io_for_fd_args *)argp;
|
|
1289
|
-
return multi_socket_io_for_fd(a->ctx, a->fd);
|
|
1380
|
+
return multi_socket_io_for_fd(a->ctx, a->fd, a->what);
|
|
1290
1381
|
}
|
|
1291
1382
|
|
|
1383
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1384
|
+
struct build_io_select_arrays_args {
|
|
1385
|
+
multi_socket_ctx *ctx;
|
|
1386
|
+
VALUE readables;
|
|
1387
|
+
VALUE writables;
|
|
1388
|
+
VALUE exceptables;
|
|
1389
|
+
int failed;
|
|
1390
|
+
};
|
|
1391
|
+
|
|
1392
|
+
static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
1393
|
+
struct build_io_select_arrays_args *a = (struct build_io_select_arrays_args *)argp;
|
|
1394
|
+
int fd = (int)key;
|
|
1395
|
+
int what = (int)val;
|
|
1396
|
+
struct io_for_fd_args io_args = { a->ctx, fd, what };
|
|
1397
|
+
int io_state = 0;
|
|
1398
|
+
VALUE io;
|
|
1399
|
+
|
|
1400
|
+
if (!multi_socket_fd_valid_p(fd)) {
|
|
1401
|
+
a->failed = 1;
|
|
1402
|
+
return ST_STOP;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1406
|
+
if (io_state || NIL_P(io)) {
|
|
1407
|
+
if (io_state) {
|
|
1408
|
+
#if CURB_SOCKET_DEBUG
|
|
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);
|
|
1414
|
+
}
|
|
1415
|
+
a->failed = 1;
|
|
1416
|
+
return ST_STOP;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) rb_ary_push(a->readables, io);
|
|
1420
|
+
if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) rb_ary_push(a->writables, io);
|
|
1421
|
+
rb_ary_push(a->exceptables, io);
|
|
1422
|
+
|
|
1423
|
+
return ST_CONTINUE;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
struct collect_io_select_ready_args {
|
|
1427
|
+
multi_socket_ctx *ctx;
|
|
1428
|
+
VALUE readables;
|
|
1429
|
+
VALUE writables;
|
|
1430
|
+
VALUE exceptables;
|
|
1431
|
+
struct ready_fd *fds;
|
|
1432
|
+
int capacity;
|
|
1433
|
+
int count;
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
static int collect_io_select_ready_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
1437
|
+
(void)val;
|
|
1438
|
+
struct collect_io_select_ready_args *a = (struct collect_io_select_ready_args *)argp;
|
|
1439
|
+
VALUE io = rb_hash_aref(a->ctx->io_cache, INT2NUM((int)key));
|
|
1440
|
+
int flags = 0;
|
|
1441
|
+
|
|
1442
|
+
if (NIL_P(io)) return ST_CONTINUE;
|
|
1443
|
+
if (RTEST(rb_ary_includes(a->readables, io))) flags |= CURL_CSELECT_IN;
|
|
1444
|
+
if (RTEST(rb_ary_includes(a->writables, io))) flags |= CURL_CSELECT_OUT;
|
|
1445
|
+
if (RTEST(rb_ary_includes(a->exceptables, io))) flags |= CURL_CSELECT_ERR;
|
|
1446
|
+
|
|
1447
|
+
if (flags && a->count < a->capacity) {
|
|
1448
|
+
a->fds[a->count].fd = (int)key;
|
|
1449
|
+
a->fds[a->count].flags = flags;
|
|
1450
|
+
a->count++;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
return ST_CONTINUE;
|
|
1454
|
+
}
|
|
1455
|
+
#endif
|
|
1456
|
+
|
|
1292
1457
|
static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) {
|
|
1293
1458
|
/* prime the state: let libcurl act on timeouts to setup sockets */
|
|
1294
1459
|
CURLMcode mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
@@ -1299,16 +1464,24 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1299
1464
|
|
|
1300
1465
|
while (rbcm->running) {
|
|
1301
1466
|
struct timeval tv = {0, 0};
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
if (
|
|
1309
|
-
|
|
1310
|
-
|
|
1467
|
+
long wait_ms = cCurlMutiDefaulttimeout;
|
|
1468
|
+
|
|
1469
|
+
if (multi_socket_timer_due(ctx)) {
|
|
1470
|
+
ctx->timeout_deadline_ms = -1;
|
|
1471
|
+
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1472
|
+
curb_debugf("[curb.socket] socket_action timeout(due) -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1473
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1474
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1475
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if (ctx->timeout_deadline_ms >= 0) {
|
|
1480
|
+
long long remaining_ms = ctx->timeout_deadline_ms - multi_socket_current_time_ms();
|
|
1481
|
+
if (remaining_ms < wait_ms) wait_ms = remaining_ms < 0 ? 0 : (long)remaining_ms;
|
|
1311
1482
|
}
|
|
1483
|
+
tv.tv_sec = wait_ms / 1000;
|
|
1484
|
+
tv.tv_usec = (wait_ms % 1000) * 1000;
|
|
1312
1485
|
|
|
1313
1486
|
/* Find a representative fd to wait on (if any). */
|
|
1314
1487
|
int wait_fd = -1;
|
|
@@ -1333,50 +1506,113 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1333
1506
|
int any_ready = 0;
|
|
1334
1507
|
int ready_flags = 0;
|
|
1335
1508
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1509
|
+
int handled_wait = 0;
|
|
1510
|
+
if (count_tracked > 1) {
|
|
1511
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1512
|
+
{
|
|
1513
|
+
VALUE scheduler = rb_fiber_scheduler_current();
|
|
1514
|
+
if (scheduler != Qnil) {
|
|
1515
|
+
VALUE readables = rb_ary_new();
|
|
1516
|
+
VALUE writables = rb_ary_new();
|
|
1517
|
+
VALUE exceptables = rb_ary_new();
|
|
1518
|
+
struct build_io_select_arrays_args build_args = { ctx, readables, writables, exceptables, 0 };
|
|
1519
|
+
st_foreach(ctx->sock_map, build_io_select_arrays_i, (st_data_t)&build_args);
|
|
1520
|
+
if (!build_args.failed) {
|
|
1521
|
+
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1522
|
+
VALUE timeout = rb_float_new(timeout_s);
|
|
1523
|
+
struct fiber_io_select_args select_args = { scheduler, readables, writables, exceptables, timeout };
|
|
1524
|
+
int state = 0;
|
|
1525
|
+
VALUE ready = rb_protect(fiber_io_select_protected, (VALUE)&select_args, &state);
|
|
1526
|
+
if (state) {
|
|
1527
|
+
#if CURB_SOCKET_DEBUG
|
|
1528
|
+
VALUE err = rb_errinfo();
|
|
1529
|
+
VALUE msg = rb_obj_as_string(err);
|
|
1530
|
+
curb_debugf("[curb.socket] scheduler io_select failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1531
|
+
#endif
|
|
1532
|
+
rb_set_errinfo(Qnil);
|
|
1533
|
+
} else {
|
|
1534
|
+
handled_wait = 1;
|
|
1535
|
+
any_ready = RB_TYPE_P(ready, T_ARRAY);
|
|
1536
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1537
|
+
if (any_ready) {
|
|
1538
|
+
VALUE ready_readables = rb_ary_entry(ready, 0);
|
|
1539
|
+
VALUE ready_writables = rb_ary_entry(ready, 1);
|
|
1540
|
+
VALUE ready_exceptables = rb_ary_entry(ready, 2);
|
|
1541
|
+
struct ready_fd *ready_fds = ALLOC_N(struct ready_fd, count_tracked);
|
|
1542
|
+
struct collect_io_select_ready_args d;
|
|
1543
|
+
int i;
|
|
1544
|
+
if (!RB_TYPE_P(ready_readables, T_ARRAY)) ready_readables = rb_ary_new();
|
|
1545
|
+
if (!RB_TYPE_P(ready_writables, T_ARRAY)) ready_writables = rb_ary_new();
|
|
1546
|
+
if (!RB_TYPE_P(ready_exceptables, T_ARRAY)) ready_exceptables = rb_ary_new();
|
|
1547
|
+
d.ctx = ctx;
|
|
1548
|
+
d.readables = ready_readables;
|
|
1549
|
+
d.writables = ready_writables;
|
|
1550
|
+
d.exceptables = ready_exceptables;
|
|
1551
|
+
d.fds = ready_fds;
|
|
1552
|
+
d.capacity = count_tracked;
|
|
1553
|
+
d.count = 0;
|
|
1554
|
+
st_foreach(ctx->sock_map, collect_io_select_ready_i, (st_data_t)&d);
|
|
1555
|
+
any_ready = (d.count > 0);
|
|
1556
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1557
|
+
for (i = 0; i < d.count; i++) {
|
|
1558
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)d.fds[i].fd, d.fds[i].flags, &rbcm->running);
|
|
1559
|
+
if (mrc != CURLM_OK) {
|
|
1560
|
+
xfree(ready_fds);
|
|
1561
|
+
raise_curl_multi_error_exception(mrc);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
xfree(ready_fds);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
#endif
|
|
1571
|
+
if (!handled_wait) {
|
|
1572
|
+
/* Multi-fd wait using scheduler-aware rb_thread_fd_select. */
|
|
1573
|
+
rb_fdset_t rfds, wfds, efds;
|
|
1574
|
+
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
|
1575
|
+
int maxfd = -1;
|
|
1576
|
+
rb_fdset_from_sockmap(ctx->sock_map, &rfds, &wfds, &efds, &maxfd);
|
|
1577
|
+
int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv);
|
|
1578
|
+
curb_debugf("[curb.socket] rb_thread_fd_select(multi) rc=%d maxfd=%d", rc, maxfd);
|
|
1579
|
+
if (rc < 0) {
|
|
1580
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1581
|
+
if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
any_ready = (rc > 0);
|
|
1585
|
+
did_timeout = (rc == 0 && multi_socket_timer_due(ctx));
|
|
1586
|
+
if (any_ready) {
|
|
1587
|
+
struct ready_fd *ready_fds = ALLOC_N(struct ready_fd, count_tracked);
|
|
1588
|
+
struct collect_ready_fd_args d;
|
|
1589
|
+
int i;
|
|
1590
|
+
d.r = &rfds;
|
|
1591
|
+
d.w = &wfds;
|
|
1592
|
+
d.e = &efds;
|
|
1593
|
+
d.fds = ready_fds;
|
|
1594
|
+
d.capacity = count_tracked;
|
|
1595
|
+
d.count = 0;
|
|
1596
|
+
st_foreach(ctx->sock_map, collect_ready_fd_i, (st_data_t)&d);
|
|
1597
|
+
for (i = 0; i < d.count; i++) {
|
|
1598
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)d.fds[i].fd, d.fds[i].flags, &rbcm->running);
|
|
1599
|
+
if (mrc != CURLM_OK) {
|
|
1600
|
+
xfree(ready_fds);
|
|
1601
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1602
|
+
raise_curl_multi_error_exception(mrc);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
xfree(ready_fds);
|
|
1606
|
+
}
|
|
1607
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1608
|
+
handled_wait = 1;
|
|
1609
|
+
}
|
|
1610
|
+
} else if (count_tracked == 1) {
|
|
1376
1611
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1377
1612
|
{
|
|
1378
1613
|
VALUE scheduler = rb_fiber_scheduler_current();
|
|
1379
1614
|
if (scheduler != Qnil) {
|
|
1615
|
+
int scheduler_wait_handled = 0;
|
|
1380
1616
|
int events = 0;
|
|
1381
1617
|
if (wait_fd >= 0) {
|
|
1382
1618
|
events = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
@@ -1389,30 +1625,42 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1389
1625
|
#else
|
|
1390
1626
|
rb_thread_wait_for(tv);
|
|
1391
1627
|
#endif
|
|
1392
|
-
did_timeout =
|
|
1628
|
+
did_timeout = multi_socket_timer_due(ctx);
|
|
1629
|
+
scheduler_wait_handled = 1;
|
|
1393
1630
|
} else if (!multi_socket_fd_valid_p(wait_fd)) {
|
|
1394
1631
|
multi_socket_forget_fd(ctx, wait_fd);
|
|
1395
1632
|
did_timeout = 1;
|
|
1633
|
+
scheduler_wait_handled = 1;
|
|
1396
1634
|
} else {
|
|
1397
|
-
struct io_for_fd_args io_args = { ctx, wait_fd };
|
|
1635
|
+
struct io_for_fd_args io_args = { ctx, wait_fd, wait_what };
|
|
1398
1636
|
int io_state = 0;
|
|
1399
1637
|
VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1400
1638
|
if (io_state || NIL_P(io)) {
|
|
1401
|
-
if (io_state)
|
|
1402
|
-
|
|
1403
|
-
|
|
1639
|
+
if (io_state) {
|
|
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
|
+
}
|
|
1404
1647
|
any_ready = 0;
|
|
1405
1648
|
} else {
|
|
1406
1649
|
struct fiber_io_wait_args args = { scheduler, io, INT2NUM(events), timeout };
|
|
1407
1650
|
int state = 0;
|
|
1408
1651
|
VALUE ready = rb_protect(fiber_io_wait_protected, (VALUE)&args, &state);
|
|
1409
1652
|
if (state) {
|
|
1653
|
+
#if CURB_SOCKET_DEBUG
|
|
1654
|
+
VALUE err = rb_errinfo();
|
|
1655
|
+
VALUE msg = rb_obj_as_string(err);
|
|
1656
|
+
curb_debugf("[curb.socket] scheduler io_wait failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1657
|
+
#endif
|
|
1410
1658
|
rb_set_errinfo(Qnil);
|
|
1411
|
-
did_timeout = 1;
|
|
1412
1659
|
any_ready = 0;
|
|
1413
1660
|
} else {
|
|
1661
|
+
scheduler_wait_handled = 1;
|
|
1414
1662
|
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1415
|
-
did_timeout = !any_ready;
|
|
1663
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1416
1664
|
if (any_ready) {
|
|
1417
1665
|
if (ready == Qtrue) {
|
|
1418
1666
|
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
@@ -1420,7 +1668,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1420
1668
|
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1421
1669
|
if (ready_flags == 0) {
|
|
1422
1670
|
any_ready = 0;
|
|
1423
|
-
did_timeout =
|
|
1671
|
+
did_timeout = multi_socket_timer_due(ctx);
|
|
1424
1672
|
}
|
|
1425
1673
|
} else {
|
|
1426
1674
|
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
@@ -1429,7 +1677,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1429
1677
|
}
|
|
1430
1678
|
}
|
|
1431
1679
|
}
|
|
1432
|
-
handled_wait = 1;
|
|
1680
|
+
if (scheduler_wait_handled) handled_wait = 1;
|
|
1433
1681
|
}
|
|
1434
1682
|
}
|
|
1435
1683
|
#endif
|
|
@@ -1443,7 +1691,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1443
1691
|
continue;
|
|
1444
1692
|
}
|
|
1445
1693
|
any_ready = (rc != 0);
|
|
1446
|
-
did_timeout = (rc == 0);
|
|
1694
|
+
did_timeout = (rc == 0 && multi_socket_timer_due(ctx));
|
|
1447
1695
|
if (any_ready) ready_flags = multi_socket_cselect_flags_for_wait_events(rc);
|
|
1448
1696
|
handled_wait = 1;
|
|
1449
1697
|
}
|
|
@@ -1467,7 +1715,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1467
1715
|
continue;
|
|
1468
1716
|
}
|
|
1469
1717
|
any_ready = (rc > 0);
|
|
1470
|
-
did_timeout = (rc == 0);
|
|
1718
|
+
did_timeout = (rc == 0 && multi_socket_timer_due(ctx));
|
|
1471
1719
|
if (any_ready && wait_fd >= 0) {
|
|
1472
1720
|
if (rb_fd_isset(wait_fd, &rfds)) ready_flags |= CURL_CSELECT_IN;
|
|
1473
1721
|
if (rb_fd_isset(wait_fd, &wfds)) ready_flags |= CURL_CSELECT_OUT;
|
|
@@ -1481,10 +1729,14 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1481
1729
|
#else
|
|
1482
1730
|
rb_thread_wait_for(tv);
|
|
1483
1731
|
#endif
|
|
1732
|
+
/* libcurl can report active work without a socket callback or deadline;
|
|
1733
|
+
* drive the timeout socket after the scheduler-aware sleep so the state
|
|
1734
|
+
* machine does not stall indefinitely. */
|
|
1484
1735
|
did_timeout = 1;
|
|
1485
1736
|
}
|
|
1486
1737
|
|
|
1487
1738
|
if (did_timeout) {
|
|
1739
|
+
ctx->timeout_deadline_ms = -1;
|
|
1488
1740
|
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1489
1741
|
curb_debugf("[curb.socket] socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1490
1742
|
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
@@ -1516,7 +1768,7 @@ static VALUE ruby_curl_multi_socket_drive_body(VALUE argp) {
|
|
|
1516
1768
|
rb_curl_multi_socket_drive(a->self, a->rbcm, a->ctx, a->block);
|
|
1517
1769
|
return Qtrue;
|
|
1518
1770
|
}
|
|
1519
|
-
struct socket_cleanup_args { ruby_curl_multi *rbcm; multi_socket_ctx *ctx; };
|
|
1771
|
+
struct socket_cleanup_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; };
|
|
1520
1772
|
static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
1521
1773
|
struct socket_cleanup_args *c = (struct socket_cleanup_args *)argp;
|
|
1522
1774
|
if (c->rbcm && c->rbcm->handle) {
|
|
@@ -1532,10 +1784,12 @@ static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
|
1532
1784
|
if (c->ctx) {
|
|
1533
1785
|
if (!NIL_P(c->ctx->io_cache)) {
|
|
1534
1786
|
rb_hash_clear(c->ctx->io_cache);
|
|
1535
|
-
rb_gc_unregister_address(&c->ctx->io_cache);
|
|
1536
1787
|
}
|
|
1537
1788
|
c->ctx->io_cache = Qnil;
|
|
1538
1789
|
}
|
|
1790
|
+
if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_io_cache_ivar)) {
|
|
1791
|
+
rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_io_cache_ivar));
|
|
1792
|
+
}
|
|
1539
1793
|
return Qnil;
|
|
1540
1794
|
}
|
|
1541
1795
|
|
|
@@ -1552,9 +1806,9 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1552
1806
|
|
|
1553
1807
|
multi_socket_ctx ctx;
|
|
1554
1808
|
ctx.sock_map = st_init_numtable();
|
|
1555
|
-
ctx.
|
|
1809
|
+
ctx.timeout_deadline_ms = -1;
|
|
1556
1810
|
ctx.io_cache = rb_hash_new();
|
|
1557
|
-
|
|
1811
|
+
rb_ivar_set(self, id_socket_io_cache_ivar, ctx.io_cache);
|
|
1558
1812
|
|
|
1559
1813
|
/* install socket/timer callbacks */
|
|
1560
1814
|
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
|
|
@@ -1564,7 +1818,7 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1564
1818
|
|
|
1565
1819
|
/* run using socket action loop with ensure-cleanup */
|
|
1566
1820
|
struct socket_drive_args body_args = { self, rbcm, &ctx, block };
|
|
1567
|
-
struct socket_cleanup_args ensure_args = { rbcm, &ctx };
|
|
1821
|
+
struct socket_cleanup_args ensure_args = { self, rbcm, &ctx };
|
|
1568
1822
|
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
|
1569
1823
|
|
|
1570
1824
|
/* finalize */
|
|
@@ -1857,10 +2111,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1857
2111
|
#endif /* disabled curl_multi_wait: use fdsets */
|
|
1858
2112
|
}
|
|
1859
2113
|
|
|
2114
|
+
rb_curl_multi_read_info( self, rbcm->handle );
|
|
2115
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1860
2116
|
} while( rbcm->running );
|
|
1861
2117
|
|
|
1862
|
-
rb_curl_multi_read_info( self, rbcm->handle );
|
|
1863
|
-
rb_curl_multi_yield_if_given(self, block);
|
|
1864
2118
|
if (cCurlMutiAutoClose == 1) {
|
|
1865
2119
|
rbcm->allow_close_during_perform = 1;
|
|
1866
2120
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
@@ -1981,6 +2235,8 @@ void init_curb_multi() {
|
|
|
1981
2235
|
idCall = rb_intern("call");
|
|
1982
2236
|
id_deferred_exception_ivar = rb_intern("@__curb_deferred_exception");
|
|
1983
2237
|
id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id");
|
|
2238
|
+
id_native_safety_signatures_ivar = rb_intern("@__curb_native_safety_signatures");
|
|
2239
|
+
id_socket_io_cache_ivar = rb_intern("@__curb_socket_io_cache");
|
|
1984
2240
|
cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
|
|
1985
2241
|
|
|
1986
2242
|
rb_define_alloc_func(cCurlMulti, ruby_curl_multi_alloc);
|
data/ext/curb_multi.h
CHANGED
data/ext/extconf.rb
CHANGED
|
@@ -298,6 +298,8 @@ have_constant "curlopt_sockoptfunction"
|
|
|
298
298
|
have_constant "curlopt_sockoptdata"
|
|
299
299
|
have_constant "curlopt_opensocketfunction"
|
|
300
300
|
have_constant "curlopt_opensocketdata"
|
|
301
|
+
have_constant "curlopt_prereqfunction"
|
|
302
|
+
have_constant "curlopt_prereqdata"
|
|
301
303
|
|
|
302
304
|
# Deprecated constants (still check for them for backward compat)
|
|
303
305
|
have_constant "curlopt_ioctlfunction"
|
|
@@ -391,6 +393,7 @@ have_constant "curlopt_socks5_gssapi_nec"
|
|
|
391
393
|
have_constant "curlopt_interface"
|
|
392
394
|
have_constant "curlopt_localport"
|
|
393
395
|
have_constant "curlopt_dns_cache_timeout"
|
|
396
|
+
have_constant "curlopt_dns_servers"
|
|
394
397
|
have_constant "curlopt_dns_use_global_cache"
|
|
395
398
|
have_constant "curlopt_buffersize"
|
|
396
399
|
have_constant "curlopt_port"
|
|
@@ -531,6 +534,7 @@ have_constant "curlusessl_none"
|
|
|
531
534
|
have_constant "curlusessl_try"
|
|
532
535
|
have_constant "curlusessl_control"
|
|
533
536
|
have_constant "curlusessl_all"
|
|
537
|
+
have_constant "curlopt_connect_to"
|
|
534
538
|
have_constant "curlopt_resolve"
|
|
535
539
|
have_constant "curlopt_request_target"
|
|
536
540
|
have_constant "curlopt_sslcert"
|
|
@@ -558,6 +562,10 @@ have_constant :CURL_SSLVERSION_TLSv1_2
|
|
|
558
562
|
have_constant :CURL_SSLVERSION_TLSv1_3
|
|
559
563
|
|
|
560
564
|
have_constant "curlopt_ssl_verifypeer"
|
|
565
|
+
have_constant "curlopt_doh_url"
|
|
566
|
+
have_constant "curlopt_doh_ssl_verifypeer"
|
|
567
|
+
have_constant "curlopt_doh_ssl_verifyhost"
|
|
568
|
+
have_constant "curlopt_doh_ssl_verifystatus"
|
|
561
569
|
have_constant "curlopt_cainfo"
|
|
562
570
|
have_constant "curlopt_issuercert"
|
|
563
571
|
have_constant "curlopt_capath"
|
|
@@ -691,6 +699,7 @@ have_func('rb_wait_for_single_fd', 'ruby/io.h')
|
|
|
691
699
|
have_header('ruby/fiber/scheduler.h')
|
|
692
700
|
have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
|
|
693
701
|
have_func('rb_fiber_scheduler_io_wait', 'ruby/fiber/scheduler.h')
|
|
702
|
+
have_func('rb_fiber_scheduler_io_select', 'ruby/fiber/scheduler.h')
|
|
694
703
|
have_func('rb_io_stdio_file')
|
|
695
704
|
have_func('curl_multi_wait')
|
|
696
705
|
have_func('curl_multi_socket_action')
|