curb 1.3.7 → 1.4.1
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/ChangeLog.md +432 -0
- data/README.md +62 -5
- data/Rakefile +12 -1
- data/ext/curb.c +17 -4
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +344 -191
- data/ext/curb_easy.h +6 -0
- data/ext/curb_macros.h +16 -0
- data/ext/curb_multi.c +479 -103
- data/ext/curb_multi.h +8 -0
- data/ext/curb_postfield.c +134 -145
- data/ext/curb_postfield.h +10 -1
- data/ext/extconf.rb +6 -0
- data/lib/curl/easy.rb +28 -12
- data/lib/curl/multi.rb +1 -1
- data/lib/curl.rb +58 -19
- data/tests/helper.rb +122 -3
- data/tests/tc_curl_easy.rb +175 -4
- data/tests/tc_curl_easy_cookielist.rb +18 -12
- data/tests/tc_curl_maxfilesize.rb +109 -5
- data/tests/tc_curl_multi.rb +29 -0
- data/tests/tc_curl_multi_poll.rb +223 -0
- data/tests/tc_curl_multi_socket_poller.rb +374 -0
- data/tests/tc_curl_native_coverage.rb +57 -0
- data/tests/tc_curl_network_policy.rb +108 -1
- data/tests/tc_curl_postfield.rb +176 -0
- data/tests/tc_curl_protocols.rb +19 -7
- data/tests/tc_curl_transfer_guard.rb +112 -0
- data/tests/tc_fiber_scheduler.rb +101 -2
- data/tests/tc_ractor.rb +141 -0
- metadata +11 -2
data/ext/curb_multi.c
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
#include <sys/time.h>
|
|
33
33
|
#include <time.h>
|
|
34
34
|
#endif
|
|
35
|
+
#include <limits.h>
|
|
35
36
|
#include <stdint.h>
|
|
36
37
|
#include <stdarg.h>
|
|
37
38
|
|
|
@@ -62,7 +63,40 @@
|
|
|
62
63
|
#include <fcntl.h>
|
|
63
64
|
#endif
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
/*
|
|
67
|
+
* Prefer curl_multi_poll for the blocking perform loop: it uses poll(), so it
|
|
68
|
+
* has no FD_SETSIZE limit (select-based waiting cannot see sockets numbered
|
|
69
|
+
* above 1024 and degrades to fixed sleeps), and curl_multi_wakeup lets the
|
|
70
|
+
* unblocking function end the wait immediately for Thread#raise, Thread#kill
|
|
71
|
+
* and signal delivery.
|
|
72
|
+
*/
|
|
73
|
+
#if defined(HAVE_CURL_MULTI_POLL) && defined(HAVE_CURL_MULTI_WAKEUP) && defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
74
|
+
#define CURB_USE_MULTI_POLL 1
|
|
75
|
+
#endif
|
|
76
|
+
|
|
77
|
+
#ifdef CURB_USE_MULTI_POLL
|
|
78
|
+
struct multi_poll_args {
|
|
79
|
+
CURLM *handle;
|
|
80
|
+
int timeout_ms;
|
|
81
|
+
int numfds;
|
|
82
|
+
CURLMcode code;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
static void *curb_multi_poll_without_gvl(void *p) {
|
|
86
|
+
struct multi_poll_args *args = p;
|
|
87
|
+
args->code = curl_multi_poll(args->handle, NULL, 0, args->timeout_ms, &args->numfds);
|
|
88
|
+
return NULL;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Unblocking function: curl_multi_wakeup is safe to call from another thread
|
|
92
|
+
* while curl_multi_poll is blocked on the same handle. A wakeup that arrives
|
|
93
|
+
* after the poll returned only makes the next poll return early once. */
|
|
94
|
+
static void curb_multi_poll_ubf(void *handle) {
|
|
95
|
+
curl_multi_wakeup((CURLM *)handle);
|
|
96
|
+
}
|
|
97
|
+
#endif
|
|
98
|
+
|
|
99
|
+
#if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT) && !defined(CURB_USE_MULTI_POLL)
|
|
66
100
|
struct wait_args {
|
|
67
101
|
CURLM *handle;
|
|
68
102
|
long timeout_ms;
|
|
@@ -76,10 +110,16 @@ static void *curl_multi_wait_wrapper(void *p) {
|
|
|
76
110
|
#endif
|
|
77
111
|
|
|
78
112
|
static VALUE cRubyFiber;
|
|
113
|
+
static VALUE cRubyRactor;
|
|
79
114
|
static ID id_fiber_blocking_p;
|
|
80
115
|
static ID id_fiber_scheduler;
|
|
81
116
|
static ID id_kernel_sleep;
|
|
82
117
|
static ID id_io_wait;
|
|
118
|
+
static ID id_ractor_current;
|
|
119
|
+
static ID id_ractor_aref;
|
|
120
|
+
static ID id_ractor_aset;
|
|
121
|
+
static ID id_ractor_default_timeout_key;
|
|
122
|
+
static ID id_ractor_autoclose_key;
|
|
83
123
|
|
|
84
124
|
/*
|
|
85
125
|
* Ruby 3.0 introduced the Fiber scheduler at the Ruby level, but did not ship
|
|
@@ -163,6 +203,7 @@ static ID id_deferred_exception_ivar;
|
|
|
163
203
|
static ID id_deferred_exception_source_id_ivar;
|
|
164
204
|
static ID id_native_safety_signatures_ivar;
|
|
165
205
|
static ID id_socket_io_cache_ivar;
|
|
206
|
+
static ID id_socket_poller_io_ivar;
|
|
166
207
|
|
|
167
208
|
#ifdef RDOC_NEVER_DEFINED
|
|
168
209
|
mCurl = rb_define_module("Curl");
|
|
@@ -173,6 +214,32 @@ VALUE cCurlMulti;
|
|
|
173
214
|
static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
|
|
174
215
|
static char cCurlMutiAutoClose = 0;
|
|
175
216
|
|
|
217
|
+
/* Ruby 3.0+ provides storage owned by the current Ractor. Keep configurable
|
|
218
|
+
* defaults there so separate Ractors never race on process-global C values.
|
|
219
|
+
* Rubies without Ractor retain the historical process-wide settings. */
|
|
220
|
+
static VALUE curb_multi_ractor_setting(ID key) {
|
|
221
|
+
VALUE current;
|
|
222
|
+
|
|
223
|
+
if (NIL_P(cRubyRactor)) return Qundef;
|
|
224
|
+
current = rb_funcall(cRubyRactor, id_ractor_current, 0);
|
|
225
|
+
return rb_funcall(current, id_ractor_aref, 1, ID2SYM(key));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
static void curb_multi_set_ractor_setting(ID key, VALUE value) {
|
|
229
|
+
VALUE current = rb_funcall(cRubyRactor, id_ractor_current, 0);
|
|
230
|
+
rb_funcall(current, id_ractor_aset, 2, ID2SYM(key), value);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static long curb_multi_default_timeout(void) {
|
|
234
|
+
VALUE value = curb_multi_ractor_setting(id_ractor_default_timeout_key);
|
|
235
|
+
return (UNDEF_P(value) || NIL_P(value)) ? cCurlMutiDefaulttimeout : NUM2LONG(value);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static int curb_multi_autoclose_enabled(void) {
|
|
239
|
+
VALUE value = curb_multi_ractor_setting(id_ractor_autoclose_key);
|
|
240
|
+
return (UNDEF_P(value) || NIL_P(value)) ? cCurlMutiAutoClose == 1 : RTEST(value);
|
|
241
|
+
}
|
|
242
|
+
|
|
176
243
|
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result);
|
|
177
244
|
static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
|
|
178
245
|
static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
|
|
@@ -183,7 +250,7 @@ static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm);
|
|
|
183
250
|
static void curl_multi_mark(void *ptr);
|
|
184
251
|
static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm);
|
|
185
252
|
static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce);
|
|
186
|
-
|
|
253
|
+
void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy);
|
|
187
254
|
static VALUE ruby_curl_multi_mark_closed(VALUE self);
|
|
188
255
|
static VALUE ruby_curl_multi_alloc(VALUE klass);
|
|
189
256
|
static VALUE ruby_curl_multi_initialize(VALUE self);
|
|
@@ -427,7 +494,29 @@ static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce) {
|
|
|
427
494
|
return st_lookup(rbcm->attached, (st_data_t)rbce, &value);
|
|
428
495
|
}
|
|
429
496
|
|
|
430
|
-
|
|
497
|
+
/* Attachment, rather than easy.multi alone, distinguishes a live transfer
|
|
498
|
+
* from a completed easy whose status callback still sees its former multi. */
|
|
499
|
+
int rb_curl_multi_easy_performing(void *rbce_ptr) {
|
|
500
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
|
501
|
+
ruby_curl_multi *rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi);
|
|
502
|
+
return rbcm && rbcm->perform_active && rb_curl_multi_has_easy(rbcm, rbce);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
static void ruby_curl_multi_check_thread(ruby_curl_multi *rbcm) {
|
|
506
|
+
if (rbcm->perform_active && rbcm->perform_thread != rb_thread_current()) {
|
|
507
|
+
rb_raise(rb_eRuntimeError, "Cannot access a Curl::Multi handle performing on another thread");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
void rb_curl_multi_check_easy_thread(void *rbce_ptr) {
|
|
512
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
|
513
|
+
ruby_curl_multi *rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi);
|
|
514
|
+
if (rbcm && rb_curl_multi_has_easy(rbcm, rbce)) {
|
|
515
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
|
|
431
520
|
VALUE requests;
|
|
432
521
|
VALUE object_id;
|
|
433
522
|
VALUE safety_signatures;
|
|
@@ -502,6 +591,7 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
|
502
591
|
rbcm->running = 0;
|
|
503
592
|
rbcm->closed = 0;
|
|
504
593
|
rbcm->perform_active = 0;
|
|
594
|
+
rbcm->perform_thread = Qnil;
|
|
505
595
|
rbcm->callback_active = 0;
|
|
506
596
|
rbcm->allow_close_during_perform = 0;
|
|
507
597
|
|
|
@@ -519,6 +609,7 @@ static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
|
519
609
|
}
|
|
520
610
|
|
|
521
611
|
static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm) {
|
|
612
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
522
613
|
if (!rbcm->handle) {
|
|
523
614
|
if (rbcm->closed) {
|
|
524
615
|
raise_curl_multi_error_exception(CURLM_BAD_HANDLE);
|
|
@@ -548,6 +639,10 @@ static VALUE ruby_curl_multi_initialize(VALUE self) {
|
|
|
548
639
|
|
|
549
640
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
550
641
|
|
|
642
|
+
if (rbcm->perform_active) {
|
|
643
|
+
rb_raise(rb_eRuntimeError, "Cannot initialize an active Curl::Multi handle during perform");
|
|
644
|
+
}
|
|
645
|
+
|
|
551
646
|
ruby_curl_multi_init(rbcm);
|
|
552
647
|
|
|
553
648
|
/*
|
|
@@ -563,12 +658,18 @@ static VALUE ruby_curl_multi_initialize(VALUE self) {
|
|
|
563
658
|
* call-seq:
|
|
564
659
|
* Curl::Multi.default_timeout = 4 => 4
|
|
565
660
|
*
|
|
566
|
-
* Set the
|
|
567
|
-
*
|
|
661
|
+
* Set the default timeout for Curl::Multi handles in the current Ractor. On
|
|
662
|
+
* Rubies without Ractor this remains process-wide. This value is used when
|
|
663
|
+
* libcurl cannot determine a timeout value when calling curl_multi_timeout.
|
|
568
664
|
*
|
|
569
665
|
*/
|
|
570
666
|
VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
571
|
-
|
|
667
|
+
long value = NUM2LONG(timeout);
|
|
668
|
+
if (NIL_P(cRubyRactor)) {
|
|
669
|
+
cCurlMutiDefaulttimeout = value;
|
|
670
|
+
} else {
|
|
671
|
+
curb_multi_set_ractor_setting(id_ractor_default_timeout_key, LONG2NUM(value));
|
|
672
|
+
}
|
|
572
673
|
return timeout;
|
|
573
674
|
}
|
|
574
675
|
|
|
@@ -576,23 +677,29 @@ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
|
576
677
|
* call-seq:
|
|
577
678
|
* Curl::Multi.default_timeout = 4 => 4
|
|
578
679
|
*
|
|
579
|
-
* Get the
|
|
680
|
+
* Get the current Ractor's default timeout for Curl::Multi handles.
|
|
580
681
|
*
|
|
581
682
|
*/
|
|
582
683
|
VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
|
|
583
|
-
return LONG2NUM(
|
|
684
|
+
return LONG2NUM(curb_multi_default_timeout());
|
|
584
685
|
}
|
|
585
686
|
|
|
586
687
|
/*
|
|
587
688
|
* call-seq:
|
|
588
689
|
* Curl::Multi.autoclose = true => true
|
|
589
690
|
*
|
|
590
|
-
* Automatically close open connections after each request
|
|
591
|
-
* for reuse until the next
|
|
691
|
+
* Automatically close open connections after each request in the current
|
|
692
|
+
* Ractor. Otherwise, the connection will remain open for reuse until the next
|
|
693
|
+
* GC.
|
|
592
694
|
*
|
|
593
695
|
*/
|
|
594
696
|
VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
|
|
595
|
-
|
|
697
|
+
VALUE value = onoff == Qtrue ? Qtrue : Qfalse;
|
|
698
|
+
if (NIL_P(cRubyRactor)) {
|
|
699
|
+
cCurlMutiAutoClose = RTEST(value) ? 1 : 0;
|
|
700
|
+
} else {
|
|
701
|
+
curb_multi_set_ractor_setting(id_ractor_autoclose_key, value);
|
|
702
|
+
}
|
|
596
703
|
return onoff;
|
|
597
704
|
}
|
|
598
705
|
|
|
@@ -600,11 +707,11 @@ VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
|
|
|
600
707
|
* call-seq:
|
|
601
708
|
* Curl::Multi.autoclose => true|false
|
|
602
709
|
*
|
|
603
|
-
* Get the
|
|
710
|
+
* Get the current Ractor's default autoclose setting for Curl::Multi handles.
|
|
604
711
|
*
|
|
605
712
|
*/
|
|
606
713
|
VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
|
|
607
|
-
return
|
|
714
|
+
return curb_multi_autoclose_enabled() ? Qtrue : Qfalse;
|
|
608
715
|
}
|
|
609
716
|
|
|
610
717
|
/*
|
|
@@ -702,7 +809,7 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
|
702
809
|
ruby_curl_multi *existing_rbcm;
|
|
703
810
|
|
|
704
811
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
705
|
-
|
|
812
|
+
rbce = ruby_curl_easy_get_handle(easy);
|
|
706
813
|
ruby_curl_multi_ensure_handle(rbcm);
|
|
707
814
|
|
|
708
815
|
if (rb_curl_multi_has_easy(rbcm, rbce)) {
|
|
@@ -739,7 +846,7 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
|
739
846
|
}
|
|
740
847
|
}
|
|
741
848
|
|
|
742
|
-
rbce->multi_attachment_generation
|
|
849
|
+
rbce->multi_attachment_generation = ++rbcm->attachment_sequence;
|
|
743
850
|
st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy);
|
|
744
851
|
|
|
745
852
|
/* track a reference to associated multi handle */
|
|
@@ -767,6 +874,7 @@ VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
|
|
|
767
874
|
|
|
768
875
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
769
876
|
|
|
877
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
770
878
|
rb_curl_multi_remove(rbcm, rb_easy_handle);
|
|
771
879
|
|
|
772
880
|
return self;
|
|
@@ -776,7 +884,7 @@ static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
|
|
|
776
884
|
CURLMcode result;
|
|
777
885
|
ruby_curl_easy *rbce;
|
|
778
886
|
|
|
779
|
-
|
|
887
|
+
rbce = ruby_curl_easy_get_handle(easy);
|
|
780
888
|
result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
781
889
|
if (result != 0) {
|
|
782
890
|
raise_curl_multi_error_exception(result);
|
|
@@ -859,42 +967,17 @@ struct multi_complete_callback_args {
|
|
|
859
967
|
ruby_curl_multi *rbcm;
|
|
860
968
|
ruby_curl_easy *rbce;
|
|
861
969
|
int result;
|
|
862
|
-
|
|
970
|
+
/* rbcm->attachment_sequence before the callbacks ran; easies stamped later
|
|
971
|
+
* were attached (or re-attached) by the callbacks. */
|
|
972
|
+
unsigned long attachment_sequence_before;
|
|
863
973
|
};
|
|
864
974
|
|
|
865
|
-
static int snapshot_attached_easy_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
866
|
-
st_table *snapshot = (st_table *)arg;
|
|
867
|
-
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
868
|
-
|
|
869
|
-
if (!rbce) {
|
|
870
|
-
return ST_CONTINUE;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
st_insert(snapshot, key, (st_data_t)rbce->multi_attachment_generation);
|
|
874
|
-
return ST_CONTINUE;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
static st_table *capture_attached_easy_snapshot(ruby_curl_multi *rbcm) {
|
|
878
|
-
st_table *snapshot = st_init_numtable();
|
|
879
|
-
|
|
880
|
-
if (!snapshot) {
|
|
881
|
-
rb_raise(rb_eNoMemError, "Failed to allocate multi callback snapshot table");
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
if (rbcm && rbcm->attached) {
|
|
885
|
-
st_foreach(rbcm->attached, snapshot_attached_easy_i, (st_data_t)snapshot);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
return snapshot;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
975
|
struct collect_new_attached_easies_ctx {
|
|
892
|
-
|
|
976
|
+
unsigned long attachment_sequence_before;
|
|
893
977
|
VALUE easies;
|
|
894
978
|
};
|
|
895
979
|
|
|
896
980
|
static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
897
|
-
st_data_t existing = 0;
|
|
898
981
|
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
899
982
|
struct collect_new_attached_easies_ctx *ctx = (struct collect_new_attached_easies_ctx *)arg;
|
|
900
983
|
|
|
@@ -902,24 +985,25 @@ static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t
|
|
|
902
985
|
return ST_CONTINUE;
|
|
903
986
|
}
|
|
904
987
|
|
|
905
|
-
if (
|
|
906
|
-
existing != (st_data_t)rbce->multi_attachment_generation) {
|
|
988
|
+
if (rbce->multi_attachment_generation > ctx->attachment_sequence_before) {
|
|
907
989
|
rb_ary_push(ctx->easies, (VALUE)val);
|
|
908
990
|
}
|
|
909
991
|
|
|
910
992
|
return ST_CONTINUE;
|
|
911
993
|
}
|
|
912
994
|
|
|
913
|
-
|
|
995
|
+
/* Only runs when a completion callback raised, so this walk over the
|
|
996
|
+
* attached table stays off the per-completion path. */
|
|
997
|
+
static void rb_curl_multi_remove_easies_attached_since(VALUE self, ruby_curl_multi *rbcm, unsigned long attachment_sequence_before) {
|
|
914
998
|
struct collect_new_attached_easies_ctx ctx;
|
|
915
999
|
VALUE easies = rb_ary_new();
|
|
916
1000
|
long index;
|
|
917
1001
|
|
|
918
|
-
if (!rbcm || !
|
|
1002
|
+
if (!rbcm || !rbcm->attached) {
|
|
919
1003
|
return;
|
|
920
1004
|
}
|
|
921
1005
|
|
|
922
|
-
ctx.
|
|
1006
|
+
ctx.attachment_sequence_before = attachment_sequence_before;
|
|
923
1007
|
ctx.easies = easies;
|
|
924
1008
|
st_foreach(rbcm->attached, collect_new_attached_easies_i, (st_data_t)&ctx);
|
|
925
1009
|
|
|
@@ -956,7 +1040,7 @@ static void stash_and_raise_status_callback_error_if_unmasked(struct multi_compl
|
|
|
956
1040
|
}
|
|
957
1041
|
|
|
958
1042
|
stash_multi_exception_if_unset(args->self, exception, args->easy);
|
|
959
|
-
|
|
1043
|
+
rb_curl_multi_remove_easies_attached_since(args->self, args->rbcm, args->attachment_sequence_before);
|
|
960
1044
|
rb_exc_raise(exception);
|
|
961
1045
|
}
|
|
962
1046
|
|
|
@@ -967,7 +1051,8 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
|
|
|
967
1051
|
VALUE easy_callback_error = Qnil;
|
|
968
1052
|
VALUE callargs;
|
|
969
1053
|
VALUE did_raise = rb_hash_new();
|
|
970
|
-
long redirect_count;
|
|
1054
|
+
long redirect_count = 0;
|
|
1055
|
+
CURLcode info_result;
|
|
971
1056
|
|
|
972
1057
|
args->rbcm->callback_active = 1;
|
|
973
1058
|
|
|
@@ -985,12 +1070,18 @@ static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
|
|
|
985
1070
|
}
|
|
986
1071
|
|
|
987
1072
|
#ifdef HAVE_CURLINFO_RESPONSE_CODE
|
|
988
|
-
curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
1073
|
+
info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
989
1074
|
#else /* use fdsets path for waiting */
|
|
990
1075
|
// old libcurl
|
|
991
|
-
curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code);
|
|
1076
|
+
info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code);
|
|
992
1077
|
#endif
|
|
993
|
-
|
|
1078
|
+
if (info_result != CURLE_OK) {
|
|
1079
|
+
raise_curl_easy_error_exception(info_result);
|
|
1080
|
+
}
|
|
1081
|
+
info_result = curl_easy_getinfo(args->rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
|
|
1082
|
+
if (info_result != CURLE_OK) {
|
|
1083
|
+
raise_curl_easy_error_exception(info_result);
|
|
1084
|
+
}
|
|
994
1085
|
|
|
995
1086
|
if (args->result != 0) {
|
|
996
1087
|
if (!rb_easy_nil("failure_proc")) {
|
|
@@ -1059,11 +1150,6 @@ static VALUE rb_curl_multi_finish_completion_callbacks(VALUE argp) {
|
|
|
1059
1150
|
args->rbce->multi = Qnil;
|
|
1060
1151
|
}
|
|
1061
1152
|
|
|
1062
|
-
if (args->attached_snapshot) {
|
|
1063
|
-
st_free_table(args->attached_snapshot);
|
|
1064
|
-
args->attached_snapshot = NULL;
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
1153
|
return Qnil;
|
|
1068
1154
|
}
|
|
1069
1155
|
|
|
@@ -1124,7 +1210,7 @@ static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int res
|
|
|
1124
1210
|
rbcm,
|
|
1125
1211
|
rbce,
|
|
1126
1212
|
result,
|
|
1127
|
-
|
|
1213
|
+
rbcm->attachment_sequence
|
|
1128
1214
|
};
|
|
1129
1215
|
rb_ensure(rb_curl_multi_run_completion_callbacks, (VALUE)&args,
|
|
1130
1216
|
rb_curl_multi_finish_completion_callbacks, (VALUE)&args);
|
|
@@ -1171,21 +1257,14 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
|
1171
1257
|
CURLMcode mcode;
|
|
1172
1258
|
|
|
1173
1259
|
/*
|
|
1174
|
-
*
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1177
|
-
*
|
|
1178
|
-
* here as long as mcode is CURLM_CALL_MULTIPERFORM.
|
|
1260
|
+
* Perform all pending actions on every easy handle attached to this multi
|
|
1261
|
+
* handle. libcurl stopped returning CURLM_CALL_MULTI_PERFORM here in 7.20
|
|
1262
|
+
* (curb supports 7.58+); should it appear, treat it as success: the drive
|
|
1263
|
+
* loop calls curl_multi_perform again on its next pass.
|
|
1179
1264
|
*/
|
|
1180
|
-
|
|
1181
|
-
mcode = curl_multi_perform(multi_handle, still_running);
|
|
1182
|
-
} while (mcode == CURLM_CALL_MULTI_PERFORM);
|
|
1265
|
+
mcode = curl_multi_perform(multi_handle, still_running);
|
|
1183
1266
|
|
|
1184
|
-
|
|
1185
|
-
* Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
|
|
1186
|
-
*/
|
|
1187
|
-
|
|
1188
|
-
if (mcode != CURLM_OK) {
|
|
1267
|
+
if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
|
|
1189
1268
|
raise_curl_multi_error_exception(mcode);
|
|
1190
1269
|
}
|
|
1191
1270
|
|
|
@@ -1200,10 +1279,36 @@ static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_runnin
|
|
|
1200
1279
|
|
|
1201
1280
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
1202
1281
|
/* ---- socket-action implementation (scheduler-friendly) ---- */
|
|
1282
|
+
|
|
1283
|
+
/*
|
|
1284
|
+
* Under a fiber scheduler, mirror libcurl's socket interest into a kernel
|
|
1285
|
+
* event queue (epoll or kqueue) and wait on that single descriptor with the
|
|
1286
|
+
* scheduler's io_wait hook. Every scheduler implements io_wait, so this avoids
|
|
1287
|
+
* the optional io_select hook (which Async implements by starting a thread
|
|
1288
|
+
* per call) and the one-descriptor fallback for schedulers without it.
|
|
1289
|
+
*/
|
|
1290
|
+
#if defined(HAVE_SYS_EPOLL_H) && defined(HAVE_EPOLL_CREATE1)
|
|
1291
|
+
#include <sys/epoll.h>
|
|
1292
|
+
#define CURB_SOCKET_POLLER 1
|
|
1293
|
+
#define CURB_SOCKET_POLLER_EPOLL 1
|
|
1294
|
+
#define CURB_SOCKET_POLLER_NAME "epoll"
|
|
1295
|
+
#elif defined(HAVE_SYS_EVENT_H) && defined(HAVE_KQUEUE)
|
|
1296
|
+
#include <sys/types.h>
|
|
1297
|
+
#include <sys/event.h>
|
|
1298
|
+
#define CURB_SOCKET_POLLER 1
|
|
1299
|
+
#define CURB_SOCKET_POLLER_KQUEUE 1
|
|
1300
|
+
#define CURB_SOCKET_POLLER_NAME "kqueue"
|
|
1301
|
+
#endif
|
|
1302
|
+
|
|
1303
|
+
/* Events harvested per poller read; more are collected on following rounds. */
|
|
1304
|
+
#define CURB_SOCKET_POLLER_BATCH 64
|
|
1305
|
+
|
|
1203
1306
|
typedef struct {
|
|
1204
1307
|
st_table *sock_map; /* key: int fd, value: int 'what' (CURL_POLL_*) */
|
|
1205
1308
|
long long timeout_deadline_ms; /* absolute deadline for CURL_SOCKET_TIMEOUT */
|
|
1206
1309
|
VALUE io_cache; /* fd -> IO wrapper for fiber-scheduler waits */
|
|
1310
|
+
int poller_fd; /* epoll/kqueue descriptor, or -1 when unused */
|
|
1311
|
+
VALUE poller_io; /* IO wrapper for poller_fd passed to io_wait */
|
|
1207
1312
|
} multi_socket_ctx;
|
|
1208
1313
|
|
|
1209
1314
|
static long long multi_socket_current_time_ms(void) {
|
|
@@ -1294,12 +1399,77 @@ static int multi_socket_cselect_flags_for_wait_events(int events) {
|
|
|
1294
1399
|
return flags;
|
|
1295
1400
|
}
|
|
1296
1401
|
|
|
1402
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1403
|
+
static int multi_socket_poller_create(void) {
|
|
1404
|
+
#if defined(CURB_SOCKET_POLLER_EPOLL)
|
|
1405
|
+
return epoll_create1(EPOLL_CLOEXEC);
|
|
1406
|
+
#else
|
|
1407
|
+
int fd = kqueue();
|
|
1408
|
+
if (fd >= 0) fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
1409
|
+
return fd;
|
|
1410
|
+
#endif
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/*
|
|
1414
|
+
* Bring the poller's interest for fd from old_what to new_what, where each is
|
|
1415
|
+
* a CURL_POLL_IN/OUT/INOUT mask and 0 means "not registered". Registration is
|
|
1416
|
+
* level-triggered so readiness left unconsumed is reported again. Errors are
|
|
1417
|
+
* ignored: the kernel drops closed descriptors from both epoll and kqueue on
|
|
1418
|
+
* its own, so a stale delete or a re-add after fd reuse is harmless.
|
|
1419
|
+
*/
|
|
1420
|
+
static void multi_socket_poller_update(multi_socket_ctx *ctx, int fd, int old_what, int new_what) {
|
|
1421
|
+
if (!ctx || ctx->poller_fd < 0 || old_what == new_what) return;
|
|
1422
|
+
#if defined(CURB_SOCKET_POLLER_EPOLL)
|
|
1423
|
+
{
|
|
1424
|
+
struct epoll_event ev;
|
|
1425
|
+
memset(&ev, 0, sizeof(ev));
|
|
1426
|
+
ev.data.fd = fd;
|
|
1427
|
+
if (new_what & CURL_POLL_IN) ev.events |= EPOLLIN;
|
|
1428
|
+
if (new_what & CURL_POLL_OUT) ev.events |= EPOLLOUT;
|
|
1429
|
+
|
|
1430
|
+
if (new_what == 0) {
|
|
1431
|
+
epoll_ctl(ctx->poller_fd, EPOLL_CTL_DEL, fd, &ev);
|
|
1432
|
+
} else if (old_what == 0) {
|
|
1433
|
+
if (epoll_ctl(ctx->poller_fd, EPOLL_CTL_ADD, fd, &ev) != 0 && errno == EEXIST) {
|
|
1434
|
+
epoll_ctl(ctx->poller_fd, EPOLL_CTL_MOD, fd, &ev);
|
|
1435
|
+
}
|
|
1436
|
+
} else if (epoll_ctl(ctx->poller_fd, EPOLL_CTL_MOD, fd, &ev) != 0 && errno == ENOENT) {
|
|
1437
|
+
epoll_ctl(ctx->poller_fd, EPOLL_CTL_ADD, fd, &ev);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
#else
|
|
1441
|
+
{
|
|
1442
|
+
/* kqueue tracks read and write as separate filters. Apply each change on
|
|
1443
|
+
* its own so one failing change cannot skip the others. */
|
|
1444
|
+
struct kevent change;
|
|
1445
|
+
int old_read = (old_what & CURL_POLL_IN) != 0;
|
|
1446
|
+
int new_read = (new_what & CURL_POLL_IN) != 0;
|
|
1447
|
+
int old_write = (old_what & CURL_POLL_OUT) != 0;
|
|
1448
|
+
int new_write = (new_what & CURL_POLL_OUT) != 0;
|
|
1449
|
+
|
|
1450
|
+
if (old_read != new_read) {
|
|
1451
|
+
EV_SET(&change, fd, EVFILT_READ, new_read ? EV_ADD : EV_DELETE, 0, 0, NULL);
|
|
1452
|
+
kevent(ctx->poller_fd, &change, 1, NULL, 0, NULL);
|
|
1453
|
+
}
|
|
1454
|
+
if (old_write != new_write) {
|
|
1455
|
+
EV_SET(&change, fd, EVFILT_WRITE, new_write ? EV_ADD : EV_DELETE, 0, 0, NULL);
|
|
1456
|
+
kevent(ctx->poller_fd, &change, 1, NULL, 0, NULL);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
#endif
|
|
1460
|
+
}
|
|
1461
|
+
#endif
|
|
1462
|
+
|
|
1297
1463
|
static void multi_socket_forget_fd(multi_socket_ctx *ctx, int fd) {
|
|
1298
1464
|
st_data_t key = (st_data_t)fd;
|
|
1299
1465
|
st_data_t rec;
|
|
1300
1466
|
|
|
1301
1467
|
if (!ctx) return;
|
|
1302
|
-
if (ctx->sock_map
|
|
1468
|
+
if (ctx->sock_map && st_delete(ctx->sock_map, &key, &rec)) {
|
|
1469
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1470
|
+
multi_socket_poller_update(ctx, fd, (int)rec, 0);
|
|
1471
|
+
#endif
|
|
1472
|
+
}
|
|
1303
1473
|
if (!NIL_P(ctx->io_cache)) rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1304
1474
|
}
|
|
1305
1475
|
|
|
@@ -1329,10 +1499,14 @@ static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, v
|
|
|
1329
1499
|
} else {
|
|
1330
1500
|
/* store current interest mask for this fd */
|
|
1331
1501
|
st_data_t key = (st_data_t)fd;
|
|
1332
|
-
st_data_t old_what;
|
|
1333
|
-
|
|
1502
|
+
st_data_t old_what = 0;
|
|
1503
|
+
int tracked = st_lookup(ctx->sock_map, key, &old_what);
|
|
1504
|
+
if (tracked && (int)old_what != what && !NIL_P(ctx->io_cache)) {
|
|
1334
1505
|
rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1335
1506
|
}
|
|
1507
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1508
|
+
multi_socket_poller_update(ctx, fd, tracked ? (int)old_what : 0, what);
|
|
1509
|
+
#endif
|
|
1336
1510
|
st_insert(ctx->sock_map, (st_data_t)fd, (st_data_t)what);
|
|
1337
1511
|
#if CURB_SOCKET_DEBUG
|
|
1338
1512
|
{
|
|
@@ -1412,13 +1586,6 @@ static int st_pick_one_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
|
1412
1586
|
s->found = 1;
|
|
1413
1587
|
return ST_STOP;
|
|
1414
1588
|
}
|
|
1415
|
-
struct counter_state { int count; };
|
|
1416
|
-
static int st_count_i(st_data_t k, st_data_t v, st_data_t argp) {
|
|
1417
|
-
(void)k; (void)v;
|
|
1418
|
-
struct counter_state *c = (struct counter_state *)argp;
|
|
1419
|
-
c->count++;
|
|
1420
|
-
return ST_CONTINUE;
|
|
1421
|
-
}
|
|
1422
1589
|
|
|
1423
1590
|
static const char *multi_socket_io_mode_for_curl_poll(int what) {
|
|
1424
1591
|
if (what == CURL_POLL_IN) return "r";
|
|
@@ -1443,6 +1610,112 @@ static VALUE multi_socket_io_for_fd_protected(VALUE argp) {
|
|
|
1443
1610
|
return multi_socket_io_for_fd(a->ctx, a->fd, a->what);
|
|
1444
1611
|
}
|
|
1445
1612
|
|
|
1613
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1614
|
+
static VALUE multi_socket_poller_io(VALUE self, multi_socket_ctx *ctx) {
|
|
1615
|
+
if (NIL_P(ctx->poller_io)) {
|
|
1616
|
+
VALUE io = rb_funcall(rb_cIO, rb_intern("for_fd"), 2, INT2NUM(ctx->poller_fd), rb_str_new_cstr("r"));
|
|
1617
|
+
/* The drive loop's ensure closes poller_fd itself. */
|
|
1618
|
+
rb_funcall(io, rb_intern("autoclose="), 1, Qfalse);
|
|
1619
|
+
ctx->poller_io = io;
|
|
1620
|
+
rb_ivar_set(self, id_socket_poller_io_ivar, io);
|
|
1621
|
+
}
|
|
1622
|
+
return ctx->poller_io;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/*
|
|
1626
|
+
* Report readiness the way select() does: an error or hangup marks the socket
|
|
1627
|
+
* ready for whatever libcurl is waiting on, and libcurl then observes the
|
|
1628
|
+
* condition from the read or write itself. (CURL_CSELECT_ERR would make it
|
|
1629
|
+
* fail the transfer outright, even with response data still buffered.)
|
|
1630
|
+
*/
|
|
1631
|
+
static int multi_socket_poller_flags(int ready_in, int ready_out, int failed, int what) {
|
|
1632
|
+
int flags = 0;
|
|
1633
|
+
|
|
1634
|
+
if (ready_in) flags |= CURL_CSELECT_IN;
|
|
1635
|
+
if (ready_out) flags |= CURL_CSELECT_OUT;
|
|
1636
|
+
if (failed) flags |= multi_socket_cselect_flags_for_curl_poll(what);
|
|
1637
|
+
|
|
1638
|
+
return flags;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
static void multi_socket_poller_act(ruby_curl_multi *rbcm, multi_socket_ctx *ctx, int fd, int ready_in, int ready_out, int failed, int *dispatched) {
|
|
1642
|
+
st_data_t what;
|
|
1643
|
+
int flags;
|
|
1644
|
+
CURLMcode mrc;
|
|
1645
|
+
|
|
1646
|
+
/* An earlier action in this batch may have made libcurl drop the socket. */
|
|
1647
|
+
if (!st_lookup(ctx->sock_map, (st_data_t)fd, &what)) return;
|
|
1648
|
+
|
|
1649
|
+
flags = multi_socket_poller_flags(ready_in, ready_out, failed, (int)what);
|
|
1650
|
+
if (!flags) return;
|
|
1651
|
+
|
|
1652
|
+
#if CURB_SOCKET_DEBUG
|
|
1653
|
+
{
|
|
1654
|
+
char b[32];
|
|
1655
|
+
curb_debugf("[curb.socket] poller socket_action fd=%d flags=%s", fd, cselect_flags_str(flags, b, sizeof(b)));
|
|
1656
|
+
}
|
|
1657
|
+
#endif
|
|
1658
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)fd, flags, &rbcm->running);
|
|
1659
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1660
|
+
(*dispatched)++;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
/* Collect pending poller events without blocking and hand each ready socket
|
|
1664
|
+
* to libcurl. Returns the number of socket actions performed. */
|
|
1665
|
+
static int multi_socket_poller_dispatch(ruby_curl_multi *rbcm, multi_socket_ctx *ctx) {
|
|
1666
|
+
int dispatched = 0;
|
|
1667
|
+
int rounds = 0;
|
|
1668
|
+
/* kqueue reports read and write readiness as separate events. */
|
|
1669
|
+
int max_rounds = (int)((ctx->sock_map->num_entries * 2) / CURB_SOCKET_POLLER_BATCH) + 1;
|
|
1670
|
+
int n, i;
|
|
1671
|
+
|
|
1672
|
+
do {
|
|
1673
|
+
#if defined(CURB_SOCKET_POLLER_EPOLL)
|
|
1674
|
+
struct epoll_event events[CURB_SOCKET_POLLER_BATCH];
|
|
1675
|
+
n = epoll_wait(ctx->poller_fd, events, CURB_SOCKET_POLLER_BATCH, 0);
|
|
1676
|
+
if (n < 0) {
|
|
1677
|
+
if (errno != EINTR) rb_sys_fail("epoll_wait");
|
|
1678
|
+
n = 0;
|
|
1679
|
+
}
|
|
1680
|
+
for (i = 0; i < n; i++) {
|
|
1681
|
+
uint32_t ev = events[i].events;
|
|
1682
|
+
multi_socket_poller_act(rbcm, ctx, events[i].data.fd,
|
|
1683
|
+
(ev & EPOLLIN) != 0, (ev & EPOLLOUT) != 0,
|
|
1684
|
+
(ev & (EPOLLERR | EPOLLHUP)) != 0, &dispatched);
|
|
1685
|
+
}
|
|
1686
|
+
#else
|
|
1687
|
+
struct kevent events[CURB_SOCKET_POLLER_BATCH];
|
|
1688
|
+
struct timespec no_wait = {0, 0};
|
|
1689
|
+
n = kevent(ctx->poller_fd, NULL, 0, events, CURB_SOCKET_POLLER_BATCH, &no_wait);
|
|
1690
|
+
if (n < 0) {
|
|
1691
|
+
if (errno != EINTR) rb_sys_fail("kevent");
|
|
1692
|
+
n = 0;
|
|
1693
|
+
}
|
|
1694
|
+
for (i = 0; i < n; i++) {
|
|
1695
|
+
if (events[i].flags & EV_ERROR) continue;
|
|
1696
|
+
multi_socket_poller_act(rbcm, ctx, (int)events[i].ident,
|
|
1697
|
+
events[i].filter == EVFILT_READ, events[i].filter == EVFILT_WRITE,
|
|
1698
|
+
0, &dispatched);
|
|
1699
|
+
}
|
|
1700
|
+
#endif
|
|
1701
|
+
} while (n == CURB_SOCKET_POLLER_BATCH && ++rounds < max_rounds);
|
|
1702
|
+
|
|
1703
|
+
return dispatched;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/* Wait for any tracked socket through the scheduler's io_wait hook on the
|
|
1707
|
+
* poller descriptor, then dispatch whatever became ready. */
|
|
1708
|
+
static int multi_socket_poller_wait(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE scheduler, struct timeval *tv) {
|
|
1709
|
+
VALUE io = multi_socket_poller_io(self, ctx);
|
|
1710
|
+
double timeout_s = (double)tv->tv_sec + ((double)tv->tv_usec / 1e6);
|
|
1711
|
+
|
|
1712
|
+
/* The hook's return value varies across schedulers; the non-blocking
|
|
1713
|
+
* dispatch below is the source of truth for what is ready. */
|
|
1714
|
+
curb_fiber_scheduler_io_wait(scheduler, io, INT2NUM(RB_WAITFD_IN), rb_float_new(timeout_s));
|
|
1715
|
+
return multi_socket_poller_dispatch(rbcm, ctx);
|
|
1716
|
+
}
|
|
1717
|
+
#endif
|
|
1718
|
+
|
|
1446
1719
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1447
1720
|
struct build_io_select_arrays_args {
|
|
1448
1721
|
multi_socket_ctx *ctx;
|
|
@@ -1526,7 +1799,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1526
1799
|
|
|
1527
1800
|
while (rbcm->running) {
|
|
1528
1801
|
struct timeval tv = {0, 0};
|
|
1529
|
-
long wait_ms =
|
|
1802
|
+
long wait_ms = curb_multi_default_timeout();
|
|
1530
1803
|
|
|
1531
1804
|
if (multi_socket_timer_due(ctx)) {
|
|
1532
1805
|
ctx->timeout_deadline_ms = -1;
|
|
@@ -1558,13 +1831,7 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1558
1831
|
if (st.found) { wait_fd = st.fd; wait_what = st.what; }
|
|
1559
1832
|
}
|
|
1560
1833
|
|
|
1561
|
-
|
|
1562
|
-
int count_tracked = 0;
|
|
1563
|
-
if (ctx->sock_map) {
|
|
1564
|
-
struct counter_state cs = { 0 };
|
|
1565
|
-
st_foreach(ctx->sock_map, st_count_i, (st_data_t)&cs);
|
|
1566
|
-
count_tracked = cs.count;
|
|
1567
|
-
}
|
|
1834
|
+
int count_tracked = ctx->sock_map ? (int)ctx->sock_map->num_entries : 0;
|
|
1568
1835
|
|
|
1569
1836
|
curb_debugf("[curb.socket] wait phase: tracked_fds=%d fd=%d what=%d tv=%ld.%06ld", count_tracked, wait_fd, wait_what, (long)tv.tv_sec, (long)tv.tv_usec);
|
|
1570
1837
|
|
|
@@ -1573,7 +1840,22 @@ static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_
|
|
|
1573
1840
|
int ready_flags = 0;
|
|
1574
1841
|
|
|
1575
1842
|
int handled_wait = 0;
|
|
1576
|
-
|
|
1843
|
+
#ifdef CURB_SOCKET_POLLER
|
|
1844
|
+
if (count_tracked > 0 && ctx->poller_fd >= 0) {
|
|
1845
|
+
VALUE scheduler = curb_fiber_scheduler_current();
|
|
1846
|
+
if (scheduler != Qnil) {
|
|
1847
|
+
/* Ready sockets are dispatched inside, so any_ready stays 0 and
|
|
1848
|
+
* the single-fd dispatch below is skipped. */
|
|
1849
|
+
int dispatched = multi_socket_poller_wait(self, rbcm, ctx, scheduler, &tv);
|
|
1850
|
+
curb_debugf("[curb.socket] poller wait dispatched=%d", dispatched);
|
|
1851
|
+
did_timeout = !dispatched && multi_socket_timer_due(ctx);
|
|
1852
|
+
handled_wait = 1;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
#endif
|
|
1856
|
+
if (handled_wait) {
|
|
1857
|
+
/* The poller already waited and dispatched. */
|
|
1858
|
+
} else if (count_tracked > 1) {
|
|
1577
1859
|
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1578
1860
|
{
|
|
1579
1861
|
VALUE scheduler = curb_fiber_scheduler_current();
|
|
@@ -1843,6 +2125,9 @@ static VALUE ruby_curl_multi_socket_drive_body(VALUE argp) {
|
|
|
1843
2125
|
return Qtrue;
|
|
1844
2126
|
}
|
|
1845
2127
|
struct socket_cleanup_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; };
|
|
2128
|
+
static VALUE multi_socket_close_io(VALUE io) {
|
|
2129
|
+
return rb_funcall(io, rb_intern("close"), 0);
|
|
2130
|
+
}
|
|
1846
2131
|
static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
1847
2132
|
struct socket_cleanup_args *c = (struct socket_cleanup_args *)argp;
|
|
1848
2133
|
if (c->rbcm && c->rbcm->handle) {
|
|
@@ -1860,10 +2145,27 @@ static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
|
1860
2145
|
rb_hash_clear(c->ctx->io_cache);
|
|
1861
2146
|
}
|
|
1862
2147
|
c->ctx->io_cache = Qnil;
|
|
2148
|
+
if (!NIL_P(c->ctx->poller_io)) {
|
|
2149
|
+
/* Let Ruby and the scheduler drop the wrapper before the descriptor
|
|
2150
|
+
* goes away. autoclose is off, so this leaves poller_fd open. Keep any
|
|
2151
|
+
* exception already propagating out of the drive loop. */
|
|
2152
|
+
VALUE errinfo = rb_errinfo();
|
|
2153
|
+
int state = 0;
|
|
2154
|
+
rb_protect(multi_socket_close_io, c->ctx->poller_io, &state);
|
|
2155
|
+
rb_set_errinfo(errinfo);
|
|
2156
|
+
c->ctx->poller_io = Qnil;
|
|
2157
|
+
}
|
|
2158
|
+
if (c->ctx->poller_fd >= 0) {
|
|
2159
|
+
close(c->ctx->poller_fd);
|
|
2160
|
+
c->ctx->poller_fd = -1;
|
|
2161
|
+
}
|
|
1863
2162
|
}
|
|
1864
2163
|
if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_io_cache_ivar)) {
|
|
1865
2164
|
rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_io_cache_ivar));
|
|
1866
2165
|
}
|
|
2166
|
+
if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_poller_io_ivar)) {
|
|
2167
|
+
rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_poller_io_ivar));
|
|
2168
|
+
}
|
|
1867
2169
|
return Qnil;
|
|
1868
2170
|
}
|
|
1869
2171
|
|
|
@@ -1882,7 +2184,17 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1882
2184
|
ctx.sock_map = st_init_numtable();
|
|
1883
2185
|
ctx.timeout_deadline_ms = -1;
|
|
1884
2186
|
ctx.io_cache = rb_hash_new();
|
|
2187
|
+
ctx.poller_fd = -1;
|
|
2188
|
+
ctx.poller_io = Qnil;
|
|
1885
2189
|
rb_ivar_set(self, id_socket_io_cache_ivar, ctx.io_cache);
|
|
2190
|
+
#ifdef CURB_SOCKET_POLLER
|
|
2191
|
+
/* Only fiber-scheduler waits use the poller; thread-level waits keep using
|
|
2192
|
+
* rb_thread_fd_select. If it cannot be created the loop falls back to the
|
|
2193
|
+
* io_select/io_wait paths. */
|
|
2194
|
+
if (curb_fiber_scheduler_current() != Qnil) {
|
|
2195
|
+
ctx.poller_fd = multi_socket_poller_create();
|
|
2196
|
+
}
|
|
2197
|
+
#endif
|
|
1886
2198
|
|
|
1887
2199
|
/* install socket/timer callbacks */
|
|
1888
2200
|
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
|
|
@@ -1896,7 +2208,7 @@ static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE se
|
|
|
1896
2208
|
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
|
1897
2209
|
|
|
1898
2210
|
/* finalize */
|
|
1899
|
-
if (
|
|
2211
|
+
if (curb_multi_autoclose_enabled()) {
|
|
1900
2212
|
rbcm->allow_close_during_perform = 1;
|
|
1901
2213
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
1902
2214
|
rbcm->allow_close_during_perform = 0;
|
|
@@ -1976,17 +2288,19 @@ static void *curb_select_without_gvl(void *args) {
|
|
|
1976
2288
|
static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
1977
2289
|
CURLMcode mcode;
|
|
1978
2290
|
ruby_curl_multi *rbcm;
|
|
2291
|
+
long timeout_milliseconds;
|
|
2292
|
+
VALUE block = Qnil;
|
|
2293
|
+
#ifndef CURB_USE_MULTI_POLL
|
|
1979
2294
|
int maxfd, rc = -1;
|
|
1980
2295
|
fd_set fdread, fdwrite, fdexcep;
|
|
1981
2296
|
#ifdef _WIN32
|
|
1982
2297
|
fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
|
|
1983
2298
|
#endif
|
|
1984
|
-
long timeout_milliseconds;
|
|
1985
2299
|
struct timeval tv = {0, 0};
|
|
1986
2300
|
struct timeval tv_100ms = {0, 100000};
|
|
1987
|
-
VALUE block = Qnil;
|
|
1988
2301
|
#if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
|
|
1989
2302
|
struct _select_set fdset_args;
|
|
2303
|
+
#endif
|
|
1990
2304
|
#endif
|
|
1991
2305
|
|
|
1992
2306
|
rb_scan_args(argc, argv, "0&", &block);
|
|
@@ -1997,7 +2311,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
1997
2311
|
clear_multi_deferred_exception_source_id_if_any(self);
|
|
1998
2312
|
}
|
|
1999
2313
|
|
|
2000
|
-
timeout_milliseconds =
|
|
2314
|
+
timeout_milliseconds = curb_multi_default_timeout();
|
|
2001
2315
|
|
|
2002
2316
|
// Run curl_multi_perform for the first time to get the ball rolling
|
|
2003
2317
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
@@ -2037,12 +2351,36 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2037
2351
|
continue;
|
|
2038
2352
|
}
|
|
2039
2353
|
|
|
2040
|
-
if (timeout_milliseconds < 0 || timeout_milliseconds >
|
|
2041
|
-
timeout_milliseconds =
|
|
2354
|
+
if (timeout_milliseconds < 0 || timeout_milliseconds > curb_multi_default_timeout()) {
|
|
2355
|
+
timeout_milliseconds = curb_multi_default_timeout(); /* libcurl doesn't know how long to wait, use a default timeout */
|
|
2042
2356
|
/* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
|
|
2043
2357
|
}
|
|
2044
2358
|
|
|
2045
|
-
#if defined(
|
|
2359
|
+
#if defined(CURB_USE_MULTI_POLL)
|
|
2360
|
+
{
|
|
2361
|
+
struct multi_poll_args poll_args;
|
|
2362
|
+
poll_args.handle = rbcm->handle;
|
|
2363
|
+
poll_args.timeout_ms = timeout_milliseconds > INT_MAX ? INT_MAX : (int)timeout_milliseconds;
|
|
2364
|
+
poll_args.numfds = 0;
|
|
2365
|
+
poll_args.code = CURLM_OK;
|
|
2366
|
+
/*
|
|
2367
|
+
* Wait with the GVL released. curl_multi_poll sleeps for the full
|
|
2368
|
+
* timeout even when libcurl has no sockets to watch, so no separate
|
|
2369
|
+
* idle sleep is needed. Pending interrupts are checked when
|
|
2370
|
+
* rb_thread_call_without_gvl returns. Like rb_thread_fd_select, this
|
|
2371
|
+
* wait does not consult the fiber scheduler; perform routes to the
|
|
2372
|
+
* socket-action loop when a scheduler is active.
|
|
2373
|
+
*/
|
|
2374
|
+
rb_thread_call_without_gvl(curb_multi_poll_without_gvl, &poll_args,
|
|
2375
|
+
curb_multi_poll_ubf, rbcm->handle);
|
|
2376
|
+
if (poll_args.code != CURLM_OK) {
|
|
2377
|
+
raise_curl_multi_error_exception(poll_args.code);
|
|
2378
|
+
}
|
|
2379
|
+
rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
|
|
2380
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
2381
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
2382
|
+
}
|
|
2383
|
+
#elif defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT)
|
|
2046
2384
|
{
|
|
2047
2385
|
struct wait_args wait_args;
|
|
2048
2386
|
wait_args.handle = rbcm->handle;
|
|
@@ -2076,8 +2414,10 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2076
2414
|
}
|
|
2077
2415
|
#else
|
|
2078
2416
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2417
|
+
/* Split into seconds and microseconds: a raw select() rejects a
|
|
2418
|
+
* tv_usec of 1000000 or more, which default_timeout > 1000 produced. */
|
|
2419
|
+
tv.tv_sec = timeout_milliseconds / 1000;
|
|
2420
|
+
tv.tv_usec = (timeout_milliseconds % 1000) * 1000;
|
|
2081
2421
|
|
|
2082
2422
|
FD_ZERO(&fdread);
|
|
2083
2423
|
FD_ZERO(&fdwrite);
|
|
@@ -2177,7 +2517,7 @@ static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
|
2177
2517
|
rb_curl_multi_yield_if_given(self, block);
|
|
2178
2518
|
} while( rbcm->running );
|
|
2179
2519
|
|
|
2180
|
-
if (
|
|
2520
|
+
if (curb_multi_autoclose_enabled()) {
|
|
2181
2521
|
rbcm->allow_close_during_perform = 1;
|
|
2182
2522
|
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
2183
2523
|
rbcm->allow_close_during_perform = 0;
|
|
@@ -2203,6 +2543,7 @@ static VALUE ruby_curl_multi_perform_guard_ensure(VALUE self) {
|
|
|
2203
2543
|
ruby_curl_multi *rbcm;
|
|
2204
2544
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2205
2545
|
rbcm->perform_active = 0;
|
|
2546
|
+
rbcm->perform_thread = Qnil;
|
|
2206
2547
|
rbcm->callback_active = 0;
|
|
2207
2548
|
rbcm->allow_close_during_perform = 0;
|
|
2208
2549
|
return Qnil;
|
|
@@ -2217,7 +2558,11 @@ static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE sel
|
|
|
2217
2558
|
rb_raise(rb_eRuntimeError, "Cannot recursively perform an active Curl::Multi handle");
|
|
2218
2559
|
}
|
|
2219
2560
|
|
|
2561
|
+
/* Reopening an autoclosed multi initializes its guard fields. Do this before
|
|
2562
|
+
* claiming ownership so repeated performs retain the full-lifetime guard. */
|
|
2563
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
2220
2564
|
rbcm->perform_active = 1;
|
|
2565
|
+
rbcm->perform_thread = rb_thread_current();
|
|
2221
2566
|
args.argc = argc;
|
|
2222
2567
|
args.argv = argv;
|
|
2223
2568
|
args.self = self;
|
|
@@ -2244,6 +2589,19 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
2244
2589
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
|
|
2245
2590
|
}
|
|
2246
2591
|
|
|
2592
|
+
/*
|
|
2593
|
+
* Name of the kernel event queue the socket-action loop waits on under a
|
|
2594
|
+
* fiber scheduler ("epoll" or "kqueue"), or nil when this build has none.
|
|
2595
|
+
*/
|
|
2596
|
+
static VALUE ruby_curl_multi_socket_poller(VALUE klass) {
|
|
2597
|
+
(void)klass;
|
|
2598
|
+
#ifdef CURB_SOCKET_POLLER
|
|
2599
|
+
return rb_str_new_cstr(CURB_SOCKET_POLLER_NAME);
|
|
2600
|
+
#else
|
|
2601
|
+
return Qnil;
|
|
2602
|
+
#endif
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2247
2605
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2248
2606
|
VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
|
|
2249
2607
|
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
|
|
@@ -2261,6 +2619,7 @@ VALUE ruby_curl_multi_close(VALUE self) {
|
|
|
2261
2619
|
ruby_curl_multi *rbcm;
|
|
2262
2620
|
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2263
2621
|
|
|
2622
|
+
ruby_curl_multi_check_thread(rbcm);
|
|
2264
2623
|
if ((rbcm->perform_active || rbcm->callback_active) && !rbcm->allow_close_during_perform) {
|
|
2265
2624
|
rb_raise(rb_eRuntimeError, "Cannot close an active Curl::Multi handle during perform");
|
|
2266
2625
|
}
|
|
@@ -2298,6 +2657,7 @@ static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
|
2298
2657
|
static void curl_multi_mark(void *ptr) {
|
|
2299
2658
|
ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
|
|
2300
2659
|
if (!rbcm) return;
|
|
2660
|
+
rb_gc_mark(rbcm->perform_thread);
|
|
2301
2661
|
if (rbcm->attached) {
|
|
2302
2662
|
st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0);
|
|
2303
2663
|
}
|
|
@@ -2309,6 +2669,20 @@ void init_curb_multi() {
|
|
|
2309
2669
|
idCall = rb_intern("call");
|
|
2310
2670
|
cRubyFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
|
2311
2671
|
rb_global_variable(&cRubyFiber);
|
|
2672
|
+
cRubyRactor = Qnil;
|
|
2673
|
+
id_ractor_current = rb_intern("current");
|
|
2674
|
+
id_ractor_aref = rb_intern("[]");
|
|
2675
|
+
id_ractor_aset = rb_intern("[]=");
|
|
2676
|
+
id_ractor_default_timeout_key = rb_intern("__curb_multi_default_timeout");
|
|
2677
|
+
id_ractor_autoclose_key = rb_intern("__curb_multi_autoclose");
|
|
2678
|
+
if (rb_const_defined(rb_cObject, rb_intern("Ractor"))) {
|
|
2679
|
+
VALUE ractor = rb_const_get(rb_cObject, rb_intern("Ractor"));
|
|
2680
|
+
VALUE current = rb_funcall(ractor, id_ractor_current, 0);
|
|
2681
|
+
if (rb_respond_to(current, id_ractor_aref) && rb_respond_to(current, id_ractor_aset)) {
|
|
2682
|
+
cRubyRactor = ractor;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
rb_global_variable(&cRubyRactor);
|
|
2312
2686
|
id_fiber_blocking_p = rb_intern("blocking?");
|
|
2313
2687
|
id_fiber_scheduler = rb_intern("scheduler");
|
|
2314
2688
|
id_kernel_sleep = rb_intern("kernel_sleep");
|
|
@@ -2317,6 +2691,7 @@ void init_curb_multi() {
|
|
|
2317
2691
|
id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id");
|
|
2318
2692
|
id_native_safety_signatures_ivar = rb_intern("@__curb_native_safety_signatures");
|
|
2319
2693
|
id_socket_io_cache_ivar = rb_intern("@__curb_socket_io_cache");
|
|
2694
|
+
id_socket_poller_io_ivar = rb_intern("@__curb_socket_poller_io");
|
|
2320
2695
|
cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
|
|
2321
2696
|
|
|
2322
2697
|
rb_define_alloc_func(cCurlMulti, ruby_curl_multi_alloc);
|
|
@@ -2343,6 +2718,7 @@ void init_curb_multi() {
|
|
|
2343
2718
|
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2344
2719
|
rb_define_private_method(cCurlMulti, "_socket_perform", ruby_curl_multi_socket_perform, -1);
|
|
2345
2720
|
#endif
|
|
2721
|
+
rb_define_private_method(rb_singleton_class(cCurlMulti), "_socket_poller", ruby_curl_multi_socket_poller, 0);
|
|
2346
2722
|
rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
|
|
2347
2723
|
rb_define_private_method(cCurlMulti, "_mark_closed", ruby_curl_multi_mark_closed, 0);
|
|
2348
2724
|
}
|