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