curb 1.3.6 → 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 +33 -13
- data/ext/curb.c +9 -0
- data/ext/curb.h +3 -3
- data/ext/curb_easy.c +107 -46
- data/ext/curb_multi.c +211 -130
- data/ext/extconf.rb +4 -0
- data/tests/helper.rb +178 -1
- data/tests/io_select_less_scheduler_probe.rb +105 -0
- data/tests/tc_curl_easy.rb +1 -0
- data/tests/tc_curl_easy_resolve.rb +2 -0
- data/tests/tc_curl_native_coverage.rb +1 -0
- data/tests/tc_fiber_scheduler.rb +332 -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 +4 -2
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,6 +75,88 @@ 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;
|
|
@@ -1172,30 +1259,6 @@ static const char *cselect_flags_str(int flags, char *buf, size_t n) {
|
|
|
1172
1259
|
#define cselect_flags_str(...) ""
|
|
1173
1260
|
#endif
|
|
1174
1261
|
|
|
1175
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1176
|
-
/* Protected call to rb_fiber_scheduler_io_wait to avoid unwinding into C on TypeError. */
|
|
1177
|
-
struct fiber_io_wait_args { VALUE scheduler; VALUE io; VALUE events; VALUE timeout; };
|
|
1178
|
-
static VALUE fiber_io_wait_protected(VALUE argp) {
|
|
1179
|
-
struct fiber_io_wait_args *a = (struct fiber_io_wait_args *)argp;
|
|
1180
|
-
return rb_fiber_scheduler_io_wait(a->scheduler, a->io, a->events, a->timeout);
|
|
1181
|
-
}
|
|
1182
|
-
#endif
|
|
1183
|
-
|
|
1184
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1185
|
-
struct fiber_io_select_args {
|
|
1186
|
-
VALUE scheduler;
|
|
1187
|
-
VALUE readables;
|
|
1188
|
-
VALUE writables;
|
|
1189
|
-
VALUE exceptables;
|
|
1190
|
-
VALUE timeout;
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
static VALUE fiber_io_select_protected(VALUE argp) {
|
|
1194
|
-
struct fiber_io_select_args *a = (struct fiber_io_select_args *)argp;
|
|
1195
|
-
return rb_fiber_scheduler_io_select(a->scheduler, a->readables, a->writables, a->exceptables, a->timeout);
|
|
1196
|
-
}
|
|
1197
|
-
#endif
|
|
1198
|
-
|
|
1199
1262
|
#if defined(RB_INTEGER_TYPE_P)
|
|
1200
1263
|
#define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value)
|
|
1201
1264
|
#else
|
|
@@ -1387,6 +1450,7 @@ struct build_io_select_arrays_args {
|
|
|
1387
1450
|
VALUE writables;
|
|
1388
1451
|
VALUE exceptables;
|
|
1389
1452
|
int failed;
|
|
1453
|
+
int state;
|
|
1390
1454
|
};
|
|
1391
1455
|
|
|
1392
1456
|
static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
@@ -1405,12 +1469,7 @@ static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp
|
|
|
1405
1469
|
io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1406
1470
|
if (io_state || NIL_P(io)) {
|
|
1407
1471
|
if (io_state) {
|
|
1408
|
-
|
|
1409
|
-
VALUE err = rb_errinfo();
|
|
1410
|
-
VALUE msg = rb_obj_as_string(err);
|
|
1411
|
-
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1412
|
-
#endif
|
|
1413
|
-
rb_set_errinfo(Qnil);
|
|
1472
|
+
a->state = io_state;
|
|
1414
1473
|
}
|
|
1415
1474
|
a->failed = 1;
|
|
1416
1475
|
return ST_STOP;
|
|
@@ -1455,14 +1514,17 @@ static int collect_io_select_ready_i(st_data_t key, st_data_t val, st_data_t arg
|
|
|
1455
1514
|
#endif
|
|
1456
1515
|
|
|
1457
1516
|
static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) {
|
|
1458
|
-
|
|
1459
|
-
CURLMcode mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1460
|
-
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1461
|
-
curb_debugf("[curb.socket] drive: initial socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1462
|
-
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1463
|
-
rb_curl_multi_yield_if_given(self, block);
|
|
1517
|
+
CURLMcode mrc;
|
|
1464
1518
|
|
|
1465
|
-
|
|
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) {
|
|
1466
1528
|
struct timeval tv = {0, 0};
|
|
1467
1529
|
long wait_ms = cCurlMutiDefaulttimeout;
|
|
1468
1530
|
|
|
@@ -1473,6 +1535,10 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1473
1535
|
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1474
1536
|
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1475
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
|
+
}
|
|
1476
1542
|
continue;
|
|
1477
1543
|
}
|
|
1478
1544
|
|
|
@@ -1510,26 +1576,24 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1510
1576
|
if (count_tracked > 1) {
|
|
1511
1577
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1512
1578
|
{
|
|
1513
|
-
VALUE scheduler =
|
|
1579
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1514
1580
|
if (scheduler != Qnil) {
|
|
1515
1581
|
VALUE readables = rb_ary_new();
|
|
1516
1582
|
VALUE writables = rb_ary_new();
|
|
1517
1583
|
VALUE exceptables = rb_ary_new();
|
|
1518
|
-
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 };
|
|
1519
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);
|
|
1520
1587
|
if (!build_args.failed) {
|
|
1521
1588
|
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1522
1589
|
VALUE timeout = rb_float_new(timeout_s);
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
curb_debugf("[curb.socket] scheduler io_select failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1531
|
-
#endif
|
|
1532
|
-
rb_set_errinfo(Qnil);
|
|
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");
|
|
1533
1597
|
} else {
|
|
1534
1598
|
handled_wait = 1;
|
|
1535
1599
|
any_ready = RB_TYPE_P(ready, T_ARRAY);
|
|
@@ -1568,8 +1632,36 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1568
1632
|
}
|
|
1569
1633
|
}
|
|
1570
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
|
+
}
|
|
1571
1661
|
if (!handled_wait) {
|
|
1572
|
-
/* 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. */
|
|
1573
1665
|
rb_fdset_t rfds, wfds, efds;
|
|
1574
1666
|
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
|
1575
1667
|
int maxfd = -1;
|
|
@@ -1608,9 +1700,8 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1608
1700
|
handled_wait = 1;
|
|
1609
1701
|
}
|
|
1610
1702
|
} else if (count_tracked == 1) {
|
|
1611
|
-
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1612
1703
|
{
|
|
1613
|
-
VALUE scheduler =
|
|
1704
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1614
1705
|
if (scheduler != Qnil) {
|
|
1615
1706
|
int scheduler_wait_handled = 0;
|
|
1616
1707
|
int events = 0;
|
|
@@ -1620,11 +1711,9 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1620
1711
|
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1621
1712
|
VALUE timeout = rb_float_new(timeout_s);
|
|
1622
1713
|
if (wait_fd < 0) {
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
rb_thread_wait_for(tv);
|
|
1627
|
-
#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);
|
|
1628
1717
|
did_timeout = multi_socket_timer_due(ctx);
|
|
1629
1718
|
scheduler_wait_handled = 1;
|
|
1630
1719
|
} else if (!multi_socket_fd_valid_p(wait_fd)) {
|
|
@@ -1635,44 +1724,25 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1635
1724
|
struct io_for_fd_args io_args = { ctx, wait_fd, wait_what };
|
|
1636
1725
|
int io_state = 0;
|
|
1637
1726
|
VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1638
|
-
if (io_state
|
|
1639
|
-
|
|
1640
|
-
#if CURB_SOCKET_DEBUG
|
|
1641
|
-
VALUE err = rb_errinfo();
|
|
1642
|
-
VALUE msg = rb_obj_as_string(err);
|
|
1643
|
-
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1644
|
-
#endif
|
|
1645
|
-
rb_set_errinfo(Qnil);
|
|
1646
|
-
}
|
|
1727
|
+
if (io_state) rb_jump_tag(io_state);
|
|
1728
|
+
if (NIL_P(io)) {
|
|
1647
1729
|
any_ready = 0;
|
|
1648
1730
|
} else {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
scheduler_wait_handled = 1;
|
|
1662
|
-
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1663
|
-
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1664
|
-
if (any_ready) {
|
|
1665
|
-
if (ready == Qtrue) {
|
|
1666
|
-
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1667
|
-
} else if (CURB_INTEGER_P(ready)) {
|
|
1668
|
-
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1669
|
-
if (ready_flags == 0) {
|
|
1670
|
-
any_ready = 0;
|
|
1671
|
-
did_timeout = multi_socket_timer_due(ctx);
|
|
1672
|
-
}
|
|
1673
|
-
} else {
|
|
1674
|
-
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
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);
|
|
1675
1743
|
}
|
|
1744
|
+
} else {
|
|
1745
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1676
1746
|
}
|
|
1677
1747
|
}
|
|
1678
1748
|
}
|
|
@@ -1680,7 +1750,6 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1680
1750
|
if (scheduler_wait_handled) handled_wait = 1;
|
|
1681
1751
|
}
|
|
1682
1752
|
}
|
|
1683
|
-
#endif
|
|
1684
1753
|
#if defined(HAVE_RB_WAIT_FOR_SINGLE_FD)
|
|
1685
1754
|
if (!handled_wait && wait_fd >= 0) {
|
|
1686
1755
|
int ev = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
@@ -1724,14 +1793,13 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1724
1793
|
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1725
1794
|
}
|
|
1726
1795
|
} else { /* count_tracked == 0 */
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
#endif
|
|
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);
|
|
1732
1800
|
/* libcurl can report active work without a socket callback or deadline;
|
|
1733
|
-
* drive the timeout socket after the
|
|
1734
|
-
*
|
|
1801
|
+
* drive the timeout socket after the sleep so the state machine does
|
|
1802
|
+
* not stall indefinitely. */
|
|
1735
1803
|
did_timeout = 1;
|
|
1736
1804
|
}
|
|
1737
1805
|
|
|
@@ -1759,7 +1827,13 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1759
1827
|
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1760
1828
|
curb_debugf("[curb.socket] processed completions; running=%d", rbcm->running);
|
|
1761
1829
|
rb_curl_multi_yield_if_given(self, block);
|
|
1762
|
-
|
|
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);
|
|
1763
1837
|
}
|
|
1764
1838
|
|
|
1765
1839
|
struct socket_drive_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; VALUE block; };
|
|
@@ -1822,8 +1896,6 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1822
1896
|
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
|
1823
1897
|
|
|
1824
1898
|
/* finalize */
|
|
1825
|
-
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1826
|
-
rb_curl_multi_yield_if_given(self, block);
|
|
1827
1899
|
if (cCurlMutiAutoClose == 1) {
|
|
1828
1900
|
rbcm->allow_close_during_perform = 1;
|
|
1829
1901
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
@@ -1959,11 +2031,9 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1959
2031
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
1960
2032
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
1961
2033
|
rb_curl_multi_yield_if_given(self, block);
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
}
|
|
1966
|
-
#endif
|
|
2034
|
+
/* Yield the fiber so repeated immediate curl timeouts cannot starve
|
|
2035
|
+
* sibling work on this thread. */
|
|
2036
|
+
curb_multi_scheduler_yield();
|
|
1967
2037
|
continue;
|
|
1968
2038
|
}
|
|
1969
2039
|
|
|
@@ -1979,11 +2049,11 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1979
2049
|
wait_args.timeout_ms = timeout_milliseconds;
|
|
1980
2050
|
wait_args.numfds = 0;
|
|
1981
2051
|
/*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
*
|
|
1985
|
-
*
|
|
1986
|
-
*
|
|
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).
|
|
1987
2057
|
*/
|
|
1988
2058
|
CURLMcode wait_rc;
|
|
1989
2059
|
#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
@@ -1997,13 +2067,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1997
2067
|
raise_curl_multi_error_exception(wait_rc);
|
|
1998
2068
|
}
|
|
1999
2069
|
if (wait_args.numfds == 0) {
|
|
2000
|
-
|
|
2001
|
-
struct timeval tv_sleep = tv_100ms;
|
|
2002
|
-
/* Sleep in a scheduler-aware way. */
|
|
2003
|
-
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
2004
|
-
#else
|
|
2005
|
-
rb_thread_wait_for(tv_100ms);
|
|
2006
|
-
#endif
|
|
2070
|
+
curb_multi_scheduler_sleep(&tv_100ms);
|
|
2007
2071
|
}
|
|
2008
2072
|
/* Process pending transfers after waiting */
|
|
2009
2073
|
rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
|
|
@@ -2027,12 +2091,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2027
2091
|
|
|
2028
2092
|
if (maxfd == -1) {
|
|
2029
2093
|
/* libcurl recommends sleeping for 100ms */
|
|
2030
|
-
|
|
2031
|
-
struct timeval tv_sleep = tv_100ms;
|
|
2032
|
-
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
2033
|
-
#else
|
|
2034
|
-
rb_thread_wait_for(tv_100ms);
|
|
2035
|
-
#endif
|
|
2094
|
+
curb_multi_scheduler_sleep(&tv_100ms);
|
|
2036
2095
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
2037
2096
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
2038
2097
|
rb_curl_multi_yield_if_given(self, block);
|
|
@@ -2055,7 +2114,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2055
2114
|
#endif
|
|
2056
2115
|
|
|
2057
2116
|
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
2058
|
-
/*
|
|
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. */
|
|
2059
2121
|
{
|
|
2060
2122
|
rb_fdset_t rfds, wfds, efds;
|
|
2061
2123
|
rb_fd_init(&rfds);
|
|
@@ -2167,6 +2229,18 @@ static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE sel
|
|
|
2167
2229
|
}
|
|
2168
2230
|
|
|
2169
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
|
|
2170
2244
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
|
|
2171
2245
|
}
|
|
2172
2246
|
|
|
@@ -2233,6 +2307,12 @@ static void curl_multi_mark(void *ptr) {
|
|
|
2233
2307
|
/* =================== INIT LIB =====================*/
|
|
2234
2308
|
void init_curb_multi() {
|
|
2235
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");
|
|
2236
2316
|
id_deferred_exception_ivar = rb_intern("@__curb_deferred_exception");
|
|
2237
2317
|
id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id");
|
|
2238
2318
|
id_native_safety_signatures_ivar = rb_intern("@__curb_native_safety_signatures");
|
|
@@ -2254,9 +2334,10 @@ void init_curb_multi() {
|
|
|
2254
2334
|
rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
|
|
2255
2335
|
rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
|
|
2256
2336
|
/*
|
|
2257
|
-
*
|
|
2258
|
-
*
|
|
2259
|
-
*
|
|
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.
|
|
2260
2341
|
*/
|
|
2261
2342
|
rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
|
|
2262
2343
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
data/ext/extconf.rb
CHANGED
|
@@ -462,6 +462,9 @@ have_constant "curlopt_ftp_use_eprt"
|
|
|
462
462
|
have_constant "curlopt_ftp_use_epsv"
|
|
463
463
|
have_constant "curlopt_ftp_use_pret"
|
|
464
464
|
have_constant "curlopt_ftp_create_missing_dirs"
|
|
465
|
+
have_constant "curlftp_create_dir_none"
|
|
466
|
+
have_constant "curlftp_create_dir"
|
|
467
|
+
have_constant "curlftp_create_dir_retry"
|
|
465
468
|
have_constant "curlopt_ftp_response_timeout"
|
|
466
469
|
have_constant "curlopt_ftp_alternative_to_user"
|
|
467
470
|
have_constant "curlopt_ftp_skip_pasv_ip"
|
|
@@ -700,6 +703,7 @@ have_header('ruby/fiber/scheduler.h')
|
|
|
700
703
|
have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
|
|
701
704
|
have_func('rb_fiber_scheduler_io_wait', 'ruby/fiber/scheduler.h')
|
|
702
705
|
have_func('rb_fiber_scheduler_io_select', 'ruby/fiber/scheduler.h')
|
|
706
|
+
have_func('rb_fiber_scheduler_kernel_sleep', 'ruby/fiber/scheduler.h')
|
|
703
707
|
have_func('rb_io_stdio_file')
|
|
704
708
|
have_func('curl_multi_wait')
|
|
705
709
|
have_func('curl_multi_socket_action')
|