curb 0.8.4 → 1.3.5
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 +7 -0
- data/README.md +522 -0
- data/Rakefile +79 -26
- data/ext/banned.h +32 -0
- data/ext/curb.c +549 -230
- data/ext/curb.h +19 -10
- data/ext/curb_easy.c +1935 -366
- data/ext/curb_easy.h +16 -0
- data/ext/curb_errors.c +115 -18
- data/ext/curb_errors.h +8 -5
- data/ext/curb_macros.h +33 -21
- data/ext/curb_multi.c +1858 -272
- data/ext/curb_multi.h +10 -3
- data/ext/curb_postfield.c +149 -77
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +38 -11
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +353 -35
- data/lib/curb.rb +1 -0
- data/lib/curl/easy.rb +314 -78
- data/lib/curl/multi.rb +134 -28
- data/lib/curl.rb +217 -11
- data/tests/bug_crash_on_debug.rb +14 -28
- data/tests/bug_crash_on_progress.rb +32 -16
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
- data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
- data/tests/bug_follow_redirect_288.rb +83 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
- data/tests/bug_issue102.rb +1 -1
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/bug_multi_segfault.rb +1 -0
- data/tests/bug_raise_on_callback.rb +30 -0
- data/tests/helper.rb +400 -44
- data/tests/leak_trace.rb +237 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl.rb +31 -1
- data/tests/tc_curl_download.rb +12 -7
- data/tests/tc_curl_easy.rb +911 -63
- data/tests/tc_curl_easy_cookielist.rb +277 -0
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_curl_easy_resolve.rb +48 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +855 -53
- data/tests/tc_curl_native_coverage.rb +145 -0
- data/tests/tc_curl_postfield.rb +207 -30
- data/tests/tc_curl_protocols.rb +37 -0
- data/tests/tc_fiber_scheduler.rb +543 -0
- data/tests/tc_ftp_options.rb +39 -0
- data/tests/tc_gc_compact.rb +223 -0
- data/tests/tc_test_server_methods.rb +110 -0
- data/tests/timeout.rb +30 -6
- metadata +59 -36
- data/README +0 -194
data/ext/curb_multi.c
CHANGED
|
@@ -4,27 +4,77 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
#include "curb_config.h"
|
|
7
|
-
#
|
|
8
|
-
|
|
7
|
+
#include <ruby.h>
|
|
8
|
+
#ifdef HAVE_RUBY_IO_H
|
|
9
|
+
#include <ruby/io.h>
|
|
10
|
+
#endif
|
|
11
|
+
#ifdef HAVE_RUBY_ST_H
|
|
9
12
|
#include <ruby/st.h>
|
|
10
13
|
#else
|
|
11
|
-
#include <ruby.h>
|
|
12
14
|
#include <st.h>
|
|
13
15
|
#endif
|
|
16
|
+
|
|
17
|
+
#ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
|
|
18
|
+
#include <ruby/thread.h>
|
|
19
|
+
#endif
|
|
20
|
+
#ifdef HAVE_RUBY_FIBER_SCHEDULER_H
|
|
21
|
+
#include <ruby/fiber/scheduler.h>
|
|
22
|
+
#endif
|
|
23
|
+
|
|
14
24
|
#include "curb_easy.h"
|
|
15
25
|
#include "curb_errors.h"
|
|
16
26
|
#include "curb_postfield.h"
|
|
17
27
|
#include "curb_multi.h"
|
|
18
28
|
|
|
19
29
|
#include <errno.h>
|
|
30
|
+
#include <fcntl.h>
|
|
31
|
+
#ifndef _WIN32
|
|
32
|
+
#include <sys/time.h>
|
|
33
|
+
#include <time.h>
|
|
34
|
+
#endif
|
|
35
|
+
#include <stdint.h>
|
|
36
|
+
#include <stdarg.h>
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* Optional socket-action debug logging. Enabled by defining CURB_SOCKET_DEBUG=1
|
|
40
|
+
* at compile time (e.g. via environment variable passed to extconf.rb).
|
|
41
|
+
*/
|
|
42
|
+
#ifndef CURB_SOCKET_DEBUG
|
|
43
|
+
#define CURB_SOCKET_DEBUG 0
|
|
44
|
+
#endif
|
|
45
|
+
#if !CURB_SOCKET_DEBUG
|
|
46
|
+
#define curb_debugf(...) ((void)0)
|
|
47
|
+
#endif
|
|
48
|
+
|
|
49
|
+
#ifdef RBIMPL_ATTR_MAYBE_UNUSED
|
|
50
|
+
#define CURB_MAYBE_UNUSED_DECL RBIMPL_ATTR_MAYBE_UNUSED()
|
|
51
|
+
#else
|
|
52
|
+
#define CURB_MAYBE_UNUSED_DECL
|
|
53
|
+
#endif
|
|
20
54
|
|
|
21
55
|
#ifdef _WIN32
|
|
22
56
|
// for O_RDWR and O_BINARY
|
|
23
57
|
#include <fcntl.h>
|
|
24
58
|
#endif
|
|
25
59
|
|
|
60
|
+
#if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT)
|
|
61
|
+
struct wait_args {
|
|
62
|
+
CURLM *handle;
|
|
63
|
+
long timeout_ms;
|
|
64
|
+
int numfds;
|
|
65
|
+
};
|
|
66
|
+
static void *curl_multi_wait_wrapper(void *p) {
|
|
67
|
+
struct wait_args *args = p;
|
|
68
|
+
CURLMcode code = curl_multi_wait(args->handle, NULL, 0, args->timeout_ms, &args->numfds);
|
|
69
|
+
return (void *)(intptr_t)code;
|
|
70
|
+
}
|
|
71
|
+
#endif
|
|
72
|
+
|
|
26
73
|
extern VALUE mCurl;
|
|
27
74
|
static VALUE idCall;
|
|
75
|
+
static ID id_deferred_exception_ivar;
|
|
76
|
+
static ID id_deferred_exception_source_id_ivar;
|
|
77
|
+
static ID id_socket_io_cache_ivar;
|
|
28
78
|
|
|
29
79
|
#ifdef RDOC_NEVER_DEFINED
|
|
30
80
|
mCurl = rb_define_module("Curl");
|
|
@@ -33,72 +83,353 @@ static VALUE idCall;
|
|
|
33
83
|
VALUE cCurlMulti;
|
|
34
84
|
|
|
35
85
|
static long cCurlMutiDefaulttimeout = 100; /* milliseconds */
|
|
86
|
+
static char cCurlMutiAutoClose = 0;
|
|
36
87
|
|
|
88
|
+
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result);
|
|
37
89
|
static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy);
|
|
38
90
|
static void rb_curl_multi_read_info(VALUE self, CURLM *mptr);
|
|
39
91
|
static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running);
|
|
40
92
|
|
|
41
|
-
static
|
|
42
|
-
|
|
43
|
-
|
|
93
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg);
|
|
94
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm);
|
|
95
|
+
static void curl_multi_mark(void *ptr);
|
|
96
|
+
static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm);
|
|
97
|
+
static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce);
|
|
98
|
+
static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy);
|
|
99
|
+
static VALUE ruby_curl_multi_mark_closed(VALUE self);
|
|
100
|
+
static VALUE ruby_curl_multi_alloc(VALUE klass);
|
|
101
|
+
static VALUE ruby_curl_multi_initialize(VALUE self);
|
|
102
|
+
static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self);
|
|
103
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
104
|
+
static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self);
|
|
105
|
+
#endif
|
|
106
|
+
|
|
107
|
+
static ruby_curl_multi *ruby_curl_multi_pointer_if_compatible(VALUE multi_val) {
|
|
108
|
+
if (NIL_P(multi_val) || !RB_TYPE_P(multi_val, T_DATA)) {
|
|
109
|
+
return NULL;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#if defined(RTYPEDDATA_P) && defined(RTYPEDDATA_TYPE) && defined(RTYPEDDATA_DATA)
|
|
113
|
+
if (!RTYPEDDATA_P(multi_val)) {
|
|
114
|
+
return NULL;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (RTYPEDDATA_TYPE(multi_val) != &ruby_curl_multi_data_type) {
|
|
118
|
+
return NULL;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (ruby_curl_multi *)RTYPEDDATA_DATA(multi_val);
|
|
122
|
+
#else
|
|
123
|
+
if (!rb_typeddata_is_kind_of(multi_val, &ruby_curl_multi_data_type)) {
|
|
124
|
+
return NULL;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return DATA_PTR(multi_val);
|
|
128
|
+
#endif
|
|
129
|
+
}
|
|
44
130
|
|
|
45
|
-
static
|
|
46
|
-
|
|
131
|
+
static VALUE callback_exception(VALUE did_raise, VALUE exception) {
|
|
132
|
+
// TODO: we could have an option to enable exception reporting
|
|
133
|
+
/* VALUE ret = rb_funcall(exception, rb_intern("message"), 0);
|
|
134
|
+
VALUE trace = rb_funcall(exception, rb_intern("backtrace"), 0);
|
|
135
|
+
if (RB_TYPE_P(trace, T_ARRAY) && RARRAY_LEN(trace) > 0) {
|
|
136
|
+
printf("we got an exception: %s:%d\n", StringValueCStr(ret), RARRAY_LEN(trace));
|
|
137
|
+
VALUE sep = rb_str_new_cstr("\n");
|
|
138
|
+
VALUE trace_lines = rb_ary_join(trace, sep);
|
|
139
|
+
if (RB_TYPE_P(trace_lines, T_STRING)) {
|
|
140
|
+
printf("%s\n", StringValueCStr(trace_lines));
|
|
141
|
+
} else {
|
|
142
|
+
printf("trace is not a string??\n");
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
printf("we got an exception: %s\nno stack available\n", StringValueCStr(ret));
|
|
146
|
+
}
|
|
147
|
+
*/
|
|
148
|
+
rb_hash_aset(did_raise, rb_easy_hkey("error"), exception);
|
|
149
|
+
return exception;
|
|
47
150
|
}
|
|
48
151
|
|
|
49
|
-
static
|
|
50
|
-
|
|
51
|
-
ruby_curl_easy *rbce;
|
|
152
|
+
static VALUE take_easy_callback_error_if_any(VALUE easy) {
|
|
153
|
+
ruby_curl_easy *rbce = NULL;
|
|
52
154
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (result != 0) {
|
|
56
|
-
raise_curl_multi_error_exception(result);
|
|
155
|
+
if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA)) {
|
|
156
|
+
return Qnil;
|
|
57
157
|
}
|
|
158
|
+
|
|
159
|
+
TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
|
|
160
|
+
return rb_curl_easy_take_callback_error(rbce);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static VALUE build_aborted_by_callback_exception(VALUE exception) {
|
|
164
|
+
VALUE message = rb_funcall(exception, rb_intern("message"), 0);
|
|
165
|
+
VALUE aborted_exception = rb_exc_new_str(eCurlErrAbortedByCallback, message);
|
|
166
|
+
VALUE backtrace = rb_funcall(exception, rb_intern("backtrace"), 0);
|
|
167
|
+
rb_funcall(aborted_exception, rb_intern("set_backtrace"), 1, backtrace);
|
|
168
|
+
return aborted_exception;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static void stash_multi_exception_if_unset(VALUE self, VALUE exception, VALUE source_easy) {
|
|
172
|
+
if (rb_ivar_defined(self, id_deferred_exception_ivar)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
rb_ivar_set(self, id_deferred_exception_ivar, exception);
|
|
177
|
+
if (!NIL_P(source_easy)) {
|
|
178
|
+
rb_ivar_set(self, id_deferred_exception_source_id_ivar, rb_obj_id(source_easy));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static void clear_multi_deferred_exception_source_id_if_any(VALUE self) {
|
|
183
|
+
if (rb_ivar_defined(self, id_deferred_exception_source_id_ivar)) {
|
|
184
|
+
rb_funcall(self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_deferred_exception_source_id_ivar));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static VALUE clear_multi_deferred_exception_if_any(VALUE self) {
|
|
189
|
+
VALUE exception = Qnil;
|
|
190
|
+
|
|
191
|
+
if (rb_ivar_defined(self, id_deferred_exception_ivar)) {
|
|
192
|
+
exception = rb_funcall(self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_deferred_exception_ivar));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return exception;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static void rb_curl_multi_yield_if_given(VALUE self, VALUE block) {
|
|
199
|
+
if (block == Qnil) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
rb_funcall(block, idCall, 1, self);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static void raise_multi_deferred_exception_if_idle(VALUE self) {
|
|
207
|
+
ruby_curl_multi *rbcm = NULL;
|
|
208
|
+
|
|
209
|
+
if (!rb_ivar_defined(self, id_deferred_exception_ivar)) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
214
|
+
if (rbcm && rbcm->active > 0) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
VALUE exception = clear_multi_deferred_exception_if_any(self);
|
|
219
|
+
rb_exc_raise(exception);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static VALUE take_status_callback_exception_if_any(VALUE did_raise) {
|
|
223
|
+
VALUE exception = rb_hash_aref(did_raise, rb_easy_hkey("error"));
|
|
224
|
+
|
|
225
|
+
if (FIX2INT(rb_hash_size(did_raise)) > 0 && exception != Qnil) {
|
|
226
|
+
rb_hash_clear(did_raise);
|
|
227
|
+
return build_aborted_by_callback_exception(exception);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return Qnil;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static void raise_completion_callback_error_if_any(VALUE did_raise, VALUE easy_callback_error) {
|
|
234
|
+
if (!NIL_P(easy_callback_error)) {
|
|
235
|
+
rb_exc_raise(easy_callback_error);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
VALUE exception = take_status_callback_exception_if_any(did_raise);
|
|
239
|
+
if (!NIL_P(exception)) {
|
|
240
|
+
rb_exc_raise(exception);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
struct multi_handle_complete_args {
|
|
245
|
+
VALUE self;
|
|
246
|
+
CURL *easy_handle;
|
|
247
|
+
int result;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
static VALUE rb_curl_mutli_handle_complete_protected(VALUE argp) {
|
|
251
|
+
struct multi_handle_complete_args *args = (struct multi_handle_complete_args *)argp;
|
|
252
|
+
rb_curl_mutli_handle_complete(args->self, args->easy_handle, args->result);
|
|
253
|
+
return Qnil;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
static int detach_easy_entry(st_data_t key, st_data_t val, st_data_t arg) {
|
|
257
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)arg;
|
|
258
|
+
VALUE easy = (VALUE)val;
|
|
259
|
+
ruby_curl_easy *rbce = NULL;
|
|
260
|
+
|
|
261
|
+
if (RB_TYPE_P(easy, T_DATA)) {
|
|
262
|
+
TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!rbce) {
|
|
266
|
+
return ST_CONTINUE;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (rbcm && rbcm->handle && rbce->curl) {
|
|
270
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
rbce->multi = Qnil;
|
|
274
|
+
|
|
275
|
+
return ST_CONTINUE;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr) {
|
|
279
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)rbce_ptr;
|
|
280
|
+
|
|
281
|
+
if (!rbcm || !rbce || !rbcm->attached) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
st_data_t key = (st_data_t)rbce;
|
|
286
|
+
st_delete(rbcm->attached, &key, NULL);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static void rb_curl_multi_detach_all(ruby_curl_multi *rbcm) {
|
|
290
|
+
if (!rbcm || !rbcm->attached) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
st_table *attached = rbcm->attached;
|
|
295
|
+
rbcm->attached = NULL;
|
|
296
|
+
|
|
297
|
+
st_foreach(attached, detach_easy_entry, (st_data_t)rbcm);
|
|
298
|
+
|
|
299
|
+
st_free_table(attached);
|
|
300
|
+
|
|
301
|
+
rbcm->active = 0;
|
|
302
|
+
rbcm->running = 0;
|
|
58
303
|
}
|
|
59
304
|
|
|
60
|
-
static int
|
|
61
|
-
|
|
62
|
-
|
|
305
|
+
static int rb_curl_multi_has_easy(ruby_curl_multi *rbcm, ruby_curl_easy *rbce) {
|
|
306
|
+
st_data_t value = 0;
|
|
307
|
+
|
|
308
|
+
if (!rbcm || !rbce || !rbcm->attached) {
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return st_lookup(rbcm->attached, (st_data_t)rbce, &value);
|
|
63
313
|
}
|
|
64
314
|
|
|
65
|
-
static void
|
|
315
|
+
static void rb_curl_multi_remove_request_reference(VALUE self, VALUE easy) {
|
|
316
|
+
VALUE requests;
|
|
317
|
+
|
|
318
|
+
if (NIL_P(self) || NIL_P(easy)) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
requests = rb_funcall(self, rb_intern("requests"), 0);
|
|
323
|
+
if (!RB_TYPE_P(requests, T_HASH)) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
rb_hash_delete(requests, rb_obj_id(easy));
|
|
328
|
+
}
|
|
66
329
|
|
|
67
|
-
|
|
330
|
+
/* TypedData-compatible free function */
|
|
331
|
+
static void curl_multi_free(void *ptr) {
|
|
332
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
|
|
333
|
+
if (!rbcm) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
68
336
|
|
|
69
|
-
|
|
337
|
+
rb_curl_multi_detach_all(rbcm);
|
|
70
338
|
|
|
71
|
-
|
|
72
|
-
rbcm->
|
|
339
|
+
if (rbcm->handle) {
|
|
340
|
+
curl_multi_cleanup(rbcm->handle);
|
|
341
|
+
rbcm->handle = NULL;
|
|
73
342
|
}
|
|
74
|
-
|
|
343
|
+
|
|
75
344
|
free(rbcm);
|
|
76
345
|
}
|
|
77
346
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
* Create a new Curl::Multi instance
|
|
83
|
-
*/
|
|
84
|
-
VALUE ruby_curl_multi_new(VALUE klass) {
|
|
85
|
-
VALUE new_curlm;
|
|
347
|
+
static size_t curl_multi_memsize(const void *ptr) {
|
|
348
|
+
(void)ptr;
|
|
349
|
+
return sizeof(ruby_curl_multi);
|
|
350
|
+
}
|
|
86
351
|
|
|
87
|
-
|
|
352
|
+
const rb_data_type_t ruby_curl_multi_data_type = {
|
|
353
|
+
"Curl::Multi",
|
|
354
|
+
{
|
|
355
|
+
curl_multi_mark,
|
|
356
|
+
curl_multi_free,
|
|
357
|
+
curl_multi_memsize,
|
|
358
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
|
359
|
+
NULL, /* compact */
|
|
360
|
+
#endif
|
|
361
|
+
},
|
|
362
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
|
363
|
+
NULL, NULL, /* parent, data */
|
|
364
|
+
RUBY_TYPED_FREE_IMMEDIATELY
|
|
365
|
+
#endif
|
|
366
|
+
};
|
|
88
367
|
|
|
368
|
+
static void ruby_curl_multi_init(ruby_curl_multi *rbcm) {
|
|
89
369
|
rbcm->handle = curl_multi_init();
|
|
90
370
|
if (!rbcm->handle) {
|
|
91
371
|
rb_raise(mCurlErrFailedInit, "Failed to initialize multi handle");
|
|
92
372
|
}
|
|
93
373
|
|
|
94
|
-
rbcm->requests = rb_hash_new();
|
|
95
|
-
|
|
96
374
|
rbcm->active = 0;
|
|
97
375
|
rbcm->running = 0;
|
|
376
|
+
rbcm->closed = 0;
|
|
377
|
+
rbcm->perform_active = 0;
|
|
378
|
+
rbcm->callback_active = 0;
|
|
379
|
+
rbcm->allow_close_during_perform = 0;
|
|
380
|
+
|
|
381
|
+
if (rbcm->attached) {
|
|
382
|
+
st_free_table(rbcm->attached);
|
|
383
|
+
rbcm->attached = NULL;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
rbcm->attached = st_init_numtable();
|
|
387
|
+
if (!rbcm->attached) {
|
|
388
|
+
curl_multi_cleanup(rbcm->handle);
|
|
389
|
+
rbcm->handle = NULL;
|
|
390
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
static void ruby_curl_multi_ensure_handle(ruby_curl_multi *rbcm) {
|
|
395
|
+
if (!rbcm->handle) {
|
|
396
|
+
if (rbcm->closed) {
|
|
397
|
+
raise_curl_multi_error_exception(CURLM_BAD_HANDLE);
|
|
398
|
+
}
|
|
399
|
+
ruby_curl_multi_init(rbcm);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/*
|
|
404
|
+
* call-seq:
|
|
405
|
+
* Curl::Multi.new => #<Curl::Easy...>
|
|
406
|
+
*
|
|
407
|
+
* Create a new Curl::Multi instance
|
|
408
|
+
*/
|
|
409
|
+
static VALUE ruby_curl_multi_alloc(VALUE klass) {
|
|
410
|
+
VALUE self;
|
|
411
|
+
ruby_curl_multi *rbcm;
|
|
98
412
|
|
|
99
|
-
|
|
413
|
+
self = TypedData_Make_Struct(klass, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
414
|
+
MEMZERO(rbcm, ruby_curl_multi, 1);
|
|
100
415
|
|
|
101
|
-
return
|
|
416
|
+
return self;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
static VALUE ruby_curl_multi_initialize(VALUE self) {
|
|
420
|
+
ruby_curl_multi *rbcm;
|
|
421
|
+
|
|
422
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
423
|
+
|
|
424
|
+
ruby_curl_multi_init(rbcm);
|
|
425
|
+
|
|
426
|
+
/*
|
|
427
|
+
* The mark routine will be called by the garbage collector during its ``mark'' phase.
|
|
428
|
+
* If your structure references other Ruby objects, then your mark function needs to
|
|
429
|
+
* identify these objects using rb_gc_mark(value). If the structure doesn't reference
|
|
430
|
+
* other Ruby objects, you can simply pass 0 as a function pointer.
|
|
431
|
+
*/
|
|
432
|
+
return self;
|
|
102
433
|
}
|
|
103
434
|
|
|
104
435
|
/*
|
|
@@ -110,7 +441,7 @@ VALUE ruby_curl_multi_new(VALUE klass) {
|
|
|
110
441
|
*
|
|
111
442
|
*/
|
|
112
443
|
VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
113
|
-
cCurlMutiDefaulttimeout =
|
|
444
|
+
cCurlMutiDefaulttimeout = NUM2LONG(timeout);
|
|
114
445
|
return timeout;
|
|
115
446
|
}
|
|
116
447
|
|
|
@@ -122,55 +453,39 @@ VALUE ruby_curl_multi_set_default_timeout(VALUE klass, VALUE timeout) {
|
|
|
122
453
|
*
|
|
123
454
|
*/
|
|
124
455
|
VALUE ruby_curl_multi_get_default_timeout(VALUE klass) {
|
|
125
|
-
return
|
|
456
|
+
return LONG2NUM(cCurlMutiDefaulttimeout);
|
|
126
457
|
}
|
|
127
458
|
|
|
128
|
-
/*
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
459
|
+
/*
|
|
460
|
+
* call-seq:
|
|
461
|
+
* Curl::Multi.autoclose = true => true
|
|
462
|
+
*
|
|
463
|
+
* Automatically close open connections after each request. Otherwise, the connection will remain open
|
|
464
|
+
* for reuse until the next GC
|
|
465
|
+
*
|
|
466
|
+
*/
|
|
467
|
+
VALUE ruby_curl_multi_set_autoclose(VALUE klass, VALUE onoff) {
|
|
468
|
+
cCurlMutiAutoClose = ((onoff == Qtrue) ? 1 : 0);
|
|
469
|
+
return onoff;
|
|
133
470
|
}
|
|
134
471
|
|
|
135
472
|
/*
|
|
136
473
|
* call-seq:
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
474
|
+
* Curl::Multi.autoclose => true|false
|
|
475
|
+
*
|
|
476
|
+
* Get the global default autoclose setting for all Curl::Multi Handles.
|
|
477
|
+
*
|
|
140
478
|
*/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
VALUE result_array;
|
|
144
|
-
|
|
145
|
-
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
|
146
|
-
|
|
147
|
-
result_array = rb_ary_new();
|
|
148
|
-
|
|
149
|
-
/* iterate over the requests hash, and stuff references into the array. */
|
|
150
|
-
rb_hash_foreach(rbcm->requests, ruby_curl_multi_requests_callback, result_array);
|
|
151
|
-
|
|
152
|
-
return result_array;
|
|
479
|
+
VALUE ruby_curl_multi_get_autoclose(VALUE klass) {
|
|
480
|
+
return cCurlMutiAutoClose == 1 ? Qtrue : Qfalse;
|
|
153
481
|
}
|
|
154
482
|
|
|
155
483
|
/*
|
|
156
484
|
* call-seq:
|
|
157
|
-
* multi.
|
|
485
|
+
* multi.requests => [#<Curl::Easy...>, ...]
|
|
158
486
|
*
|
|
159
|
-
* Returns
|
|
160
|
-
* true when multi.requests.length == 0.
|
|
487
|
+
* Returns an array containing all the active requests on this Curl::Multi object.
|
|
161
488
|
*/
|
|
162
|
-
static VALUE ruby_curl_multi_idle(VALUE self) {
|
|
163
|
-
ruby_curl_multi *rbcm;
|
|
164
|
-
|
|
165
|
-
Data_Get_Struct(self, ruby_curl_multi, rbcm);
|
|
166
|
-
|
|
167
|
-
if ( FIX2INT( rb_funcall(rbcm->requests, rb_intern("length"), 0) ) == 0 ) {
|
|
168
|
-
return Qtrue;
|
|
169
|
-
} else {
|
|
170
|
-
return Qfalse;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
489
|
/*
|
|
175
490
|
* call-seq:
|
|
176
491
|
* multi = Curl::Multi.new
|
|
@@ -182,8 +497,30 @@ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
|
|
|
182
497
|
#ifdef HAVE_CURLMOPT_MAXCONNECTS
|
|
183
498
|
ruby_curl_multi *rbcm;
|
|
184
499
|
|
|
185
|
-
|
|
186
|
-
|
|
500
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
501
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
502
|
+
|
|
503
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_MAXCONNECTS, NUM2LONG(count));
|
|
504
|
+
#endif
|
|
505
|
+
|
|
506
|
+
return count;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/*
|
|
510
|
+
* call-seq:
|
|
511
|
+
* multi = Curl::Multi.new
|
|
512
|
+
* multi.max_host_connections = 1
|
|
513
|
+
*
|
|
514
|
+
* Set the max number of connections per host
|
|
515
|
+
*/
|
|
516
|
+
static VALUE ruby_curl_multi_max_host_connections(VALUE self, VALUE count) {
|
|
517
|
+
#ifdef HAVE_CURLMOPT_MAX_HOST_CONNECTIONS
|
|
518
|
+
ruby_curl_multi *rbcm;
|
|
519
|
+
|
|
520
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
521
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
522
|
+
|
|
523
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_MAX_HOST_CONNECTIONS, NUM2LONG(count));
|
|
187
524
|
#endif
|
|
188
525
|
|
|
189
526
|
return count;
|
|
@@ -194,20 +531,32 @@ static VALUE ruby_curl_multi_max_connects(VALUE self, VALUE count) {
|
|
|
194
531
|
* multi = Curl::Multi.new
|
|
195
532
|
* multi.pipeline = true
|
|
196
533
|
*
|
|
197
|
-
* Pass a long set to 1
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
534
|
+
* Pass a long set to 1 for HTTP/1.1 pipelining, 2 for HTTP/2 multiplexing, or 0 to disable.
|
|
535
|
+
* Enabling pipelining on a multi handle will make it attempt to perform HTTP Pipelining as
|
|
536
|
+
* far as possible for transfers using this handle. This means that if you add a second request
|
|
537
|
+
* that can use an already existing connection, the second request will be "piped" on the same
|
|
538
|
+
* connection rather than being executed in parallel. (Added in 7.16.0, multiplex added in 7.43.0)
|
|
201
539
|
*
|
|
202
540
|
*/
|
|
203
|
-
static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE
|
|
541
|
+
static VALUE ruby_curl_multi_pipeline(VALUE self, VALUE method) {
|
|
204
542
|
#ifdef HAVE_CURLMOPT_PIPELINING
|
|
205
543
|
ruby_curl_multi *rbcm;
|
|
206
544
|
|
|
207
|
-
|
|
208
|
-
|
|
545
|
+
long value;
|
|
546
|
+
|
|
547
|
+
if (method == Qtrue) {
|
|
548
|
+
value = 1;
|
|
549
|
+
} else if (method == Qfalse) {
|
|
550
|
+
value = 0;
|
|
551
|
+
} else {
|
|
552
|
+
value = NUM2LONG(method);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
556
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
557
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_PIPELINING, value);
|
|
209
558
|
#endif
|
|
210
|
-
return
|
|
559
|
+
return method == Qtrue ? 1 : 0;
|
|
211
560
|
}
|
|
212
561
|
|
|
213
562
|
/*
|
|
@@ -223,224 +572,1222 @@ VALUE ruby_curl_multi_add(VALUE self, VALUE easy) {
|
|
|
223
572
|
CURLMcode mcode;
|
|
224
573
|
ruby_curl_easy *rbce;
|
|
225
574
|
ruby_curl_multi *rbcm;
|
|
575
|
+
ruby_curl_multi *existing_rbcm;
|
|
576
|
+
|
|
577
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
578
|
+
TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
|
|
579
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
580
|
+
|
|
581
|
+
if (rb_curl_multi_has_easy(rbcm, rbce)) {
|
|
582
|
+
return self;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
existing_rbcm = ruby_curl_multi_pointer_if_compatible(rbce->multi);
|
|
586
|
+
if (existing_rbcm && existing_rbcm != rbcm && rb_curl_multi_has_easy(existing_rbcm, rbce)) {
|
|
587
|
+
rb_raise(rb_eRuntimeError, "Cannot add an active Curl::Easy handle to another Curl::Multi");
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* setup the easy handle */
|
|
591
|
+
ruby_curl_easy_setup( rbce );
|
|
592
|
+
|
|
593
|
+
mcode = curl_multi_add_handle(rbcm->handle, rbce->curl);
|
|
594
|
+
if (mcode != CURLM_CALL_MULTI_PERFORM && mcode != CURLM_OK) {
|
|
595
|
+
ruby_curl_easy_cleanup(easy, rbce);
|
|
596
|
+
|
|
597
|
+
raise_curl_multi_error_exception(mcode);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
rbcm->active++;
|
|
601
|
+
|
|
602
|
+
/* Increase the running count, so that the perform loop keeps running.
|
|
603
|
+
* If this number is not correct, the next call to curl_multi_perform will correct it. */
|
|
604
|
+
rbcm->running++;
|
|
605
|
+
|
|
606
|
+
if (!rbcm->attached) {
|
|
607
|
+
rbcm->attached = st_init_numtable();
|
|
608
|
+
if (!rbcm->attached) {
|
|
609
|
+
curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
610
|
+
ruby_curl_easy_cleanup(easy, rbce);
|
|
611
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi attachment table");
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
rbce->multi_attachment_generation++;
|
|
616
|
+
st_insert(rbcm->attached, (st_data_t)rbce, (st_data_t)easy);
|
|
617
|
+
|
|
618
|
+
/* track a reference to associated multi handle */
|
|
619
|
+
rbce->multi = self;
|
|
620
|
+
|
|
621
|
+
return self;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/*
|
|
625
|
+
* call-seq:
|
|
626
|
+
* multi = Curl::Multi.new
|
|
627
|
+
* easy = Curl::Easy.new('url')
|
|
628
|
+
*
|
|
629
|
+
* multi.add(easy)
|
|
630
|
+
*
|
|
631
|
+
* # sometime later
|
|
632
|
+
* multi.remove(easy)
|
|
633
|
+
*
|
|
634
|
+
* Remove an easy handle from a multi stack.
|
|
635
|
+
*
|
|
636
|
+
* Will raise an exception if the easy handle is not found
|
|
637
|
+
*/
|
|
638
|
+
VALUE ruby_curl_multi_remove(VALUE self, VALUE rb_easy_handle) {
|
|
639
|
+
ruby_curl_multi *rbcm;
|
|
640
|
+
|
|
641
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
642
|
+
|
|
643
|
+
rb_curl_multi_remove(rbcm, rb_easy_handle);
|
|
644
|
+
|
|
645
|
+
return self;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
|
|
649
|
+
CURLMcode result;
|
|
650
|
+
ruby_curl_easy *rbce;
|
|
651
|
+
|
|
652
|
+
TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
|
|
653
|
+
result = curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
654
|
+
if (result != 0) {
|
|
655
|
+
raise_curl_multi_error_exception(result);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (rbcm->active > 0) {
|
|
659
|
+
rbcm->active--;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
rbce->multi = Qnil;
|
|
663
|
+
ruby_curl_easy_cleanup( easy, rbce );
|
|
664
|
+
|
|
665
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// on_success, on_failure, on_complete
|
|
669
|
+
static VALUE call_status_handler1(VALUE ary) {
|
|
670
|
+
return rb_funcall(rb_ary_entry(ary, 0), idCall, 1, rb_ary_entry(ary, 1));
|
|
671
|
+
}
|
|
672
|
+
static VALUE call_status_handler2(VALUE ary) {
|
|
673
|
+
return rb_funcall(rb_ary_entry(ary, 0), idCall, 2, rb_ary_entry(ary, 1), rb_ary_entry(ary, 2));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static void flush_stderr_if_any(ruby_curl_easy *rbce) {
|
|
677
|
+
VALUE stderr_io = rb_easy_get("stderr_io");
|
|
678
|
+
if (stderr_io != Qnil) {
|
|
679
|
+
/* Flush via Ruby IO API */
|
|
680
|
+
rb_funcall(stderr_io, rb_intern("flush"), 0);
|
|
681
|
+
#ifdef HAVE_RUBY_IO_H
|
|
682
|
+
/* Additionally flush underlying FILE* to be extra safe. */
|
|
683
|
+
rb_io_t *open_f_ptr;
|
|
684
|
+
if (RB_TYPE_P(stderr_io, T_FILE)) {
|
|
685
|
+
GetOpenFile(stderr_io, open_f_ptr);
|
|
686
|
+
FILE *fp = rb_io_stdio_file(open_f_ptr);
|
|
687
|
+
if (fp) fflush(fp);
|
|
688
|
+
}
|
|
689
|
+
#endif
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/* Helper to locate the Ruby Easy VALUE from the attached table using the
|
|
694
|
+
* underlying CURL* handle when CURLINFO_PRIVATE is unavailable or stale. */
|
|
695
|
+
struct find_easy_ctx { CURL *handle; VALUE easy; };
|
|
696
|
+
static int find_easy_by_handle_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
697
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
698
|
+
struct find_easy_ctx *ctx = (struct find_easy_ctx *)arg;
|
|
699
|
+
if (rbce && rbce->curl == ctx->handle) {
|
|
700
|
+
ctx->easy = (VALUE)val;
|
|
701
|
+
return ST_STOP;
|
|
702
|
+
}
|
|
703
|
+
return ST_CONTINUE;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
static VALUE find_easy_by_handle(ruby_curl_multi *rbcm, CURL *easy_handle) {
|
|
707
|
+
if (!rbcm || !rbcm->attached) return Qnil;
|
|
708
|
+
struct find_easy_ctx ctx; ctx.handle = easy_handle; ctx.easy = Qnil;
|
|
709
|
+
st_foreach(rbcm->attached, find_easy_by_handle_i, (st_data_t)&ctx);
|
|
710
|
+
return ctx.easy;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
static VALUE find_easy_value_for_handle(ruby_curl_multi *rbcm, CURL *easy_handle) {
|
|
714
|
+
VALUE easy = Qnil;
|
|
715
|
+
ruby_curl_easy *rbce = NULL;
|
|
716
|
+
|
|
717
|
+
CURLcode private_rc = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char **)&rbce);
|
|
718
|
+
if (private_rc == CURLE_OK && rbce) {
|
|
719
|
+
easy = rbce->self;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA)) {
|
|
723
|
+
easy = find_easy_by_handle(rbcm, easy_handle);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return easy;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
struct multi_complete_callback_args {
|
|
730
|
+
VALUE self;
|
|
731
|
+
VALUE easy;
|
|
732
|
+
ruby_curl_multi *rbcm;
|
|
733
|
+
ruby_curl_easy *rbce;
|
|
734
|
+
int result;
|
|
735
|
+
st_table *attached_snapshot;
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
static int snapshot_attached_easy_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
739
|
+
st_table *snapshot = (st_table *)arg;
|
|
740
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
741
|
+
|
|
742
|
+
if (!rbce) {
|
|
743
|
+
return ST_CONTINUE;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
st_insert(snapshot, key, (st_data_t)rbce->multi_attachment_generation);
|
|
747
|
+
return ST_CONTINUE;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
static st_table *capture_attached_easy_snapshot(ruby_curl_multi *rbcm) {
|
|
751
|
+
st_table *snapshot = st_init_numtable();
|
|
752
|
+
|
|
753
|
+
if (!snapshot) {
|
|
754
|
+
rb_raise(rb_eNoMemError, "Failed to allocate multi callback snapshot table");
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (rbcm && rbcm->attached) {
|
|
758
|
+
st_foreach(rbcm->attached, snapshot_attached_easy_i, (st_data_t)snapshot);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return snapshot;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
struct collect_new_attached_easies_ctx {
|
|
765
|
+
st_table *snapshot;
|
|
766
|
+
VALUE easies;
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
static int collect_new_attached_easies_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
770
|
+
st_data_t existing = 0;
|
|
771
|
+
ruby_curl_easy *rbce = (ruby_curl_easy *)key;
|
|
772
|
+
struct collect_new_attached_easies_ctx *ctx = (struct collect_new_attached_easies_ctx *)arg;
|
|
773
|
+
|
|
774
|
+
if (!rbce) {
|
|
775
|
+
return ST_CONTINUE;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (!ctx->snapshot || !st_lookup(ctx->snapshot, key, &existing) ||
|
|
779
|
+
existing != (st_data_t)rbce->multi_attachment_generation) {
|
|
780
|
+
rb_ary_push(ctx->easies, (VALUE)val);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return ST_CONTINUE;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
static void rb_curl_multi_remove_added_easies_since_snapshot(VALUE self, ruby_curl_multi *rbcm, st_table *snapshot) {
|
|
787
|
+
struct collect_new_attached_easies_ctx ctx;
|
|
788
|
+
VALUE easies = rb_ary_new();
|
|
789
|
+
long index;
|
|
790
|
+
|
|
791
|
+
if (!rbcm || !snapshot || !rbcm->attached) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
ctx.snapshot = snapshot;
|
|
796
|
+
ctx.easies = easies;
|
|
797
|
+
st_foreach(rbcm->attached, collect_new_attached_easies_i, (st_data_t)&ctx);
|
|
798
|
+
|
|
799
|
+
for (index = 0; index < RARRAY_LEN(easies); index++) {
|
|
800
|
+
VALUE easy = rb_ary_entry(easies, index);
|
|
801
|
+
ruby_curl_easy *rbce = NULL;
|
|
802
|
+
|
|
803
|
+
if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA)) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
|
|
808
|
+
if (!rbce || rbce->multi != self || !rb_curl_multi_has_easy(rbcm, rbce)) {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
rb_curl_multi_remove_request_reference(self, easy);
|
|
813
|
+
rb_curl_multi_remove(rbcm, easy);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
RB_GC_GUARD(easies);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
static void stash_and_raise_status_callback_error_if_unmasked(struct multi_complete_callback_args *args, VALUE did_raise, VALUE easy_callback_error) {
|
|
820
|
+
VALUE exception;
|
|
821
|
+
|
|
822
|
+
if (!NIL_P(easy_callback_error)) {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
exception = take_status_callback_exception_if_any(did_raise);
|
|
827
|
+
if (NIL_P(exception)) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
stash_multi_exception_if_unset(args->self, exception, args->easy);
|
|
832
|
+
rb_curl_multi_remove_added_easies_since_snapshot(args->self, args->rbcm, args->attached_snapshot);
|
|
833
|
+
rb_exc_raise(exception);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
static VALUE rb_curl_multi_run_completion_callbacks(VALUE argp) {
|
|
837
|
+
struct multi_complete_callback_args *args = (struct multi_complete_callback_args *)argp;
|
|
838
|
+
ruby_curl_easy *rbce = args->rbce;
|
|
839
|
+
long response_code = -1;
|
|
840
|
+
VALUE easy_callback_error = Qnil;
|
|
841
|
+
VALUE callargs;
|
|
842
|
+
VALUE did_raise = rb_hash_new();
|
|
843
|
+
long redirect_count;
|
|
844
|
+
|
|
845
|
+
args->rbcm->callback_active = 1;
|
|
846
|
+
|
|
847
|
+
easy_callback_error = take_easy_callback_error_if_any(args->easy);
|
|
848
|
+
if (!NIL_P(easy_callback_error)) {
|
|
849
|
+
stash_multi_exception_if_unset(args->self, easy_callback_error, args->easy);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (!rb_easy_nil("complete_proc")) {
|
|
853
|
+
callargs = rb_ary_new3(2, rb_easy_get("complete_proc"), args->easy);
|
|
854
|
+
args->rbce->callback_active = 1;
|
|
855
|
+
rb_rescue(call_status_handler1, callargs, callback_exception, did_raise);
|
|
856
|
+
args->rbce->callback_active = 0;
|
|
857
|
+
stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
#ifdef HAVE_CURLINFO_RESPONSE_CODE
|
|
861
|
+
curl_easy_getinfo(args->rbce->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
862
|
+
#else /* use fdsets path for waiting */
|
|
863
|
+
// old libcurl
|
|
864
|
+
curl_easy_getinfo(args->rbce->curl, CURLINFO_HTTP_CODE, &response_code);
|
|
865
|
+
#endif
|
|
866
|
+
curl_easy_getinfo(args->rbce->curl, CURLINFO_REDIRECT_COUNT, &redirect_count);
|
|
867
|
+
|
|
868
|
+
if (args->result != 0) {
|
|
869
|
+
if (!rb_easy_nil("failure_proc")) {
|
|
870
|
+
callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), args->easy, rb_curl_easy_error(args->result));
|
|
871
|
+
args->rbce->callback_active = 1;
|
|
872
|
+
rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
|
|
873
|
+
args->rbce->callback_active = 0;
|
|
874
|
+
stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error);
|
|
875
|
+
}
|
|
876
|
+
} else if (!rb_easy_nil("success_proc") &&
|
|
877
|
+
((response_code >= 200 && response_code < 300) || response_code == 0)) {
|
|
878
|
+
/* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
|
|
879
|
+
callargs = rb_ary_new3(2, rb_easy_get("success_proc"), args->easy);
|
|
880
|
+
args->rbce->callback_active = 1;
|
|
881
|
+
rb_rescue(call_status_handler1, callargs, callback_exception, did_raise);
|
|
882
|
+
args->rbce->callback_active = 0;
|
|
883
|
+
stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error);
|
|
884
|
+
|
|
885
|
+
} else if (!rb_easy_nil("redirect_proc") && ((response_code >= 300 && response_code < 400) || redirect_count > 0) ) {
|
|
886
|
+
/* Skip on_redirect callback if follow_location is false AND max_redirects is 0 */
|
|
887
|
+
if (!args->rbce->follow_location && args->rbce->max_redirs == 0) {
|
|
888
|
+
// Do nothing - skip the callback
|
|
889
|
+
} else {
|
|
890
|
+
args->rbce->callback_active = 1;
|
|
891
|
+
callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), args->easy, rb_curl_easy_error(args->result));
|
|
892
|
+
rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
|
|
893
|
+
args->rbce->callback_active = 0;
|
|
894
|
+
stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error);
|
|
895
|
+
}
|
|
896
|
+
} else if (!rb_easy_nil("missing_proc") &&
|
|
897
|
+
(response_code >= 400 && response_code < 500)) {
|
|
898
|
+
args->rbce->callback_active = 1;
|
|
899
|
+
callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), args->easy, rb_curl_easy_error(args->result));
|
|
900
|
+
rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
|
|
901
|
+
args->rbce->callback_active = 0;
|
|
902
|
+
stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error);
|
|
903
|
+
} else if (!rb_easy_nil("failure_proc") &&
|
|
904
|
+
(response_code >= 500 && response_code <= 999)) {
|
|
905
|
+
callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), args->easy, rb_curl_easy_error(args->result));
|
|
906
|
+
args->rbce->callback_active = 1;
|
|
907
|
+
rb_rescue(call_status_handler2, callargs, callback_exception, did_raise);
|
|
908
|
+
args->rbce->callback_active = 0;
|
|
909
|
+
stash_and_raise_status_callback_error_if_unmasked(args, did_raise, easy_callback_error);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
raise_completion_callback_error_if_any(did_raise, easy_callback_error);
|
|
913
|
+
return Qnil;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
static VALUE rb_curl_multi_finish_completion_callbacks(VALUE argp) {
|
|
917
|
+
struct multi_complete_callback_args *args = (struct multi_complete_callback_args *)argp;
|
|
918
|
+
|
|
919
|
+
if (!args->rbce) {
|
|
920
|
+
return Qnil;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (args->rbce->callback_active) {
|
|
924
|
+
args->rbce->callback_active = 0;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (args->rbcm) {
|
|
928
|
+
args->rbcm->callback_active = 0;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (args->rbce->multi == args->self && !rb_curl_multi_has_easy(args->rbcm, args->rbce)) {
|
|
932
|
+
args->rbce->multi = Qnil;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (args->attached_snapshot) {
|
|
936
|
+
st_free_table(args->attached_snapshot);
|
|
937
|
+
args->attached_snapshot = NULL;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return Qnil;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
static void rb_curl_mutli_handle_complete(VALUE self, CURL *easy_handle, int result) {
|
|
944
|
+
VALUE easy = Qnil;
|
|
945
|
+
ruby_curl_easy *rbce = NULL;
|
|
946
|
+
ruby_curl_multi *rbcm = NULL;
|
|
947
|
+
CURLMcode mcode;
|
|
948
|
+
|
|
949
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
950
|
+
|
|
951
|
+
easy = find_easy_value_for_handle(rbcm, easy_handle);
|
|
952
|
+
if (!NIL_P(easy) && RB_TYPE_P(easy, T_DATA)) {
|
|
953
|
+
TypedData_Get_Struct(easy, ruby_curl_easy, &ruby_curl_easy_data_type, rbce);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/* If we still cannot identify the easy handle, remove it and bail. */
|
|
957
|
+
if (NIL_P(easy) || !RB_TYPE_P(easy, T_DATA) || !rbce) {
|
|
958
|
+
if (rbcm && rbcm->handle && easy_handle) {
|
|
959
|
+
curl_multi_remove_handle(rbcm->handle, easy_handle);
|
|
960
|
+
}
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
rbce->last_result = result; /* save the last easy result code */
|
|
965
|
+
|
|
966
|
+
/* Ensure any verbose output redirected via CURLOPT_STDERR is flushed
|
|
967
|
+
* before we tear down handler state. */
|
|
968
|
+
flush_stderr_if_any(rbce);
|
|
969
|
+
|
|
970
|
+
/* Detach the easy from libcurl and the Ruby requests table before running
|
|
971
|
+
* status callbacks, but preserve easy.multi until the callbacks finish. */
|
|
972
|
+
mcode = curl_multi_remove_handle(rbcm->handle, rbce->curl);
|
|
973
|
+
if (mcode != CURLM_OK) {
|
|
974
|
+
raise_curl_multi_error_exception(mcode);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
if (rbcm->active > 0) {
|
|
978
|
+
rbcm->active--;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
rb_curl_multi_remove_request_reference(self, easy);
|
|
982
|
+
rb_curl_multi_forget_easy(rbcm, rbce);
|
|
983
|
+
ruby_curl_easy_cleanup(easy, rbce);
|
|
984
|
+
|
|
985
|
+
/* after running a request cleanup the headers, these are set before each request */
|
|
986
|
+
if (rbce->curl_headers) {
|
|
987
|
+
curl_slist_free_all(rbce->curl_headers);
|
|
988
|
+
rbce->curl_headers = NULL;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/* Flush again after removal to cover any last buffered data. */
|
|
992
|
+
flush_stderr_if_any(rbce);
|
|
993
|
+
|
|
994
|
+
struct multi_complete_callback_args args = {
|
|
995
|
+
self,
|
|
996
|
+
easy,
|
|
997
|
+
rbcm,
|
|
998
|
+
rbce,
|
|
999
|
+
result,
|
|
1000
|
+
capture_attached_easy_snapshot(rbcm)
|
|
1001
|
+
};
|
|
1002
|
+
rb_ensure(rb_curl_multi_run_completion_callbacks, (VALUE)&args,
|
|
1003
|
+
rb_curl_multi_finish_completion_callbacks, (VALUE)&args);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
static void rb_curl_multi_read_info(VALUE self, CURLM *multi_handle) {
|
|
1007
|
+
int msgs_left;
|
|
1008
|
+
|
|
1009
|
+
CURLcode c_easy_result;
|
|
1010
|
+
CURLMsg *c_multi_result; // for picking up messages with the transfer status
|
|
1011
|
+
CURL *c_easy_handle;
|
|
1012
|
+
ruby_curl_multi *rbcm = NULL;
|
|
1013
|
+
|
|
1014
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
1015
|
+
|
|
1016
|
+
/* Check for finished easy handles and remove from the multi handle.
|
|
1017
|
+
* curl_multi_info_read will query for messages from individual handles.
|
|
1018
|
+
*
|
|
1019
|
+
* The messages fetched with this function are removed from the curl internal
|
|
1020
|
+
* queue and when there are no messages left it will return NULL (and break
|
|
1021
|
+
* the loop effectively).
|
|
1022
|
+
*/
|
|
1023
|
+
while ((c_multi_result = curl_multi_info_read(multi_handle, &msgs_left))) {
|
|
1024
|
+
// A message is there, but we really care only about transfer completetion.
|
|
1025
|
+
if (c_multi_result->msg != CURLMSG_DONE) continue;
|
|
1026
|
+
|
|
1027
|
+
c_easy_handle = c_multi_result->easy_handle;
|
|
1028
|
+
c_easy_result = c_multi_result->data.result; /* return code for transfer */
|
|
1029
|
+
|
|
1030
|
+
struct multi_handle_complete_args args = { self, c_easy_handle, c_easy_result };
|
|
1031
|
+
int state = 0;
|
|
1032
|
+
rb_protect(rb_curl_mutli_handle_complete_protected, (VALUE)&args, &state);
|
|
1033
|
+
if (state) {
|
|
1034
|
+
stash_multi_exception_if_unset(self, rb_errinfo(), find_easy_value_for_handle(rbcm, c_easy_handle));
|
|
1035
|
+
rb_set_errinfo(Qnil);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
raise_multi_deferred_exception_if_idle(self);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/* called within ruby_curl_multi_perform */
|
|
1043
|
+
static void rb_curl_multi_run(VALUE self, CURLM *multi_handle, int *still_running) {
|
|
1044
|
+
CURLMcode mcode;
|
|
1045
|
+
|
|
1046
|
+
/*
|
|
1047
|
+
* curl_multi_perform will return CURLM_CALL_MULTI_PERFORM only when it wants to be called again immediately.
|
|
1048
|
+
* When things are fine and there is nothing immediate it wants done, it'll return CURLM_OK.
|
|
1049
|
+
*
|
|
1050
|
+
* It will perform all pending actions on all added easy handles attached to this multi handle. We will loop
|
|
1051
|
+
* here as long as mcode is CURLM_CALL_MULTIPERFORM.
|
|
1052
|
+
*/
|
|
1053
|
+
do {
|
|
1054
|
+
mcode = curl_multi_perform(multi_handle, still_running);
|
|
1055
|
+
} while (mcode == CURLM_CALL_MULTI_PERFORM);
|
|
1056
|
+
|
|
1057
|
+
/*
|
|
1058
|
+
* Nothing more to do, check if an error occured in the loop above and raise an exception if necessary.
|
|
1059
|
+
*/
|
|
1060
|
+
|
|
1061
|
+
if (mcode != CURLM_OK) {
|
|
1062
|
+
raise_curl_multi_error_exception(mcode);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/*
|
|
1066
|
+
* Everything is ok, but this does not mean all the transfers are completed.
|
|
1067
|
+
* There is no data to read or write available for Curl at the moment.
|
|
1068
|
+
*
|
|
1069
|
+
* At this point we can return control to the caller to do something else while
|
|
1070
|
+
* curl is waiting for more actions to queue.
|
|
1071
|
+
*/
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
1075
|
+
/* ---- socket-action implementation (scheduler-friendly) ---- */
|
|
1076
|
+
typedef struct {
|
|
1077
|
+
st_table *sock_map; /* key: int fd, value: int 'what' (CURL_POLL_*) */
|
|
1078
|
+
long long timeout_deadline_ms; /* absolute deadline for CURL_SOCKET_TIMEOUT */
|
|
1079
|
+
VALUE io_cache; /* fd -> IO wrapper for fiber-scheduler waits */
|
|
1080
|
+
} multi_socket_ctx;
|
|
1081
|
+
|
|
1082
|
+
static long long multi_socket_current_time_ms(void) {
|
|
1083
|
+
#if defined(CLOCK_MONOTONIC)
|
|
1084
|
+
struct timespec ts;
|
|
1085
|
+
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
|
1086
|
+
return ((long long)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
|
|
1087
|
+
}
|
|
1088
|
+
#endif
|
|
1089
|
+
|
|
1090
|
+
struct timeval tv;
|
|
1091
|
+
gettimeofday(&tv, NULL);
|
|
1092
|
+
return ((long long)tv.tv_sec * 1000) + (tv.tv_usec / 1000);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
static int multi_socket_timer_due(multi_socket_ctx *ctx) {
|
|
1096
|
+
return ctx && ctx->timeout_deadline_ms >= 0 &&
|
|
1097
|
+
multi_socket_current_time_ms() >= ctx->timeout_deadline_ms;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
#if CURB_SOCKET_DEBUG
|
|
1101
|
+
static void curb_debugf(const char *fmt, ...) {
|
|
1102
|
+
va_list ap;
|
|
1103
|
+
va_start(ap, fmt);
|
|
1104
|
+
vfprintf(stderr, fmt, ap);
|
|
1105
|
+
fputc('\n', stderr);
|
|
1106
|
+
fflush(stderr);
|
|
1107
|
+
va_end(ap);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
static const char *poll_what_str(int what, char *buf, size_t n) {
|
|
1111
|
+
/* what is one of CURL_POLL_*, not a bitmask except INOUT */
|
|
1112
|
+
if (what == CURL_POLL_REMOVE) snprintf(buf, n, "REMOVE");
|
|
1113
|
+
else if (what == CURL_POLL_IN) snprintf(buf, n, "IN");
|
|
1114
|
+
else if (what == CURL_POLL_OUT) snprintf(buf, n, "OUT");
|
|
1115
|
+
else if (what == CURL_POLL_INOUT) snprintf(buf, n, "INOUT");
|
|
1116
|
+
else snprintf(buf, n, "WHAT=%d", what);
|
|
1117
|
+
return buf;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
static const char *cselect_flags_str(int flags, char *buf, size_t n) {
|
|
1121
|
+
char tmp[32]; tmp[0] = 0;
|
|
1122
|
+
int off = 0;
|
|
1123
|
+
if (flags & CURL_CSELECT_IN) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sIN", off?"|":"");
|
|
1124
|
+
if (flags & CURL_CSELECT_OUT) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sOUT", off?"|":"");
|
|
1125
|
+
if (flags & CURL_CSELECT_ERR) off += snprintf(tmp+off, (size_t)(sizeof(tmp)-off), "%sERR", off?"|":"");
|
|
1126
|
+
if (off == 0) snprintf(tmp, sizeof(tmp), "0");
|
|
1127
|
+
snprintf(buf, n, "%s", tmp);
|
|
1128
|
+
return buf;
|
|
1129
|
+
}
|
|
1130
|
+
#else
|
|
1131
|
+
#define poll_what_str(...) ""
|
|
1132
|
+
#define cselect_flags_str(...) ""
|
|
1133
|
+
#endif
|
|
226
1134
|
|
|
227
|
-
|
|
228
|
-
|
|
1135
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1136
|
+
/* Protected call to rb_fiber_scheduler_io_wait to avoid unwinding into C on TypeError. */
|
|
1137
|
+
struct fiber_io_wait_args { VALUE scheduler; VALUE io; VALUE events; VALUE timeout; };
|
|
1138
|
+
static VALUE fiber_io_wait_protected(VALUE argp) {
|
|
1139
|
+
struct fiber_io_wait_args *a = (struct fiber_io_wait_args *)argp;
|
|
1140
|
+
return rb_fiber_scheduler_io_wait(a->scheduler, a->io, a->events, a->timeout);
|
|
1141
|
+
}
|
|
1142
|
+
#endif
|
|
229
1143
|
|
|
230
|
-
|
|
231
|
-
|
|
1144
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1145
|
+
struct fiber_io_select_args {
|
|
1146
|
+
VALUE scheduler;
|
|
1147
|
+
VALUE readables;
|
|
1148
|
+
VALUE writables;
|
|
1149
|
+
VALUE exceptables;
|
|
1150
|
+
VALUE timeout;
|
|
1151
|
+
};
|
|
232
1152
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
1153
|
+
static VALUE fiber_io_select_protected(VALUE argp) {
|
|
1154
|
+
struct fiber_io_select_args *a = (struct fiber_io_select_args *)argp;
|
|
1155
|
+
return rb_fiber_scheduler_io_select(a->scheduler, a->readables, a->writables, a->exceptables, a->timeout);
|
|
1156
|
+
}
|
|
1157
|
+
#endif
|
|
237
1158
|
|
|
238
|
-
|
|
1159
|
+
#if defined(RB_INTEGER_TYPE_P)
|
|
1160
|
+
#define CURB_INTEGER_P(value) RB_INTEGER_TYPE_P(value)
|
|
1161
|
+
#else
|
|
1162
|
+
#define CURB_INTEGER_P(value) (FIXNUM_P(value) || RB_TYPE_P((value), T_BIGNUM))
|
|
1163
|
+
#endif
|
|
239
1164
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
1165
|
+
static int multi_socket_wait_events_for_curl_poll(int what) {
|
|
1166
|
+
if (what == CURL_POLL_IN) return RB_WAITFD_IN;
|
|
1167
|
+
if (what == CURL_POLL_OUT) return RB_WAITFD_OUT;
|
|
1168
|
+
if (what == CURL_POLL_INOUT) return RB_WAITFD_IN | RB_WAITFD_OUT;
|
|
1169
|
+
return RB_WAITFD_IN | RB_WAITFD_OUT;
|
|
1170
|
+
}
|
|
243
1171
|
|
|
244
|
-
|
|
1172
|
+
static int multi_socket_cselect_flags_for_curl_poll(int what) {
|
|
1173
|
+
int flags = 0;
|
|
245
1174
|
|
|
246
|
-
|
|
1175
|
+
if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_IN;
|
|
1176
|
+
if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) flags |= CURL_CSELECT_OUT;
|
|
1177
|
+
if (flags == 0) flags = CURL_CSELECT_IN | CURL_CSELECT_OUT;
|
|
1178
|
+
|
|
1179
|
+
return flags;
|
|
247
1180
|
}
|
|
248
1181
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
* multi = Curl::Multi.new
|
|
252
|
-
* easy = Curl::Easy.new('url')
|
|
253
|
-
*
|
|
254
|
-
* multi.add(easy)
|
|
255
|
-
*
|
|
256
|
-
* # sometime later
|
|
257
|
-
* multi.remove(easy)
|
|
258
|
-
*
|
|
259
|
-
* Remove an easy handle from a multi stack.
|
|
260
|
-
*
|
|
261
|
-
* Will raise an exception if the easy handle is not found
|
|
262
|
-
*/
|
|
263
|
-
VALUE ruby_curl_multi_remove(VALUE self, VALUE easy) {
|
|
264
|
-
ruby_curl_multi *rbcm;
|
|
265
|
-
ruby_curl_easy *rbce;
|
|
1182
|
+
static int multi_socket_cselect_flags_for_wait_events(int events) {
|
|
1183
|
+
int flags = 0;
|
|
266
1184
|
|
|
267
|
-
|
|
1185
|
+
if (events & RB_WAITFD_IN) flags |= CURL_CSELECT_IN;
|
|
1186
|
+
if (events & RB_WAITFD_OUT) flags |= CURL_CSELECT_OUT;
|
|
1187
|
+
#ifdef RB_WAITFD_PRI
|
|
1188
|
+
if (events & RB_WAITFD_PRI) flags |= CURL_CSELECT_ERR;
|
|
1189
|
+
#endif
|
|
268
1190
|
|
|
269
|
-
|
|
1191
|
+
return flags;
|
|
1192
|
+
}
|
|
270
1193
|
|
|
271
|
-
|
|
1194
|
+
static void multi_socket_forget_fd(multi_socket_ctx *ctx, int fd) {
|
|
1195
|
+
st_data_t key = (st_data_t)fd;
|
|
1196
|
+
st_data_t rec;
|
|
272
1197
|
|
|
273
|
-
return
|
|
1198
|
+
if (!ctx) return;
|
|
1199
|
+
if (ctx->sock_map) st_delete(ctx->sock_map, &key, &rec);
|
|
1200
|
+
if (!NIL_P(ctx->io_cache)) rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
274
1201
|
}
|
|
275
|
-
static void rb_curl_multi_remove(ruby_curl_multi *rbcm, VALUE easy) {
|
|
276
|
-
CURLMcode result;
|
|
277
|
-
ruby_curl_easy *rbce;
|
|
278
|
-
VALUE r;
|
|
279
1202
|
|
|
280
|
-
|
|
1203
|
+
static int multi_socket_fd_valid_p(int fd) {
|
|
1204
|
+
if (fd < 0) return 0;
|
|
281
1205
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
1206
|
+
errno = 0;
|
|
1207
|
+
return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
|
|
1208
|
+
}
|
|
286
1209
|
|
|
287
|
-
|
|
1210
|
+
static int multi_socket_cb(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp) {
|
|
1211
|
+
multi_socket_ctx *ctx = (multi_socket_ctx *)userp;
|
|
1212
|
+
(void)easy; (void)socketp;
|
|
1213
|
+
int fd = (int)s;
|
|
288
1214
|
|
|
289
|
-
|
|
1215
|
+
if (!ctx || !ctx->sock_map) return 0;
|
|
1216
|
+
if (fd < 0) return 0;
|
|
290
1217
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
1218
|
+
if (what == CURL_POLL_REMOVE) {
|
|
1219
|
+
multi_socket_forget_fd(ctx, fd);
|
|
1220
|
+
#if CURB_SOCKET_DEBUG
|
|
1221
|
+
{
|
|
1222
|
+
char b[16];
|
|
1223
|
+
curb_debugf("[curb.socket] sock_cb fd=%d what=%s (removed)", fd, poll_what_str(what, b, sizeof(b)));
|
|
1224
|
+
}
|
|
1225
|
+
#endif
|
|
1226
|
+
} else {
|
|
1227
|
+
/* store current interest mask for this fd */
|
|
1228
|
+
st_data_t key = (st_data_t)fd;
|
|
1229
|
+
st_data_t old_what;
|
|
1230
|
+
if (st_lookup(ctx->sock_map, key, &old_what) && (int)old_what != what && !NIL_P(ctx->io_cache)) {
|
|
1231
|
+
rb_hash_delete(ctx->io_cache, INT2NUM(fd));
|
|
1232
|
+
}
|
|
1233
|
+
st_insert(ctx->sock_map, (st_data_t)fd, (st_data_t)what);
|
|
1234
|
+
#if CURB_SOCKET_DEBUG
|
|
1235
|
+
{
|
|
1236
|
+
char b[16];
|
|
1237
|
+
curb_debugf("[curb.socket] sock_cb fd=%d what=%s (tracked)", fd, poll_what_str(what, b, sizeof(b)));
|
|
1238
|
+
}
|
|
1239
|
+
#endif
|
|
1240
|
+
}
|
|
1241
|
+
return 0;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
static int multi_timer_cb(CURLM *multi, long timeout_ms, void *userp) {
|
|
1245
|
+
(void)multi;
|
|
1246
|
+
multi_socket_ctx *ctx = (multi_socket_ctx *)userp;
|
|
1247
|
+
if (ctx) {
|
|
1248
|
+
ctx->timeout_deadline_ms = timeout_ms < 0 ? -1 : multi_socket_current_time_ms() + timeout_ms;
|
|
295
1249
|
}
|
|
1250
|
+
curb_debugf("[curb.socket] timer_cb timeout_ms=%ld", timeout_ms);
|
|
1251
|
+
return 0;
|
|
296
1252
|
}
|
|
297
1253
|
|
|
298
|
-
|
|
299
|
-
static int
|
|
300
|
-
|
|
301
|
-
|
|
1254
|
+
struct build_fdset_args { rb_fdset_t *r; rb_fdset_t *w; rb_fdset_t *e; int maxfd; };
|
|
1255
|
+
static int rb_fdset_from_sockmap_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
1256
|
+
struct build_fdset_args *a = (struct build_fdset_args *)argp;
|
|
1257
|
+
int fd = (int)key;
|
|
1258
|
+
int what = (int)val;
|
|
1259
|
+
if (what & CURL_POLL_IN) rb_fd_set(fd, a->r);
|
|
1260
|
+
if (what & CURL_POLL_OUT) rb_fd_set(fd, a->w);
|
|
1261
|
+
rb_fd_set(fd, a->e);
|
|
1262
|
+
if (fd > a->maxfd) a->maxfd = fd;
|
|
302
1263
|
return ST_CONTINUE;
|
|
303
1264
|
}
|
|
1265
|
+
CURB_MAYBE_UNUSED_DECL
|
|
1266
|
+
static void rb_fdset_from_sockmap(st_table *map, rb_fdset_t *rfds, rb_fdset_t *wfds, rb_fdset_t *efds, int *maxfd_out) {
|
|
1267
|
+
if (!map) { *maxfd_out = -1; return; }
|
|
1268
|
+
struct build_fdset_args a; a.r = rfds; a.w = wfds; a.e = efds; a.maxfd = -1;
|
|
1269
|
+
st_foreach(map, rb_fdset_from_sockmap_i, (st_data_t)&a);
|
|
1270
|
+
*maxfd_out = a.maxfd;
|
|
1271
|
+
}
|
|
304
1272
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
* Cancels all requests currently being made on this Curl::Multi handle.
|
|
310
|
-
*/
|
|
311
|
-
static VALUE ruby_curl_multi_cancel(VALUE self) {
|
|
312
|
-
ruby_curl_multi *rbcm;
|
|
1273
|
+
struct ready_fd {
|
|
1274
|
+
int fd;
|
|
1275
|
+
int flags;
|
|
1276
|
+
};
|
|
313
1277
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
1278
|
+
struct collect_ready_fd_args {
|
|
1279
|
+
rb_fdset_t *r;
|
|
1280
|
+
rb_fdset_t *w;
|
|
1281
|
+
rb_fdset_t *e;
|
|
1282
|
+
struct ready_fd *fds;
|
|
1283
|
+
int capacity;
|
|
1284
|
+
int count;
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
static int collect_ready_fd_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
1288
|
+
(void)val;
|
|
1289
|
+
struct collect_ready_fd_args *a = (struct collect_ready_fd_args *)argp;
|
|
1290
|
+
int fd = (int)key;
|
|
1291
|
+
int flags = 0;
|
|
1292
|
+
if (rb_fd_isset(fd, a->r)) flags |= CURL_CSELECT_IN;
|
|
1293
|
+
if (rb_fd_isset(fd, a->w)) flags |= CURL_CSELECT_OUT;
|
|
1294
|
+
if (rb_fd_isset(fd, a->e)) flags |= CURL_CSELECT_ERR;
|
|
1295
|
+
if (flags && a->count < a->capacity) {
|
|
1296
|
+
a->fds[a->count].fd = fd;
|
|
1297
|
+
a->fds[a->count].flags = flags;
|
|
1298
|
+
a->count++;
|
|
1299
|
+
}
|
|
1300
|
+
return ST_CONTINUE;
|
|
320
1301
|
}
|
|
321
1302
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
1303
|
+
/* Helpers used with st_foreach to avoid compiler-specific nested functions. */
|
|
1304
|
+
struct pick_one_state { int fd; int what; int found; };
|
|
1305
|
+
static int st_pick_one_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
1306
|
+
struct pick_one_state *s = (struct pick_one_state *)argp;
|
|
1307
|
+
s->fd = (int)key;
|
|
1308
|
+
s->what = (int)val;
|
|
1309
|
+
s->found = 1;
|
|
1310
|
+
return ST_STOP;
|
|
325
1311
|
}
|
|
326
|
-
|
|
327
|
-
|
|
1312
|
+
struct counter_state { int count; };
|
|
1313
|
+
static int st_count_i(st_data_t k, st_data_t v, st_data_t argp) {
|
|
1314
|
+
(void)k; (void)v;
|
|
1315
|
+
struct counter_state *c = (struct counter_state *)argp;
|
|
1316
|
+
c->count++;
|
|
1317
|
+
return ST_CONTINUE;
|
|
328
1318
|
}
|
|
329
1319
|
|
|
330
|
-
static
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
VALUE callargs, val = Qtrue;
|
|
336
|
-
|
|
337
|
-
CURLcode ecode = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, (char**)&easy);
|
|
1320
|
+
static const char *multi_socket_io_mode_for_curl_poll(int what) {
|
|
1321
|
+
if (what == CURL_POLL_IN) return "r";
|
|
1322
|
+
if (what == CURL_POLL_OUT) return "w";
|
|
1323
|
+
return "r+";
|
|
1324
|
+
}
|
|
338
1325
|
|
|
339
|
-
|
|
1326
|
+
static VALUE multi_socket_io_for_fd(multi_socket_ctx *ctx, int fd, int what) {
|
|
1327
|
+
VALUE key = INT2NUM(fd);
|
|
1328
|
+
VALUE io = rb_hash_aref(ctx->io_cache, key);
|
|
1329
|
+
if (NIL_P(io)) {
|
|
1330
|
+
io = rb_funcall(rb_cIO, rb_intern("for_fd"), 2, key, rb_str_new_cstr(multi_socket_io_mode_for_curl_poll(what)));
|
|
1331
|
+
rb_funcall(io, rb_intern("autoclose="), 1, Qfalse);
|
|
1332
|
+
rb_hash_aset(ctx->io_cache, key, io);
|
|
1333
|
+
}
|
|
1334
|
+
return io;
|
|
1335
|
+
}
|
|
340
1336
|
|
|
341
|
-
|
|
1337
|
+
struct io_for_fd_args { multi_socket_ctx *ctx; int fd; int what; };
|
|
1338
|
+
static VALUE multi_socket_io_for_fd_protected(VALUE argp) {
|
|
1339
|
+
struct io_for_fd_args *a = (struct io_for_fd_args *)argp;
|
|
1340
|
+
return multi_socket_io_for_fd(a->ctx, a->fd, a->what);
|
|
1341
|
+
}
|
|
342
1342
|
|
|
343
|
-
|
|
1343
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1344
|
+
struct build_io_select_arrays_args {
|
|
1345
|
+
multi_socket_ctx *ctx;
|
|
1346
|
+
VALUE readables;
|
|
1347
|
+
VALUE writables;
|
|
1348
|
+
VALUE exceptables;
|
|
1349
|
+
int failed;
|
|
1350
|
+
};
|
|
344
1351
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
1352
|
+
static int build_io_select_arrays_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
1353
|
+
struct build_io_select_arrays_args *a = (struct build_io_select_arrays_args *)argp;
|
|
1354
|
+
int fd = (int)key;
|
|
1355
|
+
int what = (int)val;
|
|
1356
|
+
struct io_for_fd_args io_args = { a->ctx, fd, what };
|
|
1357
|
+
int io_state = 0;
|
|
1358
|
+
VALUE io;
|
|
1359
|
+
|
|
1360
|
+
if (!multi_socket_fd_valid_p(fd)) {
|
|
1361
|
+
a->failed = 1;
|
|
1362
|
+
return ST_STOP;
|
|
349
1363
|
}
|
|
350
1364
|
|
|
351
|
-
|
|
352
|
-
|
|
1365
|
+
io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1366
|
+
if (io_state || NIL_P(io)) {
|
|
1367
|
+
if (io_state) {
|
|
1368
|
+
#if CURB_SOCKET_DEBUG
|
|
1369
|
+
VALUE err = rb_errinfo();
|
|
1370
|
+
VALUE msg = rb_obj_as_string(err);
|
|
1371
|
+
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1372
|
+
#endif
|
|
1373
|
+
rb_set_errinfo(Qnil);
|
|
1374
|
+
}
|
|
1375
|
+
a->failed = 1;
|
|
1376
|
+
return ST_STOP;
|
|
353
1377
|
}
|
|
354
1378
|
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
|
|
359
|
-
rbce->callback_active = 0;
|
|
360
|
-
//rb_funcall( rb_easy_get("complete_proc"), idCall, 1, easy );
|
|
361
|
-
}
|
|
1379
|
+
if (what == CURL_POLL_IN || what == CURL_POLL_INOUT) rb_ary_push(a->readables, io);
|
|
1380
|
+
if (what == CURL_POLL_OUT || what == CURL_POLL_INOUT) rb_ary_push(a->writables, io);
|
|
1381
|
+
rb_ary_push(a->exceptables, io);
|
|
362
1382
|
|
|
363
|
-
|
|
1383
|
+
return ST_CONTINUE;
|
|
1384
|
+
}
|
|
364
1385
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
else if (!rb_easy_nil("success_proc") &&
|
|
375
|
-
((response_code >= 200 && response_code < 300) || response_code == 0)) {
|
|
376
|
-
/* NOTE: we allow response_code == 0, in the case of non http requests e.g. reading from disk */
|
|
377
|
-
callargs = rb_ary_new3(2, rb_easy_get("success_proc"), easy);
|
|
378
|
-
rbce->callback_active = 1;
|
|
379
|
-
val = rb_rescue(call_status_handler1, callargs, callback_exception, Qnil);
|
|
380
|
-
rbce->callback_active = 0;
|
|
381
|
-
//rb_funcall( rb_easy_get("success_proc"), idCall, 1, easy );
|
|
382
|
-
}
|
|
383
|
-
else if (!rb_easy_nil("redirect_proc") &&
|
|
384
|
-
(response_code >= 300 && response_code < 400)) {
|
|
385
|
-
rbce->callback_active = 1;
|
|
386
|
-
callargs = rb_ary_new3(3, rb_easy_get("redirect_proc"), easy, rb_curl_easy_error(result));
|
|
387
|
-
rbce->callback_active = 0;
|
|
388
|
-
val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
|
|
389
|
-
}
|
|
390
|
-
else if (!rb_easy_nil("missing_proc") &&
|
|
391
|
-
(response_code >= 400 && response_code < 500)) {
|
|
392
|
-
rbce->callback_active = 1;
|
|
393
|
-
callargs = rb_ary_new3(3, rb_easy_get("missing_proc"), easy, rb_curl_easy_error(result));
|
|
394
|
-
rbce->callback_active = 0;
|
|
395
|
-
val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
|
|
396
|
-
}
|
|
397
|
-
else if (!rb_easy_nil("failure_proc") &&
|
|
398
|
-
(response_code >= 500 && response_code <= 999)) {
|
|
399
|
-
callargs = rb_ary_new3(3, rb_easy_get("failure_proc"), easy, rb_curl_easy_error(result));
|
|
400
|
-
rbce->callback_active = 1;
|
|
401
|
-
val = rb_rescue(call_status_handler2, callargs, callback_exception, Qnil);
|
|
402
|
-
rbce->callback_active = 0;
|
|
403
|
-
//rb_funcall( rb_easy_get("failure_proc"), idCall, 2, easy, rb_curl_easy_error(result) );
|
|
404
|
-
}
|
|
1386
|
+
struct collect_io_select_ready_args {
|
|
1387
|
+
multi_socket_ctx *ctx;
|
|
1388
|
+
VALUE readables;
|
|
1389
|
+
VALUE writables;
|
|
1390
|
+
VALUE exceptables;
|
|
1391
|
+
struct ready_fd *fds;
|
|
1392
|
+
int capacity;
|
|
1393
|
+
int count;
|
|
1394
|
+
};
|
|
405
1395
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
1396
|
+
static int collect_io_select_ready_i(st_data_t key, st_data_t val, st_data_t argp) {
|
|
1397
|
+
(void)val;
|
|
1398
|
+
struct collect_io_select_ready_args *a = (struct collect_io_select_ready_args *)argp;
|
|
1399
|
+
VALUE io = rb_hash_aref(a->ctx->io_cache, INT2NUM((int)key));
|
|
1400
|
+
int flags = 0;
|
|
1401
|
+
|
|
1402
|
+
if (NIL_P(io)) return ST_CONTINUE;
|
|
1403
|
+
if (RTEST(rb_ary_includes(a->readables, io))) flags |= CURL_CSELECT_IN;
|
|
1404
|
+
if (RTEST(rb_ary_includes(a->writables, io))) flags |= CURL_CSELECT_OUT;
|
|
1405
|
+
if (RTEST(rb_ary_includes(a->exceptables, io))) flags |= CURL_CSELECT_ERR;
|
|
1406
|
+
|
|
1407
|
+
if (flags && a->count < a->capacity) {
|
|
1408
|
+
a->fds[a->count].fd = (int)key;
|
|
1409
|
+
a->fds[a->count].flags = flags;
|
|
1410
|
+
a->count++;
|
|
410
1411
|
}
|
|
411
1412
|
|
|
1413
|
+
return ST_CONTINUE;
|
|
412
1414
|
}
|
|
1415
|
+
#endif
|
|
413
1416
|
|
|
414
|
-
static void
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
1417
|
+
static void rb_curl_multi_socket_drive(VALUE self, ruby_curl_multi *rbcm, multi_socket_ctx *ctx, VALUE block) {
|
|
1418
|
+
/* prime the state: let libcurl act on timeouts to setup sockets */
|
|
1419
|
+
CURLMcode mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1420
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1421
|
+
curb_debugf("[curb.socket] drive: initial socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1422
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1423
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1424
|
+
|
|
1425
|
+
while (rbcm->running) {
|
|
1426
|
+
struct timeval tv = {0, 0};
|
|
1427
|
+
long wait_ms = cCurlMutiDefaulttimeout;
|
|
1428
|
+
|
|
1429
|
+
if (multi_socket_timer_due(ctx)) {
|
|
1430
|
+
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1431
|
+
curb_debugf("[curb.socket] socket_action timeout(due) -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1432
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1433
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1434
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (ctx->timeout_deadline_ms >= 0) {
|
|
1439
|
+
long long remaining_ms = ctx->timeout_deadline_ms - multi_socket_current_time_ms();
|
|
1440
|
+
if (remaining_ms < wait_ms) wait_ms = remaining_ms < 0 ? 0 : (long)remaining_ms;
|
|
1441
|
+
}
|
|
1442
|
+
tv.tv_sec = wait_ms / 1000;
|
|
1443
|
+
tv.tv_usec = (wait_ms % 1000) * 1000;
|
|
1444
|
+
|
|
1445
|
+
/* Find a representative fd to wait on (if any). */
|
|
1446
|
+
int wait_fd = -1;
|
|
1447
|
+
int wait_what = 0;
|
|
1448
|
+
if (ctx->sock_map) {
|
|
1449
|
+
struct pick_one_state st = { -1, 0, 0 };
|
|
1450
|
+
st_foreach(ctx->sock_map, st_pick_one_i, (st_data_t)&st);
|
|
1451
|
+
if (st.found) { wait_fd = st.fd; wait_what = st.what; }
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/* Count tracked fds for logging */
|
|
1455
|
+
int count_tracked = 0;
|
|
1456
|
+
if (ctx->sock_map) {
|
|
1457
|
+
struct counter_state cs = { 0 };
|
|
1458
|
+
st_foreach(ctx->sock_map, st_count_i, (st_data_t)&cs);
|
|
1459
|
+
count_tracked = cs.count;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
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);
|
|
1463
|
+
|
|
1464
|
+
int did_timeout = 0;
|
|
1465
|
+
int any_ready = 0;
|
|
1466
|
+
int ready_flags = 0;
|
|
1467
|
+
|
|
1468
|
+
int handled_wait = 0;
|
|
1469
|
+
if (count_tracked > 1) {
|
|
1470
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_SELECT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1471
|
+
{
|
|
1472
|
+
VALUE scheduler = rb_fiber_scheduler_current();
|
|
1473
|
+
if (scheduler != Qnil) {
|
|
1474
|
+
VALUE readables = rb_ary_new();
|
|
1475
|
+
VALUE writables = rb_ary_new();
|
|
1476
|
+
VALUE exceptables = rb_ary_new();
|
|
1477
|
+
struct build_io_select_arrays_args build_args = { ctx, readables, writables, exceptables, 0 };
|
|
1478
|
+
st_foreach(ctx->sock_map, build_io_select_arrays_i, (st_data_t)&build_args);
|
|
1479
|
+
if (!build_args.failed) {
|
|
1480
|
+
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1481
|
+
VALUE timeout = rb_float_new(timeout_s);
|
|
1482
|
+
struct fiber_io_select_args select_args = { scheduler, readables, writables, exceptables, timeout };
|
|
1483
|
+
int state = 0;
|
|
1484
|
+
VALUE ready = rb_protect(fiber_io_select_protected, (VALUE)&select_args, &state);
|
|
1485
|
+
if (state) {
|
|
1486
|
+
#if CURB_SOCKET_DEBUG
|
|
1487
|
+
VALUE err = rb_errinfo();
|
|
1488
|
+
VALUE msg = rb_obj_as_string(err);
|
|
1489
|
+
curb_debugf("[curb.socket] scheduler io_select failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1490
|
+
#endif
|
|
1491
|
+
rb_set_errinfo(Qnil);
|
|
1492
|
+
} else {
|
|
1493
|
+
handled_wait = 1;
|
|
1494
|
+
any_ready = RB_TYPE_P(ready, T_ARRAY);
|
|
1495
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1496
|
+
if (any_ready) {
|
|
1497
|
+
VALUE ready_readables = rb_ary_entry(ready, 0);
|
|
1498
|
+
VALUE ready_writables = rb_ary_entry(ready, 1);
|
|
1499
|
+
VALUE ready_exceptables = rb_ary_entry(ready, 2);
|
|
1500
|
+
struct ready_fd *ready_fds = ALLOC_N(struct ready_fd, count_tracked);
|
|
1501
|
+
struct collect_io_select_ready_args d;
|
|
1502
|
+
int i;
|
|
1503
|
+
if (!RB_TYPE_P(ready_readables, T_ARRAY)) ready_readables = rb_ary_new();
|
|
1504
|
+
if (!RB_TYPE_P(ready_writables, T_ARRAY)) ready_writables = rb_ary_new();
|
|
1505
|
+
if (!RB_TYPE_P(ready_exceptables, T_ARRAY)) ready_exceptables = rb_ary_new();
|
|
1506
|
+
d.ctx = ctx;
|
|
1507
|
+
d.readables = ready_readables;
|
|
1508
|
+
d.writables = ready_writables;
|
|
1509
|
+
d.exceptables = ready_exceptables;
|
|
1510
|
+
d.fds = ready_fds;
|
|
1511
|
+
d.capacity = count_tracked;
|
|
1512
|
+
d.count = 0;
|
|
1513
|
+
st_foreach(ctx->sock_map, collect_io_select_ready_i, (st_data_t)&d);
|
|
1514
|
+
any_ready = (d.count > 0);
|
|
1515
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1516
|
+
for (i = 0; i < d.count; i++) {
|
|
1517
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)d.fds[i].fd, d.fds[i].flags, &rbcm->running);
|
|
1518
|
+
if (mrc != CURLM_OK) {
|
|
1519
|
+
xfree(ready_fds);
|
|
1520
|
+
raise_curl_multi_error_exception(mrc);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
xfree(ready_fds);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
#endif
|
|
1530
|
+
if (!handled_wait) {
|
|
1531
|
+
/* Multi-fd wait using scheduler-aware rb_thread_fd_select. */
|
|
1532
|
+
rb_fdset_t rfds, wfds, efds;
|
|
1533
|
+
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
|
1534
|
+
int maxfd = -1;
|
|
1535
|
+
rb_fdset_from_sockmap(ctx->sock_map, &rfds, &wfds, &efds, &maxfd);
|
|
1536
|
+
int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv);
|
|
1537
|
+
curb_debugf("[curb.socket] rb_thread_fd_select(multi) rc=%d maxfd=%d", rc, maxfd);
|
|
1538
|
+
if (rc < 0) {
|
|
1539
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1540
|
+
if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
any_ready = (rc > 0);
|
|
1544
|
+
did_timeout = (rc == 0 && multi_socket_timer_due(ctx));
|
|
1545
|
+
if (any_ready) {
|
|
1546
|
+
struct ready_fd *ready_fds = ALLOC_N(struct ready_fd, count_tracked);
|
|
1547
|
+
struct collect_ready_fd_args d;
|
|
1548
|
+
int i;
|
|
1549
|
+
d.r = &rfds;
|
|
1550
|
+
d.w = &wfds;
|
|
1551
|
+
d.e = &efds;
|
|
1552
|
+
d.fds = ready_fds;
|
|
1553
|
+
d.capacity = count_tracked;
|
|
1554
|
+
d.count = 0;
|
|
1555
|
+
st_foreach(ctx->sock_map, collect_ready_fd_i, (st_data_t)&d);
|
|
1556
|
+
for (i = 0; i < d.count; i++) {
|
|
1557
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)d.fds[i].fd, d.fds[i].flags, &rbcm->running);
|
|
1558
|
+
if (mrc != CURLM_OK) {
|
|
1559
|
+
xfree(ready_fds);
|
|
1560
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1561
|
+
raise_curl_multi_error_exception(mrc);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
xfree(ready_fds);
|
|
1565
|
+
}
|
|
1566
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1567
|
+
handled_wait = 1;
|
|
1568
|
+
}
|
|
1569
|
+
} else if (count_tracked == 1) {
|
|
1570
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_IO_WAIT) && defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1571
|
+
{
|
|
1572
|
+
VALUE scheduler = rb_fiber_scheduler_current();
|
|
1573
|
+
if (scheduler != Qnil) {
|
|
1574
|
+
int scheduler_wait_handled = 0;
|
|
1575
|
+
int events = 0;
|
|
1576
|
+
if (wait_fd >= 0) {
|
|
1577
|
+
events = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
1578
|
+
}
|
|
1579
|
+
double timeout_s = (double)tv.tv_sec + ((double)tv.tv_usec / 1e6);
|
|
1580
|
+
VALUE timeout = rb_float_new(timeout_s);
|
|
1581
|
+
if (wait_fd < 0) {
|
|
1582
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
1583
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv);
|
|
1584
|
+
#else
|
|
1585
|
+
rb_thread_wait_for(tv);
|
|
1586
|
+
#endif
|
|
1587
|
+
did_timeout = multi_socket_timer_due(ctx);
|
|
1588
|
+
scheduler_wait_handled = 1;
|
|
1589
|
+
} else if (!multi_socket_fd_valid_p(wait_fd)) {
|
|
1590
|
+
multi_socket_forget_fd(ctx, wait_fd);
|
|
1591
|
+
did_timeout = 1;
|
|
1592
|
+
scheduler_wait_handled = 1;
|
|
1593
|
+
} else {
|
|
1594
|
+
struct io_for_fd_args io_args = { ctx, wait_fd, wait_what };
|
|
1595
|
+
int io_state = 0;
|
|
1596
|
+
VALUE io = rb_protect(multi_socket_io_for_fd_protected, (VALUE)&io_args, &io_state);
|
|
1597
|
+
if (io_state || NIL_P(io)) {
|
|
1598
|
+
if (io_state) {
|
|
1599
|
+
#if CURB_SOCKET_DEBUG
|
|
1600
|
+
VALUE err = rb_errinfo();
|
|
1601
|
+
VALUE msg = rb_obj_as_string(err);
|
|
1602
|
+
curb_debugf("[curb.socket] IO.for_fd failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1603
|
+
#endif
|
|
1604
|
+
rb_set_errinfo(Qnil);
|
|
1605
|
+
}
|
|
1606
|
+
any_ready = 0;
|
|
1607
|
+
} else {
|
|
1608
|
+
struct fiber_io_wait_args args = { scheduler, io, INT2NUM(events), timeout };
|
|
1609
|
+
int state = 0;
|
|
1610
|
+
VALUE ready = rb_protect(fiber_io_wait_protected, (VALUE)&args, &state);
|
|
1611
|
+
if (state) {
|
|
1612
|
+
#if CURB_SOCKET_DEBUG
|
|
1613
|
+
VALUE err = rb_errinfo();
|
|
1614
|
+
VALUE msg = rb_obj_as_string(err);
|
|
1615
|
+
curb_debugf("[curb.socket] scheduler io_wait failed: %s: %s", rb_obj_classname(err), StringValueCStr(msg));
|
|
1616
|
+
#endif
|
|
1617
|
+
rb_set_errinfo(Qnil);
|
|
1618
|
+
any_ready = 0;
|
|
1619
|
+
} else {
|
|
1620
|
+
scheduler_wait_handled = 1;
|
|
1621
|
+
any_ready = (ready != Qfalse && !NIL_P(ready));
|
|
1622
|
+
did_timeout = !any_ready && multi_socket_timer_due(ctx);
|
|
1623
|
+
if (any_ready) {
|
|
1624
|
+
if (ready == Qtrue) {
|
|
1625
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1626
|
+
} else if (CURB_INTEGER_P(ready)) {
|
|
1627
|
+
ready_flags = multi_socket_cselect_flags_for_wait_events(NUM2INT(ready));
|
|
1628
|
+
if (ready_flags == 0) {
|
|
1629
|
+
any_ready = 0;
|
|
1630
|
+
did_timeout = multi_socket_timer_due(ctx);
|
|
1631
|
+
}
|
|
1632
|
+
} else {
|
|
1633
|
+
ready_flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (scheduler_wait_handled) handled_wait = 1;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
#endif
|
|
1643
|
+
#if defined(HAVE_RB_WAIT_FOR_SINGLE_FD)
|
|
1644
|
+
if (!handled_wait && wait_fd >= 0) {
|
|
1645
|
+
int ev = multi_socket_wait_events_for_curl_poll(wait_what);
|
|
1646
|
+
int rc = rb_wait_for_single_fd(wait_fd, ev, &tv);
|
|
1647
|
+
curb_debugf("[curb.socket] rb_wait_for_single_fd rc=%d fd=%d ev=%d", rc, wait_fd, ev);
|
|
1648
|
+
if (rc < 0) {
|
|
1649
|
+
if (errno != EINTR) rb_raise(rb_eRuntimeError, "wait_for_single_fd(): %s", strerror(errno));
|
|
1650
|
+
continue;
|
|
1651
|
+
}
|
|
1652
|
+
any_ready = (rc != 0);
|
|
1653
|
+
did_timeout = (rc == 0 && multi_socket_timer_due(ctx));
|
|
1654
|
+
if (any_ready) ready_flags = multi_socket_cselect_flags_for_wait_events(rc);
|
|
1655
|
+
handled_wait = 1;
|
|
1656
|
+
}
|
|
1657
|
+
#endif
|
|
1658
|
+
if (!handled_wait) {
|
|
1659
|
+
/* Fallback: single-fd select. */
|
|
1660
|
+
rb_fdset_t rfds, wfds, efds;
|
|
1661
|
+
rb_fd_init(&rfds); rb_fd_init(&wfds); rb_fd_init(&efds);
|
|
1662
|
+
int maxfd = -1;
|
|
1663
|
+
if (wait_fd >= 0) {
|
|
1664
|
+
if (wait_what == CURL_POLL_IN || wait_what == CURL_POLL_INOUT) rb_fd_set(wait_fd, &rfds);
|
|
1665
|
+
if (wait_what == CURL_POLL_OUT || wait_what == CURL_POLL_INOUT) rb_fd_set(wait_fd, &wfds);
|
|
1666
|
+
rb_fd_set(wait_fd, &efds);
|
|
1667
|
+
maxfd = wait_fd;
|
|
1668
|
+
}
|
|
1669
|
+
int rc = rb_thread_fd_select(maxfd + 1, &rfds, &wfds, &efds, &tv);
|
|
1670
|
+
curb_debugf("[curb.socket] rb_thread_fd_select(single) rc=%d fd=%d", rc, wait_fd);
|
|
1671
|
+
if (rc < 0) {
|
|
1672
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1673
|
+
if (errno != EINTR) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
any_ready = (rc > 0);
|
|
1677
|
+
did_timeout = (rc == 0 && multi_socket_timer_due(ctx));
|
|
1678
|
+
if (any_ready && wait_fd >= 0) {
|
|
1679
|
+
if (rb_fd_isset(wait_fd, &rfds)) ready_flags |= CURL_CSELECT_IN;
|
|
1680
|
+
if (rb_fd_isset(wait_fd, &wfds)) ready_flags |= CURL_CSELECT_OUT;
|
|
1681
|
+
if (rb_fd_isset(wait_fd, &efds)) ready_flags |= CURL_CSELECT_ERR;
|
|
1682
|
+
}
|
|
1683
|
+
rb_fd_term(&rfds); rb_fd_term(&wfds); rb_fd_term(&efds);
|
|
1684
|
+
}
|
|
1685
|
+
} else { /* count_tracked == 0 */
|
|
1686
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
1687
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv);
|
|
1688
|
+
#else
|
|
1689
|
+
rb_thread_wait_for(tv);
|
|
1690
|
+
#endif
|
|
1691
|
+
did_timeout = multi_socket_timer_due(ctx);
|
|
1692
|
+
}
|
|
418
1693
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (
|
|
425
|
-
|
|
1694
|
+
if (did_timeout) {
|
|
1695
|
+
mrc = curl_multi_socket_action(rbcm->handle, CURL_SOCKET_TIMEOUT, 0, &rbcm->running);
|
|
1696
|
+
curb_debugf("[curb.socket] socket_action timeout -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1697
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
1698
|
+
} else if (any_ready) {
|
|
1699
|
+
if (count_tracked == 1 && wait_fd >= 0) {
|
|
1700
|
+
int flags = ready_flags;
|
|
1701
|
+
if (flags == 0) flags = multi_socket_cselect_flags_for_curl_poll(wait_what);
|
|
1702
|
+
#if CURB_SOCKET_DEBUG
|
|
1703
|
+
{
|
|
1704
|
+
char b[32];
|
|
1705
|
+
curb_debugf("[curb.socket] socket_action fd=%d flags=%s", wait_fd, cselect_flags_str(flags, b, sizeof(b)));
|
|
1706
|
+
}
|
|
1707
|
+
#endif
|
|
1708
|
+
mrc = curl_multi_socket_action(rbcm->handle, (curl_socket_t)wait_fd, flags, &rbcm->running);
|
|
1709
|
+
curb_debugf("[curb.socket] socket_action -> mrc=%d running=%d", mrc, rbcm->running);
|
|
1710
|
+
if (mrc != CURLM_OK) raise_curl_multi_error_exception(mrc);
|
|
426
1711
|
}
|
|
427
1712
|
}
|
|
1713
|
+
|
|
1714
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1715
|
+
curb_debugf("[curb.socket] processed completions; running=%d", rbcm->running);
|
|
1716
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
428
1717
|
}
|
|
429
1718
|
}
|
|
430
1719
|
|
|
431
|
-
|
|
432
|
-
static
|
|
433
|
-
|
|
1720
|
+
struct socket_drive_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; VALUE block; };
|
|
1721
|
+
static VALUE ruby_curl_multi_socket_drive_body(VALUE argp) {
|
|
1722
|
+
struct socket_drive_args *a = (struct socket_drive_args *)argp;
|
|
1723
|
+
rb_curl_multi_socket_drive(a->self, a->rbcm, a->ctx, a->block);
|
|
1724
|
+
return Qtrue;
|
|
1725
|
+
}
|
|
1726
|
+
struct socket_cleanup_args { VALUE self; ruby_curl_multi *rbcm; multi_socket_ctx *ctx; };
|
|
1727
|
+
static VALUE ruby_curl_multi_socket_drive_ensure(VALUE argp) {
|
|
1728
|
+
struct socket_cleanup_args *c = (struct socket_cleanup_args *)argp;
|
|
1729
|
+
if (c->rbcm && c->rbcm->handle) {
|
|
1730
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_SOCKETFUNCTION, NULL);
|
|
1731
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_SOCKETDATA, NULL);
|
|
1732
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_TIMERFUNCTION, NULL);
|
|
1733
|
+
curl_multi_setopt(c->rbcm->handle, CURLMOPT_TIMERDATA, NULL);
|
|
1734
|
+
}
|
|
1735
|
+
if (c->ctx && c->ctx->sock_map) {
|
|
1736
|
+
st_free_table(c->ctx->sock_map);
|
|
1737
|
+
c->ctx->sock_map = NULL;
|
|
1738
|
+
}
|
|
1739
|
+
if (c->ctx) {
|
|
1740
|
+
if (!NIL_P(c->ctx->io_cache)) {
|
|
1741
|
+
rb_hash_clear(c->ctx->io_cache);
|
|
1742
|
+
}
|
|
1743
|
+
c->ctx->io_cache = Qnil;
|
|
1744
|
+
}
|
|
1745
|
+
if (!NIL_P(c->self) && rb_ivar_defined(c->self, id_socket_io_cache_ivar)) {
|
|
1746
|
+
rb_funcall(c->self, rb_intern("remove_instance_variable"), 1, ID2SYM(id_socket_io_cache_ivar));
|
|
1747
|
+
}
|
|
1748
|
+
return Qnil;
|
|
1749
|
+
}
|
|
434
1750
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
1751
|
+
static VALUE ruby_curl_multi_socket_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
1752
|
+
ruby_curl_multi *rbcm;
|
|
1753
|
+
VALUE block = Qnil;
|
|
1754
|
+
rb_scan_args(argc, argv, "0&", &block);
|
|
438
1755
|
|
|
439
|
-
|
|
440
|
-
|
|
1756
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
1757
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
1758
|
+
if (!rb_ivar_defined(self, id_deferred_exception_ivar)) {
|
|
1759
|
+
clear_multi_deferred_exception_source_id_if_any(self);
|
|
441
1760
|
}
|
|
442
|
-
|
|
1761
|
+
|
|
1762
|
+
multi_socket_ctx ctx;
|
|
1763
|
+
ctx.sock_map = st_init_numtable();
|
|
1764
|
+
ctx.timeout_deadline_ms = -1;
|
|
1765
|
+
ctx.io_cache = rb_hash_new();
|
|
1766
|
+
rb_ivar_set(self, id_socket_io_cache_ivar, ctx.io_cache);
|
|
1767
|
+
|
|
1768
|
+
/* install socket/timer callbacks */
|
|
1769
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETFUNCTION, multi_socket_cb);
|
|
1770
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_SOCKETDATA, &ctx);
|
|
1771
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
|
|
1772
|
+
curl_multi_setopt(rbcm->handle, CURLMOPT_TIMERDATA, &ctx);
|
|
1773
|
+
|
|
1774
|
+
/* run using socket action loop with ensure-cleanup */
|
|
1775
|
+
struct socket_drive_args body_args = { self, rbcm, &ctx, block };
|
|
1776
|
+
struct socket_cleanup_args ensure_args = { self, rbcm, &ctx };
|
|
1777
|
+
rb_ensure(ruby_curl_multi_socket_drive_body, (VALUE)&body_args, ruby_curl_multi_socket_drive_ensure, (VALUE)&ensure_args);
|
|
1778
|
+
|
|
1779
|
+
/* finalize */
|
|
1780
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1781
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1782
|
+
if (cCurlMutiAutoClose == 1) {
|
|
1783
|
+
rbcm->allow_close_during_perform = 1;
|
|
1784
|
+
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
1785
|
+
rbcm->allow_close_during_perform = 0;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
return Qtrue;
|
|
443
1789
|
}
|
|
1790
|
+
#endif /* socket-action implementation */
|
|
444
1791
|
|
|
445
1792
|
#ifdef _WIN32
|
|
446
1793
|
void create_crt_fd(fd_set *os_set, fd_set *crt_set)
|
|
@@ -471,7 +1818,8 @@ void cleanup_crt_fd(fd_set *os_set, fd_set *crt_set)
|
|
|
471
1818
|
}
|
|
472
1819
|
#endif
|
|
473
1820
|
|
|
474
|
-
|
|
1821
|
+
/* curb_select is only needed when rb_thread_fd_select is NOT available */
|
|
1822
|
+
#if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
|
|
475
1823
|
struct _select_set {
|
|
476
1824
|
int maxfd;
|
|
477
1825
|
fd_set *fdread, *fdwrite, *fdexcep;
|
|
@@ -483,6 +1831,14 @@ static VALUE curb_select(void *args) {
|
|
|
483
1831
|
int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
|
|
484
1832
|
return INT2FIX(rc);
|
|
485
1833
|
}
|
|
1834
|
+
|
|
1835
|
+
#ifdef HAVE_RB_THREAD_CALL_WITHOUT_GVL
|
|
1836
|
+
static void *curb_select_without_gvl(void *args) {
|
|
1837
|
+
struct _select_set* set = args;
|
|
1838
|
+
int rc = select(set->maxfd, set->fdread, set->fdwrite, set->fdexcep, set->tv);
|
|
1839
|
+
return (void *)(intptr_t)rc;
|
|
1840
|
+
}
|
|
1841
|
+
#endif
|
|
486
1842
|
#endif
|
|
487
1843
|
|
|
488
1844
|
/*
|
|
@@ -500,34 +1856,49 @@ static VALUE curb_select(void *args) {
|
|
|
500
1856
|
*
|
|
501
1857
|
* Run multi handles, looping selecting when data can be transfered
|
|
502
1858
|
*/
|
|
503
|
-
VALUE
|
|
1859
|
+
static VALUE ruby_curl_multi_perform_impl(int argc, VALUE *argv, VALUE self) {
|
|
504
1860
|
CURLMcode mcode;
|
|
505
1861
|
ruby_curl_multi *rbcm;
|
|
506
|
-
int maxfd, rc;
|
|
1862
|
+
int maxfd, rc = -1;
|
|
507
1863
|
fd_set fdread, fdwrite, fdexcep;
|
|
508
1864
|
#ifdef _WIN32
|
|
509
1865
|
fd_set crt_fdread, crt_fdwrite, crt_fdexcep;
|
|
510
1866
|
#endif
|
|
511
1867
|
long timeout_milliseconds;
|
|
512
1868
|
struct timeval tv = {0, 0};
|
|
1869
|
+
struct timeval tv_100ms = {0, 100000};
|
|
513
1870
|
VALUE block = Qnil;
|
|
514
|
-
#
|
|
1871
|
+
#if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
|
|
515
1872
|
struct _select_set fdset_args;
|
|
516
1873
|
#endif
|
|
517
1874
|
|
|
518
1875
|
rb_scan_args(argc, argv, "0&", &block);
|
|
519
1876
|
|
|
520
|
-
|
|
1877
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
1878
|
+
ruby_curl_multi_ensure_handle(rbcm);
|
|
1879
|
+
if (!rb_ivar_defined(self, id_deferred_exception_ivar)) {
|
|
1880
|
+
clear_multi_deferred_exception_source_id_if_any(self);
|
|
1881
|
+
}
|
|
521
1882
|
|
|
522
1883
|
timeout_milliseconds = cCurlMutiDefaulttimeout;
|
|
523
1884
|
|
|
1885
|
+
// Run curl_multi_perform for the first time to get the ball rolling
|
|
524
1886
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
1887
|
+
|
|
1888
|
+
// Check the easy handles for new messages one more time before yielding
|
|
1889
|
+
// control to passed ruby block.
|
|
1890
|
+
//
|
|
1891
|
+
// This call will block until all queued messages are processed and if any
|
|
1892
|
+
// handle completed the transfer we will run the on_complete callback here too.
|
|
525
1893
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
526
|
-
|
|
527
|
-
|
|
1894
|
+
|
|
1895
|
+
// There are no more messages to handle by curl and we can run the ruby block
|
|
1896
|
+
// passed to perform method.
|
|
1897
|
+
// When the block completes curl will resume.
|
|
1898
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1899
|
+
|
|
528
1900
|
do {
|
|
529
1901
|
while (rbcm->running) {
|
|
530
|
-
|
|
531
1902
|
#ifdef HAVE_CURL_MULTI_TIMEOUT
|
|
532
1903
|
/* get the curl suggested time out */
|
|
533
1904
|
mcode = curl_multi_timeout(rbcm->handle, &timeout_milliseconds);
|
|
@@ -542,7 +1913,12 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
542
1913
|
if (timeout_milliseconds == 0) { /* no delay */
|
|
543
1914
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
544
1915
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
545
|
-
|
|
1916
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1917
|
+
#if defined(HAVE_RB_FIBER_SCHEDULER_CURRENT)
|
|
1918
|
+
if (rb_fiber_scheduler_current() != Qnil) {
|
|
1919
|
+
rb_thread_schedule();
|
|
1920
|
+
}
|
|
1921
|
+
#endif
|
|
546
1922
|
continue;
|
|
547
1923
|
}
|
|
548
1924
|
|
|
@@ -551,6 +1927,46 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
551
1927
|
/* or buggy versions libcurl sometimes reports huge timeouts... let's cap it */
|
|
552
1928
|
}
|
|
553
1929
|
|
|
1930
|
+
#if defined(HAVE_CURL_MULTI_WAIT) && !defined(HAVE_RB_THREAD_FD_SELECT)
|
|
1931
|
+
{
|
|
1932
|
+
struct wait_args wait_args;
|
|
1933
|
+
wait_args.handle = rbcm->handle;
|
|
1934
|
+
wait_args.timeout_ms = timeout_milliseconds;
|
|
1935
|
+
wait_args.numfds = 0;
|
|
1936
|
+
/*
|
|
1937
|
+
* When a Fiber scheduler is available (Ruby >= 3.x), rb_thread_fd_select
|
|
1938
|
+
* integrates with it. If we have rb_thread_fd_select available at build
|
|
1939
|
+
* time, we avoid curl_multi_wait entirely (see preprocessor guard above)
|
|
1940
|
+
* and use the fdset branch below. Otherwise, we use curl_multi_wait and
|
|
1941
|
+
* release the GVL so Ruby threads can continue to run.
|
|
1942
|
+
*/
|
|
1943
|
+
CURLMcode wait_rc;
|
|
1944
|
+
#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
1945
|
+
wait_rc = (CURLMcode)(intptr_t)rb_thread_call_without_gvl(
|
|
1946
|
+
curl_multi_wait_wrapper, &wait_args, RUBY_UBF_IO, NULL
|
|
1947
|
+
);
|
|
1948
|
+
#else
|
|
1949
|
+
wait_rc = curl_multi_wait(rbcm->handle, NULL, 0, timeout_milliseconds, &wait_args.numfds);
|
|
1950
|
+
#endif
|
|
1951
|
+
if (wait_rc != CURLM_OK) {
|
|
1952
|
+
raise_curl_multi_error_exception(wait_rc);
|
|
1953
|
+
}
|
|
1954
|
+
if (wait_args.numfds == 0) {
|
|
1955
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
1956
|
+
struct timeval tv_sleep = tv_100ms;
|
|
1957
|
+
/* Sleep in a scheduler-aware way. */
|
|
1958
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
1959
|
+
#else
|
|
1960
|
+
rb_thread_wait_for(tv_100ms);
|
|
1961
|
+
#endif
|
|
1962
|
+
}
|
|
1963
|
+
/* Process pending transfers after waiting */
|
|
1964
|
+
rb_curl_multi_run(self, rbcm->handle, &(rbcm->running));
|
|
1965
|
+
rb_curl_multi_read_info(self, rbcm->handle);
|
|
1966
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1967
|
+
}
|
|
1968
|
+
#else
|
|
1969
|
+
|
|
554
1970
|
tv.tv_sec = 0; /* never wait longer than 1 second */
|
|
555
1971
|
tv.tv_usec = (int)(timeout_milliseconds * 1000); /* XXX: int is the right type for OSX, what about linux? */
|
|
556
1972
|
|
|
@@ -564,21 +1980,68 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
564
1980
|
raise_curl_multi_error_exception(mcode);
|
|
565
1981
|
}
|
|
566
1982
|
|
|
1983
|
+
if (maxfd == -1) {
|
|
1984
|
+
/* libcurl recommends sleeping for 100ms */
|
|
1985
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
1986
|
+
struct timeval tv_sleep = tv_100ms;
|
|
1987
|
+
rb_thread_fd_select(0, NULL, NULL, NULL, &tv_sleep);
|
|
1988
|
+
#else
|
|
1989
|
+
rb_thread_wait_for(tv_100ms);
|
|
1990
|
+
#endif
|
|
1991
|
+
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
1992
|
+
rb_curl_multi_read_info( self, rbcm->handle );
|
|
1993
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
567
1997
|
#ifdef _WIN32
|
|
568
1998
|
create_crt_fd(&fdread, &crt_fdread);
|
|
569
1999
|
create_crt_fd(&fdwrite, &crt_fdwrite);
|
|
570
2000
|
create_crt_fd(&fdexcep, &crt_fdexcep);
|
|
571
2001
|
#endif
|
|
572
2002
|
|
|
573
|
-
|
|
2003
|
+
|
|
2004
|
+
#if !defined(HAVE_RB_THREAD_FD_SELECT) && (defined(HAVE_RB_THREAD_BLOCKING_REGION) || defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL))
|
|
574
2005
|
fdset_args.maxfd = maxfd+1;
|
|
575
2006
|
fdset_args.fdread = &fdread;
|
|
576
2007
|
fdset_args.fdwrite = &fdwrite;
|
|
577
2008
|
fdset_args.fdexcep = &fdexcep;
|
|
578
2009
|
fdset_args.tv = &tv;
|
|
2010
|
+
#endif
|
|
2011
|
+
|
|
2012
|
+
#ifdef HAVE_RB_THREAD_FD_SELECT
|
|
2013
|
+
/* Prefer scheduler-aware waiting when available. Build rb_fdset_t sets. */
|
|
2014
|
+
{
|
|
2015
|
+
rb_fdset_t rfds, wfds, efds;
|
|
2016
|
+
rb_fd_init(&rfds);
|
|
2017
|
+
rb_fd_init(&wfds);
|
|
2018
|
+
rb_fd_init(&efds);
|
|
2019
|
+
#ifdef _WIN32
|
|
2020
|
+
/* On Windows, iterate explicit fd arrays for CRT fds. */
|
|
2021
|
+
int i;
|
|
2022
|
+
for (i = 0; i < crt_fdread.fd_count; i++) rb_fd_set(crt_fdread.fd_array[i], &rfds);
|
|
2023
|
+
for (i = 0; i < crt_fdwrite.fd_count; i++) rb_fd_set(crt_fdwrite.fd_array[i], &wfds);
|
|
2024
|
+
for (i = 0; i < crt_fdexcep.fd_count; i++) rb_fd_set(crt_fdexcep.fd_array[i], &efds);
|
|
2025
|
+
rc = rb_thread_fd_select(0, &rfds, &wfds, &efds, &tv);
|
|
2026
|
+
#else
|
|
2027
|
+
int fd;
|
|
2028
|
+
for (fd = 0; fd <= maxfd; fd++) {
|
|
2029
|
+
if (FD_ISSET(fd, &fdread)) rb_fd_set(fd, &rfds);
|
|
2030
|
+
if (FD_ISSET(fd, &fdwrite)) rb_fd_set(fd, &wfds);
|
|
2031
|
+
if (FD_ISSET(fd, &fdexcep)) rb_fd_set(fd, &efds);
|
|
2032
|
+
}
|
|
2033
|
+
rc = rb_thread_fd_select(maxfd+1, &rfds, &wfds, &efds, &tv);
|
|
2034
|
+
#endif
|
|
2035
|
+
rb_fd_term(&rfds);
|
|
2036
|
+
rb_fd_term(&wfds);
|
|
2037
|
+
rb_fd_term(&efds);
|
|
2038
|
+
}
|
|
2039
|
+
#elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
|
|
2040
|
+
rc = (int)(intptr_t) rb_thread_call_without_gvl(curb_select_without_gvl, &fdset_args, RUBY_UBF_IO, 0);
|
|
2041
|
+
#elif HAVE_RB_THREAD_BLOCKING_REGION
|
|
579
2042
|
rc = rb_thread_blocking_region(curb_select, &fdset_args, RUBY_UBF_IO, 0);
|
|
580
2043
|
#else
|
|
581
|
-
rc =
|
|
2044
|
+
rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &tv);
|
|
582
2045
|
#endif
|
|
583
2046
|
|
|
584
2047
|
#ifdef _WIN32
|
|
@@ -597,39 +2060,162 @@ VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
|
597
2060
|
default: /* action */
|
|
598
2061
|
rb_curl_multi_run( self, rbcm->handle, &(rbcm->running) );
|
|
599
2062
|
rb_curl_multi_read_info( self, rbcm->handle );
|
|
600
|
-
|
|
2063
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
601
2064
|
break;
|
|
602
2065
|
}
|
|
2066
|
+
#endif /* disabled curl_multi_wait: use fdsets */
|
|
603
2067
|
}
|
|
604
2068
|
|
|
2069
|
+
rb_curl_multi_read_info( self, rbcm->handle );
|
|
2070
|
+
rb_curl_multi_yield_if_given(self, block);
|
|
605
2071
|
} while( rbcm->running );
|
|
606
2072
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
2073
|
+
if (cCurlMutiAutoClose == 1) {
|
|
2074
|
+
rbcm->allow_close_during_perform = 1;
|
|
2075
|
+
rb_funcall(self, rb_intern("_autoclose"), 0);
|
|
2076
|
+
rbcm->allow_close_during_perform = 0;
|
|
2077
|
+
}
|
|
610
2078
|
return Qtrue;
|
|
611
2079
|
}
|
|
612
2080
|
|
|
2081
|
+
struct multi_perform_call_args {
|
|
2082
|
+
int argc;
|
|
2083
|
+
VALUE *argv;
|
|
2084
|
+
VALUE self;
|
|
2085
|
+
VALUE result;
|
|
2086
|
+
VALUE (*func)(int, VALUE *, VALUE);
|
|
2087
|
+
};
|
|
2088
|
+
|
|
2089
|
+
static VALUE ruby_curl_multi_perform_guard_body(VALUE argp) {
|
|
2090
|
+
struct multi_perform_call_args *args = (struct multi_perform_call_args *)argp;
|
|
2091
|
+
args->result = args->func(args->argc, args->argv, args->self);
|
|
2092
|
+
return args->result;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
static VALUE ruby_curl_multi_perform_guard_ensure(VALUE self) {
|
|
2096
|
+
ruby_curl_multi *rbcm;
|
|
2097
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2098
|
+
rbcm->perform_active = 0;
|
|
2099
|
+
rbcm->callback_active = 0;
|
|
2100
|
+
rbcm->allow_close_during_perform = 0;
|
|
2101
|
+
return Qnil;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
static VALUE ruby_curl_multi_with_perform_guard(int argc, VALUE *argv, VALUE self, VALUE (*func)(int, VALUE *, VALUE)) {
|
|
2105
|
+
ruby_curl_multi *rbcm;
|
|
2106
|
+
struct multi_perform_call_args args;
|
|
2107
|
+
|
|
2108
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2109
|
+
if (rbcm->perform_active) {
|
|
2110
|
+
rb_raise(rb_eRuntimeError, "Cannot recursively perform an active Curl::Multi handle");
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
rbcm->perform_active = 1;
|
|
2114
|
+
args.argc = argc;
|
|
2115
|
+
args.argv = argv;
|
|
2116
|
+
args.self = self;
|
|
2117
|
+
args.result = Qnil;
|
|
2118
|
+
args.func = func;
|
|
2119
|
+
|
|
2120
|
+
return rb_ensure(ruby_curl_multi_perform_guard_body, (VALUE)&args,
|
|
2121
|
+
ruby_curl_multi_perform_guard_ensure, self);
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
VALUE ruby_curl_multi_perform(int argc, VALUE *argv, VALUE self) {
|
|
2125
|
+
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_perform_impl);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2129
|
+
VALUE ruby_curl_multi_socket_perform(int argc, VALUE *argv, VALUE self) {
|
|
2130
|
+
return ruby_curl_multi_with_perform_guard(argc, argv, self, ruby_curl_multi_socket_perform_impl);
|
|
2131
|
+
}
|
|
2132
|
+
#endif
|
|
2133
|
+
|
|
2134
|
+
/*
|
|
2135
|
+
* call-seq:
|
|
2136
|
+
*
|
|
2137
|
+
* multi.close
|
|
2138
|
+
* after closing the multi handle all connections will be closed and the handle will no longer be usable
|
|
2139
|
+
*
|
|
2140
|
+
*/
|
|
2141
|
+
VALUE ruby_curl_multi_close(VALUE self) {
|
|
2142
|
+
ruby_curl_multi *rbcm;
|
|
2143
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2144
|
+
|
|
2145
|
+
if ((rbcm->perform_active || rbcm->callback_active) && !rbcm->allow_close_during_perform) {
|
|
2146
|
+
rb_raise(rb_eRuntimeError, "Cannot close an active Curl::Multi handle during perform");
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
rb_curl_multi_detach_all(rbcm);
|
|
2150
|
+
|
|
2151
|
+
if (rbcm->handle) {
|
|
2152
|
+
curl_multi_cleanup(rbcm->handle);
|
|
2153
|
+
rbcm->handle = NULL;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
rbcm->active = 0;
|
|
2157
|
+
rbcm->running = 0;
|
|
2158
|
+
clear_multi_deferred_exception_if_any(self);
|
|
2159
|
+
clear_multi_deferred_exception_source_id_if_any(self);
|
|
2160
|
+
return self;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
static VALUE ruby_curl_multi_mark_closed(VALUE self) {
|
|
2164
|
+
ruby_curl_multi *rbcm;
|
|
2165
|
+
|
|
2166
|
+
TypedData_Get_Struct(self, ruby_curl_multi, &ruby_curl_multi_data_type, rbcm);
|
|
2167
|
+
rbcm->closed = 1;
|
|
2168
|
+
|
|
2169
|
+
return self;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
/* GC mark: keep attached easy VALUEs alive while associated. */
|
|
2173
|
+
static int mark_attached_i(st_data_t key, st_data_t val, st_data_t arg) {
|
|
2174
|
+
VALUE easy = (VALUE)val;
|
|
2175
|
+
if (!NIL_P(easy)) rb_gc_mark(easy);
|
|
2176
|
+
return ST_CONTINUE;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
static void curl_multi_mark(void *ptr) {
|
|
2180
|
+
ruby_curl_multi *rbcm = (ruby_curl_multi *)ptr;
|
|
2181
|
+
if (!rbcm) return;
|
|
2182
|
+
if (rbcm->attached) {
|
|
2183
|
+
st_foreach(rbcm->attached, mark_attached_i, (st_data_t)0);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
|
|
613
2188
|
/* =================== INIT LIB =====================*/
|
|
614
2189
|
void init_curb_multi() {
|
|
615
2190
|
idCall = rb_intern("call");
|
|
616
|
-
|
|
2191
|
+
id_deferred_exception_ivar = rb_intern("@__curb_deferred_exception");
|
|
2192
|
+
id_deferred_exception_source_id_ivar = rb_intern("@__curb_deferred_exception_source_id");
|
|
2193
|
+
id_socket_io_cache_ivar = rb_intern("@__curb_socket_io_cache");
|
|
617
2194
|
cCurlMulti = rb_define_class_under(mCurl, "Multi", rb_cObject);
|
|
618
2195
|
|
|
2196
|
+
rb_define_alloc_func(cCurlMulti, ruby_curl_multi_alloc);
|
|
2197
|
+
|
|
619
2198
|
/* Class methods */
|
|
620
|
-
rb_define_singleton_method(cCurlMulti, "new", ruby_curl_multi_new, 0);
|
|
621
2199
|
rb_define_singleton_method(cCurlMulti, "default_timeout=", ruby_curl_multi_set_default_timeout, 1);
|
|
622
2200
|
rb_define_singleton_method(cCurlMulti, "default_timeout", ruby_curl_multi_get_default_timeout, 0);
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
rb_define_method(cCurlMulti, "
|
|
627
|
-
|
|
628
|
-
/* Instnace methods */
|
|
2201
|
+
rb_define_singleton_method(cCurlMulti, "autoclose=", ruby_curl_multi_set_autoclose, 1);
|
|
2202
|
+
rb_define_singleton_method(cCurlMulti, "autoclose", ruby_curl_multi_get_autoclose, 0);
|
|
2203
|
+
/* Instance methods */
|
|
2204
|
+
rb_define_method(cCurlMulti, "initialize", ruby_curl_multi_initialize, 0);
|
|
629
2205
|
rb_define_method(cCurlMulti, "max_connects=", ruby_curl_multi_max_connects, 1);
|
|
2206
|
+
rb_define_method(cCurlMulti, "max_host_connections=", ruby_curl_multi_max_host_connections, 1);
|
|
630
2207
|
rb_define_method(cCurlMulti, "pipeline=", ruby_curl_multi_pipeline, 1);
|
|
631
|
-
rb_define_method(cCurlMulti, "
|
|
632
|
-
rb_define_method(cCurlMulti, "
|
|
633
|
-
|
|
2208
|
+
rb_define_method(cCurlMulti, "_add", ruby_curl_multi_add, 1);
|
|
2209
|
+
rb_define_method(cCurlMulti, "_remove", ruby_curl_multi_remove, 1);
|
|
2210
|
+
/*
|
|
2211
|
+
* The legacy fdset loop is the stable default. The newer socket-action path
|
|
2212
|
+
* is kept in-tree, but it has shown scheduler regressions for one-handle
|
|
2213
|
+
* multi usage (for example Curl::Easy#perform under Async).
|
|
2214
|
+
*/
|
|
634
2215
|
rb_define_method(cCurlMulti, "perform", ruby_curl_multi_perform, -1);
|
|
2216
|
+
#if defined(HAVE_CURL_MULTI_SOCKET_ACTION) && defined(HAVE_CURLMOPT_SOCKETFUNCTION) && defined(HAVE_CURLMOPT_TIMERFUNCTION) && defined(HAVE_RB_THREAD_FD_SELECT) && !defined(_WIN32)
|
|
2217
|
+
rb_define_private_method(cCurlMulti, "_socket_perform", ruby_curl_multi_socket_perform, -1);
|
|
2218
|
+
#endif
|
|
2219
|
+
rb_define_method(cCurlMulti, "_close", ruby_curl_multi_close, 0);
|
|
2220
|
+
rb_define_private_method(cCurlMulti, "_mark_closed", ruby_curl_multi_mark_closed, 0);
|
|
635
2221
|
}
|