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.
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
- rb_hash_delete(requests, rb_obj_id(easy));
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 timeout_ms; /* last timeout set by libcurl timer callback */
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) ctx->timeout_ms = timeout_ms;
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 VALUE multi_socket_io_for_fd(multi_socket_ctx *ctx, int fd) {
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("r+"));
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
- if (ctx->timeout_ms < 0) {
1303
- tv.tv_sec = cCurlMutiDefaulttimeout / 1000;
1304
- tv.tv_usec = (cCurlMutiDefaulttimeout % 1000) * 1000;
1305
- } else {
1306
- long t = ctx->timeout_ms;
1307
- if (t > cCurlMutiDefaulttimeout) t = cCurlMutiDefaulttimeout;
1308
- if (t < 0) t = 0;
1309
- tv.tv_sec = t / 1000;
1310
- tv.tv_usec = (t % 1000) * 1000;
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
- int handled_wait = 0;
1337
- if (count_tracked > 1) {
1338
- /* Multi-fd wait using scheduler-aware rb_thread_fd_select. */
1339
- rb_fdset_t rfds, wfds, efds;
1340
- rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
1341
- int maxfd = -1;
1342
- rb_fdset_from_sockmap(ctx->sock_map, &rfds, &wfds, &efds, &maxfd);
1343
- int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv);
1344
- curb_debugf("[curb.socket] rb_thread_fd_select(multi) rc=%d maxfd=%d", rc, maxfd);
1345
- if (rc < 0) {
1346
- rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
1347
- if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
1348
- continue;
1349
- }
1350
- any_ready = (rc > 0);
1351
- did_timeout = (rc == 0);
1352
- if (any_ready) {
1353
- struct ready_fd *ready_fds = ALLOC_N(struct ready_fd, count_tracked);
1354
- struct collect_ready_fd_args d;
1355
- int i;
1356
- d.r = &rfds;
1357
- d.w = &wfds;
1358
- d.e = &efds;
1359
- d.fds = ready_fds;
1360
- d.capacity = count_tracked;
1361
- d.count = 0;
1362
- st_foreach(ctx->sock_map, collect_ready_fd_i, (st_data_t)&d);
1363
- for (i = 0; i < d.count; i++) {
1364
- mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)d.fds[i].fd, d.fds[i].flags, &rbcm->running);
1365
- if (mrc != CURLM_OK) {
1366
- xfree(ready_fds);
1367
- rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
1368
- raise_curl_multi_error_exception(mrc);
1369
- }
1370
- }
1371
- xfree(ready_fds);
1372
- }
1373
- rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
1374
- handled_wait = 1;
1375
- } else if (count_tracked == 1) {
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 = 1;
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) rb_set_errinfo(Qnil);
1402
- multi_socket_forget_fd(ctx, wait_fd);
1403
- did_timeout = 1;
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 = 1;
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.timeout_ms = -1;
1809
+ ctx.timeout_deadline_ms = -1;
1556
1810
  ctx.io_cache = rb_hash_new();
1557
- rb_gc_register_address(&ctx.io_cache);
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
@@ -28,6 +28,7 @@ extern const rb_data_type_t ruby_curl_multi_data_type;
28
28
 
29
29
  void init_curb_multi();
30
30
  void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
31
+ CURLMcode rb_curl_multi_detach_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
31
32
 
32
33
 
33
34
  #endif
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')