fast_curl 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +56 -12
- data/ext/fast_curl/fast_curl.c +822 -193
- data/lib/fast_curl/version.rb +1 -1
- data/lib/fast_curl.rb +31 -16
- metadata +3 -6
data/ext/fast_curl/fast_curl.c
CHANGED
|
@@ -20,18 +20,22 @@
|
|
|
20
20
|
#include <strings.h>
|
|
21
21
|
#include <time.h>
|
|
22
22
|
|
|
23
|
+
void Init_fast_curl(void);
|
|
24
|
+
|
|
23
25
|
#if defined(__GNUC__) || defined(__clang__)
|
|
24
26
|
#define FAST_CURL_NORETURN __attribute__((noreturn))
|
|
25
27
|
#else
|
|
26
28
|
#define FAST_CURL_NORETURN
|
|
27
29
|
#endif
|
|
28
30
|
|
|
29
|
-
#define MAX_RESPONSE_SIZE (100 * 1024 * 1024)
|
|
31
|
+
#define MAX_RESPONSE_SIZE ((size_t)100 * 1024 * 1024)
|
|
32
|
+
#define MAX_RESPONSE_HEADER_SIZE ((size_t)1024 * 1024)
|
|
30
33
|
#define MAX_REDIRECTS 5
|
|
31
34
|
#define MAX_TIMEOUT 300
|
|
32
35
|
#define MAX_RETRIES 10
|
|
33
36
|
#define MAX_REQUESTS 10000
|
|
34
37
|
#define MAX_CONNECTIONS 100
|
|
38
|
+
#define MAX_BUFFER 10000
|
|
35
39
|
#define MAX_RETRY_DELAY_MS 30000
|
|
36
40
|
#define DEFAULT_RETRIES 1
|
|
37
41
|
#define DEFAULT_RETRY_DELAY 100
|
|
@@ -72,6 +76,8 @@ typedef enum {
|
|
|
72
76
|
KEY_EFFECTIVE_URL,
|
|
73
77
|
KEY_ERROR,
|
|
74
78
|
KEY_ATTEMPTS,
|
|
79
|
+
KEY_BUFFER,
|
|
80
|
+
KEY_ACCEPT,
|
|
75
81
|
KEY_LAST
|
|
76
82
|
} key_id_t;
|
|
77
83
|
|
|
@@ -101,6 +107,8 @@ static const char *const KEY_NAMES[KEY_LAST] = {
|
|
|
101
107
|
"effective_url",
|
|
102
108
|
"error",
|
|
103
109
|
"attempts",
|
|
110
|
+
"buffer",
|
|
111
|
+
"accept",
|
|
104
112
|
};
|
|
105
113
|
|
|
106
114
|
typedef enum {
|
|
@@ -253,29 +261,63 @@ typedef struct {
|
|
|
253
261
|
|
|
254
262
|
typedef struct {
|
|
255
263
|
header_entry_t *entries;
|
|
256
|
-
|
|
257
|
-
|
|
264
|
+
size_t count;
|
|
265
|
+
size_t cap;
|
|
266
|
+
size_t total_bytes;
|
|
258
267
|
} header_list_t;
|
|
259
268
|
|
|
260
269
|
static inline void header_list_init(header_list_t *h) {
|
|
261
270
|
h->entries = NULL;
|
|
262
271
|
h->count = 0;
|
|
263
272
|
h->cap = 0;
|
|
273
|
+
h->total_bytes = 0;
|
|
264
274
|
}
|
|
265
275
|
|
|
266
276
|
static void header_list_free(header_list_t *h) {
|
|
267
|
-
for (
|
|
277
|
+
for (size_t i = 0; i < h->count; i++)
|
|
268
278
|
free(h->entries[i].str);
|
|
269
279
|
free(h->entries);
|
|
270
280
|
h->entries = NULL;
|
|
271
281
|
h->count = 0;
|
|
272
282
|
h->cap = 0;
|
|
283
|
+
h->total_bytes = 0;
|
|
273
284
|
}
|
|
274
285
|
|
|
275
286
|
static void header_list_reset(header_list_t *h) {
|
|
276
|
-
for (
|
|
287
|
+
for (size_t i = 0; i < h->count; i++)
|
|
277
288
|
free(h->entries[i].str);
|
|
278
289
|
h->count = 0;
|
|
290
|
+
h->total_bytes = 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static int header_list_add_bytes(header_list_t *h, size_t total) {
|
|
294
|
+
if (h->total_bytes > MAX_RESPONSE_HEADER_SIZE ||
|
|
295
|
+
total > MAX_RESPONSE_HEADER_SIZE - h->total_bytes)
|
|
296
|
+
return 0;
|
|
297
|
+
|
|
298
|
+
h->total_bytes += total;
|
|
299
|
+
return 1;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
static int header_list_reserve(header_list_t *h) {
|
|
303
|
+
if (h->count < h->cap)
|
|
304
|
+
return 1;
|
|
305
|
+
|
|
306
|
+
size_t new_cap = h->cap ? h->cap : INITIAL_HEADER_CAP;
|
|
307
|
+
if (h->cap && h->cap > SIZE_MAX / 2)
|
|
308
|
+
return 0;
|
|
309
|
+
if (h->cap)
|
|
310
|
+
new_cap = h->cap * 2;
|
|
311
|
+
if (new_cap > SIZE_MAX / sizeof(*h->entries))
|
|
312
|
+
return 0;
|
|
313
|
+
|
|
314
|
+
header_entry_t *entries = realloc(h->entries, sizeof(*h->entries) * new_cap);
|
|
315
|
+
if (!entries)
|
|
316
|
+
return 0;
|
|
317
|
+
|
|
318
|
+
h->entries = entries;
|
|
319
|
+
h->cap = new_cap;
|
|
320
|
+
return 1;
|
|
279
321
|
}
|
|
280
322
|
|
|
281
323
|
static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
@@ -289,22 +331,18 @@ static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userda
|
|
|
289
331
|
while (stripped > 0 && (ptr[stripped - 1] == '\r' || ptr[stripped - 1] == '\n'))
|
|
290
332
|
stripped--;
|
|
291
333
|
|
|
292
|
-
|
|
334
|
+
int status_line = stripped >= 5 && memcmp(ptr, "HTTP/", 5) == 0;
|
|
335
|
+
if (status_line)
|
|
293
336
|
header_list_reset(h);
|
|
294
|
-
return total;
|
|
295
|
-
}
|
|
296
337
|
|
|
297
|
-
if (
|
|
338
|
+
if (!header_list_add_bytes(h, total))
|
|
339
|
+
return 0;
|
|
340
|
+
|
|
341
|
+
if (status_line || stripped == 0)
|
|
298
342
|
return total;
|
|
299
343
|
|
|
300
|
-
if (h
|
|
301
|
-
|
|
302
|
-
header_entry_t *ne = realloc(h->entries, sizeof(header_entry_t) * new_cap);
|
|
303
|
-
if (!ne)
|
|
304
|
-
return 0;
|
|
305
|
-
h->entries = ne;
|
|
306
|
-
h->cap = new_cap;
|
|
307
|
-
}
|
|
344
|
+
if (!header_list_reserve(h))
|
|
345
|
+
return 0;
|
|
308
346
|
|
|
309
347
|
char *entry = malloc(stripped + 1);
|
|
310
348
|
if (!entry)
|
|
@@ -320,7 +358,7 @@ static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userda
|
|
|
320
358
|
|
|
321
359
|
typedef struct {
|
|
322
360
|
CURL *easy;
|
|
323
|
-
|
|
361
|
+
long index;
|
|
324
362
|
buffer_t body;
|
|
325
363
|
header_list_t headers;
|
|
326
364
|
struct curl_slist *req_headers;
|
|
@@ -341,7 +379,7 @@ static VALUE fast_validation_error = Qnil;
|
|
|
341
379
|
(ctx)->setup_error_fatal = (fatal); \
|
|
342
380
|
} while (0)
|
|
343
381
|
|
|
344
|
-
static inline void request_ctx_init(request_ctx_t *ctx,
|
|
382
|
+
static inline void request_ctx_init(request_ctx_t *ctx, long index) {
|
|
345
383
|
ctx->easy = NULL;
|
|
346
384
|
ctx->index = index;
|
|
347
385
|
buffer_init(&ctx->body);
|
|
@@ -410,7 +448,7 @@ typedef struct {
|
|
|
410
448
|
int still_running;
|
|
411
449
|
long timeout_ms;
|
|
412
450
|
int max_connections;
|
|
413
|
-
|
|
451
|
+
int cancelled;
|
|
414
452
|
|
|
415
453
|
int active_count;
|
|
416
454
|
int pending_pos;
|
|
@@ -418,12 +456,21 @@ typedef struct {
|
|
|
418
456
|
int *pending_indices;
|
|
419
457
|
} multi_session_t;
|
|
420
458
|
|
|
459
|
+
static int session_cancelled(const multi_session_t *session) {
|
|
460
|
+
return __atomic_load_n(&session->cancelled, __ATOMIC_ACQUIRE);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
static void cancel_session(multi_session_t *session) {
|
|
464
|
+
__atomic_store_n(&session->cancelled, 1, __ATOMIC_RELEASE);
|
|
465
|
+
}
|
|
466
|
+
|
|
421
467
|
typedef struct {
|
|
422
468
|
int max_retries;
|
|
423
469
|
int retries_explicit;
|
|
424
470
|
long retry_delay_ms;
|
|
425
471
|
int *retry_http_codes;
|
|
426
472
|
int retry_http_count;
|
|
473
|
+
VALUE retry_code_values;
|
|
427
474
|
int retry_non_idempotent;
|
|
428
475
|
} retry_config_t;
|
|
429
476
|
|
|
@@ -566,7 +613,7 @@ static VALUE build_response(request_ctx_t *ctx) {
|
|
|
566
613
|
curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &status);
|
|
567
614
|
|
|
568
615
|
VALUE headers_hash = new_headers_hash();
|
|
569
|
-
for (
|
|
616
|
+
for (size_t i = 0; i < ctx->headers.count; i++) {
|
|
570
617
|
const char *hdr = ctx->headers.entries[i].str;
|
|
571
618
|
size_t hdr_len = ctx->headers.entries[i].len;
|
|
572
619
|
const char *colon = memchr(hdr, ':', hdr_len);
|
|
@@ -593,7 +640,7 @@ static VALUE build_response(request_ctx_t *ctx) {
|
|
|
593
640
|
}
|
|
594
641
|
|
|
595
642
|
VALUE body_str =
|
|
596
|
-
ctx->body.data ? rb_str_new(ctx->body.data, ctx->body.len) : rb_str_new_cstr("");
|
|
643
|
+
ctx->body.data ? rb_str_new(ctx->body.data, (long)ctx->body.len) : rb_str_new_cstr("");
|
|
597
644
|
|
|
598
645
|
VALUE result = rb_hash_new();
|
|
599
646
|
rb_hash_aset(result, SYM(KEY_STATUS), LONG2NUM(status));
|
|
@@ -638,7 +685,7 @@ static int is_valid_url(const char *url) {
|
|
|
638
685
|
return 0;
|
|
639
686
|
if (strncmp(url, "https://", 8) == 0)
|
|
640
687
|
return 1;
|
|
641
|
-
if (
|
|
688
|
+
if (strncmp(url, "http://", 7) == 0)
|
|
642
689
|
return 1;
|
|
643
690
|
return 0;
|
|
644
691
|
}
|
|
@@ -881,7 +928,7 @@ static int setup_easy_handle(request_ctx_t *ctx, VALUE request, const request_op
|
|
|
881
928
|
SET_SETUP_ERROR(ctx, "Missing :url", 0);
|
|
882
929
|
return 0;
|
|
883
930
|
}
|
|
884
|
-
if (!RB_TYPE_P(url, T_STRING) || memchr(RSTRING_PTR(url), '\0', RSTRING_LEN(url))) {
|
|
931
|
+
if (!RB_TYPE_P(url, T_STRING) || memchr(RSTRING_PTR(url), '\0', (size_t)RSTRING_LEN(url))) {
|
|
885
932
|
SET_SETUP_ERROR(ctx, "Invalid URL", 1);
|
|
886
933
|
return 0;
|
|
887
934
|
}
|
|
@@ -929,6 +976,67 @@ static int setup_easy_handle(request_ctx_t *ctx, VALUE request, const request_op
|
|
|
929
976
|
return 1;
|
|
930
977
|
}
|
|
931
978
|
|
|
979
|
+
typedef struct {
|
|
980
|
+
request_ctx_t request;
|
|
981
|
+
VALUE value;
|
|
982
|
+
const request_options_t *options;
|
|
983
|
+
long index;
|
|
984
|
+
int fatal;
|
|
985
|
+
char message[128];
|
|
986
|
+
} request_probe_t;
|
|
987
|
+
|
|
988
|
+
static VALUE request_probe_body(VALUE arg) {
|
|
989
|
+
request_probe_t *probe = (request_probe_t *)(uintptr_t)arg;
|
|
990
|
+
request_ctx_init(&probe->request, probe->index);
|
|
991
|
+
if (!request_ctx_prepare_easy(&probe->request))
|
|
992
|
+
return Qnil;
|
|
993
|
+
|
|
994
|
+
setup_easy_handle(&probe->request, probe->value, probe->options);
|
|
995
|
+
probe->fatal = probe->request.setup_error_fatal;
|
|
996
|
+
if (probe->fatal && probe->request.setup_error)
|
|
997
|
+
snprintf(probe->message, sizeof(probe->message), "%s (request %ld)",
|
|
998
|
+
probe->request.setup_error, probe->index);
|
|
999
|
+
return Qnil;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
static VALUE request_probe_ensure(VALUE arg) {
|
|
1003
|
+
request_probe_t *probe = (request_probe_t *)(uintptr_t)arg;
|
|
1004
|
+
request_ctx_free(&probe->request);
|
|
1005
|
+
return Qnil;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
static int validate_request_probe(VALUE request, long index, const request_options_t *options,
|
|
1009
|
+
char message[128]) {
|
|
1010
|
+
request_probe_t probe;
|
|
1011
|
+
memset(&probe, 0, sizeof(probe));
|
|
1012
|
+
probe.value = request;
|
|
1013
|
+
probe.options = options;
|
|
1014
|
+
probe.index = index;
|
|
1015
|
+
rb_ensure(request_probe_body, (VALUE)(uintptr_t)&probe, request_probe_ensure,
|
|
1016
|
+
(VALUE)(uintptr_t)&probe);
|
|
1017
|
+
if (probe.fatal)
|
|
1018
|
+
memcpy(message, probe.message, sizeof(probe.message));
|
|
1019
|
+
RB_GC_GUARD(request);
|
|
1020
|
+
return probe.fatal;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
static void validate_execute_requests(VALUE requests, int count, int target, VALUE options,
|
|
1024
|
+
const request_options_t *opts) {
|
|
1025
|
+
if (target > 0 && !NIL_P(options)) {
|
|
1026
|
+
VALUE accept = hash_aref_key(options, KEY_ACCEPT);
|
|
1027
|
+
if (!NIL_P(accept) && !rb_respond_to(accept, rb_intern("call")))
|
|
1028
|
+
rb_raise(rb_eArgError, "accept must respond to #call");
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
for (int i = 0; i < count; i++) {
|
|
1032
|
+
char message[128];
|
|
1033
|
+
if (validate_request_probe(rb_ary_entry(requests, i), i, opts, message)) {
|
|
1034
|
+
VALUE klass = NIL_P(fast_validation_error) ? rb_eArgError : fast_validation_error;
|
|
1035
|
+
rb_raise(klass, "%s", message);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
932
1040
|
/* Poll until something actually completes, or the slice budget expires.
|
|
933
1041
|
Previously this returned after every single 50ms poll, which under a fiber
|
|
934
1042
|
scheduler meant a fresh OS thread twenty times a second. Cancellation still
|
|
@@ -938,7 +1046,7 @@ static void *poll_without_gvl(void *arg) {
|
|
|
938
1046
|
long long started = fast_now_ms();
|
|
939
1047
|
int before = s->still_running;
|
|
940
1048
|
|
|
941
|
-
while (!s
|
|
1049
|
+
while (!session_cancelled(s)) {
|
|
942
1050
|
int numfds = 0;
|
|
943
1051
|
curl_multi_poll(s->multi, NULL, 0, POLL_TIMEOUT_MS, &numfds);
|
|
944
1052
|
curl_multi_perform(s->multi, &s->still_running);
|
|
@@ -953,7 +1061,7 @@ static void *poll_without_gvl(void *arg) {
|
|
|
953
1061
|
|
|
954
1062
|
static void unblock_perform(void *arg) {
|
|
955
1063
|
multi_session_t *s = (multi_session_t *)arg;
|
|
956
|
-
s
|
|
1064
|
+
cancel_session(s);
|
|
957
1065
|
#ifdef HAVE_CURL_MULTI_WAKEUP
|
|
958
1066
|
curl_multi_wakeup(s->multi);
|
|
959
1067
|
#endif
|
|
@@ -961,30 +1069,52 @@ static void unblock_perform(void *arg) {
|
|
|
961
1069
|
|
|
962
1070
|
typedef struct {
|
|
963
1071
|
VALUE results;
|
|
1072
|
+
VALUE accept;
|
|
964
1073
|
int completed;
|
|
965
1074
|
int target;
|
|
966
1075
|
int stream;
|
|
967
1076
|
} completion_ctx_t;
|
|
968
1077
|
|
|
969
|
-
static VALUE build_result_pair(
|
|
970
|
-
return rb_ary_new_from_args(2,
|
|
1078
|
+
static VALUE build_result_pair(long index, VALUE response) {
|
|
1079
|
+
return rb_ary_new_from_args(2, LONG2NUM(index), response);
|
|
971
1080
|
}
|
|
972
1081
|
|
|
973
|
-
static int
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
VALUE pair =
|
|
977
|
-
build_result_pair(index, build_error_response(message, ERR_INVALID_REQUEST, attempts));
|
|
1082
|
+
static int response_accepted(completion_ctx_t *cctx, VALUE response) {
|
|
1083
|
+
if (cctx->target <= 0 || NIL_P(cctx->accept))
|
|
1084
|
+
return 1;
|
|
978
1085
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
else
|
|
982
|
-
rb_ary_push(cctx->results, pair);
|
|
1086
|
+
return RTEST(rb_funcall(cctx->accept, rb_intern("call"), 1, response));
|
|
1087
|
+
}
|
|
983
1088
|
|
|
1089
|
+
static int record_response(completion_ctx_t *cctx, long index, VALUE response) {
|
|
1090
|
+
VALUE pair = build_result_pair(index, response);
|
|
1091
|
+
|
|
1092
|
+
if (cctx->stream) {
|
|
1093
|
+
rb_yield(pair);
|
|
984
1094
|
cctx->completed++;
|
|
985
|
-
|
|
986
|
-
|
|
1095
|
+
return 0;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
if (cctx->target > 0) {
|
|
1099
|
+
if (!response_accepted(cctx, response))
|
|
1100
|
+
return 0;
|
|
1101
|
+
|
|
1102
|
+
rb_ary_push(cctx->results, pair);
|
|
1103
|
+
cctx->completed++;
|
|
1104
|
+
return cctx->completed >= cctx->target;
|
|
987
1105
|
}
|
|
1106
|
+
|
|
1107
|
+
rb_ary_store(cctx->results, index, pair);
|
|
1108
|
+
cctx->completed++;
|
|
1109
|
+
return 0;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
static int record_immediate_error(completion_ctx_t *cctx, long index, const char *message,
|
|
1113
|
+
int attempts) {
|
|
1114
|
+
if (cctx->stream || cctx->target > 0)
|
|
1115
|
+
return record_response(cctx, index,
|
|
1116
|
+
build_error_response(message, ERR_INVALID_REQUEST, attempts));
|
|
1117
|
+
|
|
988
1118
|
return 0;
|
|
989
1119
|
}
|
|
990
1120
|
|
|
@@ -1019,17 +1149,11 @@ static int process_completed(multi_session_t *session, completion_ctx_t *cctx) {
|
|
|
1019
1149
|
? build_response(ctx)
|
|
1020
1150
|
: build_error_response_with_code(curl_easy_strerror(msg->data.result),
|
|
1021
1151
|
(int)msg->data.result, ctx->attempts);
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
else
|
|
1027
|
-
rb_ary_push(cctx->results, pair);
|
|
1152
|
+
if (record_response(cctx, ctx->index, response))
|
|
1153
|
+
return 1;
|
|
1154
|
+
} else {
|
|
1155
|
+
cctx->completed++;
|
|
1028
1156
|
}
|
|
1029
|
-
|
|
1030
|
-
cctx->completed++;
|
|
1031
|
-
if (cctx->target > 0 && cctx->completed >= cctx->target)
|
|
1032
|
-
return 1;
|
|
1033
1157
|
}
|
|
1034
1158
|
|
|
1035
1159
|
return 0;
|
|
@@ -1123,7 +1247,8 @@ static void run_multi_loop(multi_session_t *session, completion_ctx_t *cctx, VAL
|
|
|
1123
1247
|
if (process_completed(session, cctx))
|
|
1124
1248
|
return;
|
|
1125
1249
|
|
|
1126
|
-
while (!
|
|
1250
|
+
while (!session_cancelled(session) &&
|
|
1251
|
+
(session->active_count > 0 || pending_remaining(session))) {
|
|
1127
1252
|
if (opts->deadline_ms > 0 && fast_now_ms() >= opts->deadline_ms)
|
|
1128
1253
|
break;
|
|
1129
1254
|
|
|
@@ -1208,6 +1333,7 @@ static void retry_config_init(retry_config_t *retry_cfg) {
|
|
|
1208
1333
|
retry_cfg->retry_delay_ms = DEFAULT_RETRY_DELAY;
|
|
1209
1334
|
retry_cfg->retry_http_codes = NULL;
|
|
1210
1335
|
retry_cfg->retry_http_count = 0;
|
|
1336
|
+
retry_cfg->retry_code_values = Qnil;
|
|
1211
1337
|
retry_cfg->retry_non_idempotent = 0;
|
|
1212
1338
|
}
|
|
1213
1339
|
|
|
@@ -1266,22 +1392,33 @@ static void parse_retry_codes(VALUE options, retry_config_t *retry_cfg) {
|
|
|
1266
1392
|
rb_raise(rb_eArgError, "retry_codes is too large");
|
|
1267
1393
|
|
|
1268
1394
|
len = (int)len_long;
|
|
1395
|
+
VALUE normalized = rb_ary_new_capa(len);
|
|
1269
1396
|
for (int i = 0; i < len; i++) {
|
|
1270
1397
|
int code = NUM2INT(rb_ary_entry(codes, i));
|
|
1271
1398
|
if (code < 100 || code > 599)
|
|
1272
1399
|
rb_raise(rb_eArgError, "retry_codes must contain valid HTTP status codes");
|
|
1400
|
+
rb_ary_push(normalized, INT2NUM(code));
|
|
1273
1401
|
}
|
|
1274
1402
|
|
|
1275
|
-
|
|
1403
|
+
retry_cfg->retry_code_values = normalized;
|
|
1404
|
+
retry_cfg->retry_http_count = len;
|
|
1405
|
+
RB_GC_GUARD(codes);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
static void materialize_retry_codes(retry_config_t *retry_cfg) {
|
|
1409
|
+
if (retry_cfg->retry_http_count == 0)
|
|
1276
1410
|
return;
|
|
1277
1411
|
|
|
1278
|
-
|
|
1412
|
+
size_t count = (size_t)retry_cfg->retry_http_count;
|
|
1413
|
+
if (count > SIZE_MAX / sizeof(*retry_cfg->retry_http_codes))
|
|
1414
|
+
rb_raise(rb_eArgError, "retry_codes is too large");
|
|
1415
|
+
|
|
1416
|
+
retry_cfg->retry_http_codes = malloc(sizeof(*retry_cfg->retry_http_codes) * count);
|
|
1279
1417
|
if (!retry_cfg->retry_http_codes)
|
|
1280
1418
|
rb_raise(rb_eNoMemError, "failed to allocate retry codes");
|
|
1281
1419
|
|
|
1282
|
-
retry_cfg->retry_http_count
|
|
1283
|
-
|
|
1284
|
-
retry_cfg->retry_http_codes[i] = NUM2INT(rb_ary_entry(codes, i));
|
|
1420
|
+
for (int i = 0; i < retry_cfg->retry_http_count; i++)
|
|
1421
|
+
retry_cfg->retry_http_codes[i] = NUM2INT(rb_ary_entry(retry_cfg->retry_code_values, i));
|
|
1285
1422
|
}
|
|
1286
1423
|
|
|
1287
1424
|
static void parse_options(VALUE options, request_options_t *opts, int *max_conn,
|
|
@@ -1326,7 +1463,17 @@ static void parse_options(VALUE options, request_options_t *opts, int *max_conn,
|
|
|
1326
1463
|
retry_cfg->retry_delay_ms =
|
|
1327
1464
|
parse_long_option(options, KEY_RETRY_DELAY, "retry_delay", 0, MAX_RETRY_DELAY_MS,
|
|
1328
1465
|
retry_cfg->retry_delay_ms, NULL);
|
|
1329
|
-
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
static void configure_retry_mode(retry_config_t *retry_cfg, int target, int stream) {
|
|
1469
|
+
if (!stream && target <= 0)
|
|
1470
|
+
return;
|
|
1471
|
+
|
|
1472
|
+
if (retry_cfg->retries_explicit && retry_cfg->max_retries > 0 && stream)
|
|
1473
|
+
rb_warn("FastCurl: retries are not supported in stream_execute, ignoring retries option");
|
|
1474
|
+
if (retry_cfg->retries_explicit && retry_cfg->max_retries > 0 && target > 0)
|
|
1475
|
+
rb_warn("FastCurl: retries are not supported in first_execute, ignoring retries option");
|
|
1476
|
+
retry_cfg->max_retries = 0;
|
|
1330
1477
|
}
|
|
1331
1478
|
|
|
1332
1479
|
static void multi_session_init(multi_session_t *session, CURLM *multi, int count, int max_conn,
|
|
@@ -1389,6 +1536,27 @@ static FAST_CURL_NORETURN void cleanup_and_raise(cleanup_ctx_t *cleanup, VALUE e
|
|
|
1389
1536
|
rb_raise(exception, "%s", message);
|
|
1390
1537
|
}
|
|
1391
1538
|
|
|
1539
|
+
static void prepare_execute_session(multi_session_t *session, int **invalid, cleanup_ctx_t *cleanup,
|
|
1540
|
+
retry_config_t *retry_cfg, int count, int max_conn,
|
|
1541
|
+
long timeout_sec) {
|
|
1542
|
+
multi_session_init(session, curl_multi_init(), count, max_conn, timeout_sec);
|
|
1543
|
+
cleanup->session = session;
|
|
1544
|
+
cleanup->invalid = NULL;
|
|
1545
|
+
cleanup->retry_cfg = retry_cfg;
|
|
1546
|
+
if (!session->multi)
|
|
1547
|
+
cleanup_and_raise(cleanup, rb_eNoMemError, "failed to initialize curl multi handle");
|
|
1548
|
+
|
|
1549
|
+
multi_session_configure(session->multi, max_conn);
|
|
1550
|
+
session->requests = calloc((size_t)count, sizeof(*session->requests));
|
|
1551
|
+
if (!session->requests)
|
|
1552
|
+
cleanup_and_raise(cleanup, rb_eNoMemError, "failed to allocate request contexts");
|
|
1553
|
+
|
|
1554
|
+
*invalid = calloc((size_t)count, sizeof(**invalid));
|
|
1555
|
+
cleanup->invalid = *invalid;
|
|
1556
|
+
if (!*invalid)
|
|
1557
|
+
cleanup_and_raise(cleanup, rb_eNoMemError, "failed to allocate tracking array");
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1392
1560
|
typedef struct {
|
|
1393
1561
|
VALUE requests;
|
|
1394
1562
|
VALUE options;
|
|
@@ -1400,126 +1568,623 @@ typedef struct {
|
|
|
1400
1568
|
request_options_t *opts;
|
|
1401
1569
|
} execute_args_t;
|
|
1402
1570
|
|
|
1571
|
+
static void completion_context_init(completion_ctx_t *cctx, const execute_args_t *ea, int count) {
|
|
1572
|
+
cctx->results =
|
|
1573
|
+
ea->stream ? Qnil : (ea->target > 0 ? rb_ary_new2(ea->target) : rb_ary_new2(count));
|
|
1574
|
+
cctx->accept =
|
|
1575
|
+
ea->target > 0 && !NIL_P(ea->options) ? hash_aref_key(ea->options, KEY_ACCEPT) : Qnil;
|
|
1576
|
+
cctx->completed = 0;
|
|
1577
|
+
cctx->target = ea->target;
|
|
1578
|
+
cctx->stream = ea->stream;
|
|
1579
|
+
|
|
1580
|
+
if (!NIL_P(cctx->accept) && !rb_respond_to(cctx->accept, rb_intern("call")))
|
|
1581
|
+
rb_raise(rb_eArgError, "accept must respond to #call");
|
|
1582
|
+
|
|
1583
|
+
if (!ea->stream && ea->target <= 0) {
|
|
1584
|
+
for (int i = 0; i < count; i++)
|
|
1585
|
+
rb_ary_store(cctx->results, i, Qnil);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
static int retry_one_attempt(execute_args_t *ea, completion_ctx_t *cctx, int *deadline_hit,
|
|
1590
|
+
int attempt) {
|
|
1591
|
+
multi_session_t *session = ea->session;
|
|
1592
|
+
int count = session->count;
|
|
1593
|
+
request_options_t *opts = ea->opts;
|
|
1594
|
+
retry_config_t *retry_cfg = ea->retry_cfg;
|
|
1595
|
+
|
|
1596
|
+
if (opts->deadline_ms > 0 && fast_now_ms() >= opts->deadline_ms) {
|
|
1597
|
+
*deadline_hit = 1;
|
|
1598
|
+
return 0;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
int *retry_indices = ALLOCA_N(int, count);
|
|
1602
|
+
int retry_count = 0;
|
|
1603
|
+
for (int i = 0; i < count; i++) {
|
|
1604
|
+
if (!ea->invalid[i] && session->requests[i].done &&
|
|
1605
|
+
should_retry(&session->requests[i], retry_cfg))
|
|
1606
|
+
retry_indices[retry_count++] = i;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
if (retry_count == 0)
|
|
1610
|
+
return 0;
|
|
1611
|
+
|
|
1612
|
+
long backoff = retry_backoff_ms(retry_cfg, attempt);
|
|
1613
|
+
if (opts->deadline_ms > 0) {
|
|
1614
|
+
long long left = opts->deadline_ms - fast_now_ms();
|
|
1615
|
+
if (left <= 0) {
|
|
1616
|
+
*deadline_hit = 1;
|
|
1617
|
+
return 0;
|
|
1618
|
+
}
|
|
1619
|
+
if (backoff > (long)left)
|
|
1620
|
+
backoff = (long)left;
|
|
1621
|
+
}
|
|
1622
|
+
retry_delay_sleep(backoff);
|
|
1623
|
+
|
|
1624
|
+
int runnable_count = 0;
|
|
1625
|
+
for (int r = 0; r < retry_count; r++) {
|
|
1626
|
+
int idx = retry_indices[r];
|
|
1627
|
+
request_ctx_t *request = &session->requests[idx];
|
|
1628
|
+
if (!request_ctx_reset_for_retry(request)) {
|
|
1629
|
+
ea->invalid[idx] = 1;
|
|
1630
|
+
request->done = 1;
|
|
1631
|
+
request->setup_error = "Failed to allocate a curl handle for the retry";
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
retry_indices[runnable_count++] = idx;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
if (runnable_count > 0) {
|
|
1639
|
+
cctx->completed = 0;
|
|
1640
|
+
run_multi_loop(session, cctx, ea->requests, ea->invalid, opts, retry_indices,
|
|
1641
|
+
runnable_count);
|
|
1642
|
+
}
|
|
1643
|
+
return 1;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
static int retry_completed_requests(execute_args_t *ea, completion_ctx_t *cctx) {
|
|
1647
|
+
int deadline_hit = 0;
|
|
1648
|
+
for (int attempt = 0; attempt < ea->retry_cfg->max_retries; attempt++) {
|
|
1649
|
+
if (!retry_one_attempt(ea, cctx, &deadline_hit, attempt))
|
|
1650
|
+
break;
|
|
1651
|
+
}
|
|
1652
|
+
return deadline_hit;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
static void store_execution_results(execute_args_t *ea, completion_ctx_t *cctx, int deadline_hit) {
|
|
1656
|
+
multi_session_t *session = ea->session;
|
|
1657
|
+
request_options_t *opts = ea->opts;
|
|
1658
|
+
for (int i = 0; i < session->count; i++) {
|
|
1659
|
+
request_ctx_t *request = &session->requests[i];
|
|
1660
|
+
VALUE response;
|
|
1661
|
+
|
|
1662
|
+
if (ea->invalid[i]) {
|
|
1663
|
+
response = build_error_response(request->setup_error ? request->setup_error
|
|
1664
|
+
: "Invalid request configuration",
|
|
1665
|
+
ERR_INVALID_REQUEST, request->attempts);
|
|
1666
|
+
} else if (!request->done) {
|
|
1667
|
+
response = deadline_hit || (opts->deadline_ms > 0 && fast_now_ms() >= opts->deadline_ms)
|
|
1668
|
+
? build_error_response("Total timeout exceeded", ERR_DEADLINE,
|
|
1669
|
+
request->attempts)
|
|
1670
|
+
: build_error_response("Request was not completed", ERR_NOT_COMPLETED,
|
|
1671
|
+
request->attempts);
|
|
1672
|
+
} else if (request->curl_result == CURLE_OK) {
|
|
1673
|
+
response = build_response(request);
|
|
1674
|
+
} else {
|
|
1675
|
+
response = build_error_response_with_code(curl_easy_strerror(request->curl_result),
|
|
1676
|
+
(int)request->curl_result, request->attempts);
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
rb_ary_store(cctx->results, i, build_result_pair(i, response));
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1403
1683
|
static VALUE internal_execute_body(VALUE arg) {
|
|
1404
1684
|
execute_args_t *ea = (execute_args_t *)arg;
|
|
1405
|
-
VALUE requests = ea->requests;
|
|
1406
1685
|
multi_session_t *session = ea->session;
|
|
1407
|
-
int *invalid = ea->invalid;
|
|
1408
1686
|
retry_config_t *retry_cfg = ea->retry_cfg;
|
|
1409
1687
|
request_options_t *opts = ea->opts;
|
|
1410
|
-
int deadline_hit = 0;
|
|
1411
1688
|
int count = session->count;
|
|
1412
|
-
int target = ea->target;
|
|
1413
|
-
int stream = ea->stream;
|
|
1414
1689
|
|
|
1415
1690
|
for (int i = 0; i < count; i++)
|
|
1416
1691
|
request_ctx_init(&session->requests[i], i);
|
|
1417
1692
|
|
|
1418
1693
|
completion_ctx_t cctx;
|
|
1419
|
-
cctx
|
|
1420
|
-
cctx.completed = 0;
|
|
1421
|
-
cctx.target = target;
|
|
1422
|
-
cctx.stream = stream;
|
|
1423
|
-
|
|
1424
|
-
if (!stream && target <= 0) {
|
|
1425
|
-
for (int i = 0; i < count; i++)
|
|
1426
|
-
rb_ary_store(cctx.results, i, Qnil);
|
|
1427
|
-
}
|
|
1694
|
+
completion_context_init(&cctx, ea, count);
|
|
1428
1695
|
|
|
1429
1696
|
if (opts->total_timeout_ms > 0)
|
|
1430
1697
|
opts->deadline_ms = fast_now_ms() + opts->total_timeout_ms;
|
|
1431
1698
|
|
|
1432
|
-
run_multi_loop(session, &cctx, requests, invalid, opts, NULL, count);
|
|
1699
|
+
run_multi_loop(session, &cctx, ea->requests, ea->invalid, opts, NULL, count);
|
|
1433
1700
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
deadline_hit = 1;
|
|
1438
|
-
break;
|
|
1439
|
-
}
|
|
1701
|
+
int deadline_hit = 0;
|
|
1702
|
+
if (!ea->stream && ea->target <= 0 && retry_cfg->max_retries > 0)
|
|
1703
|
+
deadline_hit = retry_completed_requests(ea, &cctx);
|
|
1440
1704
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
rb_raise(rb_eNoMemError, "failed to allocate retry index array");
|
|
1705
|
+
if (!ea->stream && ea->target <= 0)
|
|
1706
|
+
store_execution_results(ea, &cctx, deadline_hit);
|
|
1444
1707
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
if (invalid[i] || !session->requests[i].done)
|
|
1448
|
-
continue;
|
|
1449
|
-
if (should_retry(&session->requests[i], retry_cfg))
|
|
1450
|
-
retry_indices[retry_count++] = i;
|
|
1451
|
-
}
|
|
1708
|
+
return ea->stream ? Qnil : cctx.results;
|
|
1709
|
+
}
|
|
1452
1710
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1711
|
+
typedef enum {
|
|
1712
|
+
LAZY_SLOT_FREE,
|
|
1713
|
+
LAZY_SLOT_PENDING,
|
|
1714
|
+
LAZY_SLOT_ACTIVE,
|
|
1715
|
+
LAZY_SLOT_RETRY_WAIT
|
|
1716
|
+
} lazy_slot_state_t;
|
|
1457
1717
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1718
|
+
typedef struct {
|
|
1719
|
+
multi_session_t session;
|
|
1720
|
+
request_options_t opts;
|
|
1721
|
+
retry_config_t retry_cfg;
|
|
1722
|
+
completion_ctx_t cctx;
|
|
1723
|
+
VALUE source;
|
|
1724
|
+
VALUE anchors;
|
|
1725
|
+
int *states;
|
|
1726
|
+
int *free_slots;
|
|
1727
|
+
int free_count;
|
|
1728
|
+
int *pending_slots;
|
|
1729
|
+
int pending_head;
|
|
1730
|
+
int pending_tail;
|
|
1731
|
+
int pending_count;
|
|
1732
|
+
long long *retry_at_ms;
|
|
1733
|
+
int capacity;
|
|
1734
|
+
int buffer_size;
|
|
1735
|
+
int occupied_count;
|
|
1736
|
+
long next_index;
|
|
1737
|
+
int deadline_hit;
|
|
1738
|
+
int stop;
|
|
1739
|
+
} lazy_execute_ctx_t;
|
|
1740
|
+
|
|
1741
|
+
static int lazy_pending_push(lazy_execute_ctx_t *ctx, int slot) {
|
|
1742
|
+
if (ctx->pending_count >= ctx->capacity)
|
|
1743
|
+
return 0;
|
|
1470
1744
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1745
|
+
ctx->pending_slots[ctx->pending_tail] = slot;
|
|
1746
|
+
ctx->pending_tail = (ctx->pending_tail + 1) % ctx->capacity;
|
|
1747
|
+
ctx->pending_count++;
|
|
1748
|
+
return 1;
|
|
1749
|
+
}
|
|
1475
1750
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
rc->setup_error = "Failed to allocate a curl handle for the retry";
|
|
1480
|
-
continue;
|
|
1481
|
-
}
|
|
1751
|
+
static int lazy_pending_pop(lazy_execute_ctx_t *ctx) {
|
|
1752
|
+
if (ctx->pending_count == 0)
|
|
1753
|
+
return -1;
|
|
1482
1754
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1755
|
+
int slot = ctx->pending_slots[ctx->pending_head];
|
|
1756
|
+
ctx->pending_head = (ctx->pending_head + 1) % ctx->capacity;
|
|
1757
|
+
ctx->pending_count--;
|
|
1758
|
+
return slot;
|
|
1759
|
+
}
|
|
1485
1760
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1761
|
+
static void lazy_release_slot(lazy_execute_ctx_t *ctx, int slot) {
|
|
1762
|
+
request_ctx_free(&ctx->session.requests[slot]);
|
|
1763
|
+
rb_ary_store(ctx->anchors, slot, Qnil);
|
|
1764
|
+
ctx->states[slot] = LAZY_SLOT_FREE;
|
|
1765
|
+
ctx->retry_at_ms[slot] = 0;
|
|
1766
|
+
ctx->free_slots[ctx->free_count++] = slot;
|
|
1767
|
+
if (ctx->occupied_count > 0)
|
|
1768
|
+
ctx->occupied_count--;
|
|
1769
|
+
}
|
|
1491
1770
|
|
|
1492
|
-
|
|
1771
|
+
static FAST_CURL_NORETURN void lazy_raise_setup_error(request_ctx_t *request) {
|
|
1772
|
+
VALUE klass = NIL_P(fast_validation_error) ? rb_eArgError : fast_validation_error;
|
|
1773
|
+
rb_raise(klass, "%s (request %ld)",
|
|
1774
|
+
request->setup_error ? request->setup_error : "Invalid request configuration",
|
|
1775
|
+
request->index);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
static int lazy_finalize_error(lazy_execute_ctx_t *ctx, int slot, const char *message) {
|
|
1779
|
+
request_ctx_t *request = &ctx->session.requests[slot];
|
|
1780
|
+
VALUE response = build_error_response(message, ERR_INVALID_REQUEST, request->attempts);
|
|
1781
|
+
int stop = record_response(&ctx->cctx, request->index, response);
|
|
1782
|
+
lazy_release_slot(ctx, slot);
|
|
1783
|
+
return stop;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
static int lazy_activate_pending(lazy_execute_ctx_t *ctx) {
|
|
1787
|
+
while (ctx->session.active_count < ctx->session.max_connections && ctx->pending_count > 0) {
|
|
1788
|
+
int slot = lazy_pending_pop(ctx);
|
|
1789
|
+
request_ctx_t *request = &ctx->session.requests[slot];
|
|
1790
|
+
VALUE request_value = rb_ary_entry(ctx->anchors, slot);
|
|
1791
|
+
|
|
1792
|
+
if (!request_ctx_prepare_easy(request)) {
|
|
1793
|
+
if (lazy_finalize_error(ctx, slot, "Failed to allocate a curl handle"))
|
|
1794
|
+
return 1;
|
|
1795
|
+
continue;
|
|
1493
1796
|
}
|
|
1797
|
+
|
|
1798
|
+
if (!setup_easy_handle(request, request_value, &ctx->opts)) {
|
|
1799
|
+
if (request->setup_error_fatal)
|
|
1800
|
+
lazy_raise_setup_error(request);
|
|
1801
|
+
if (lazy_finalize_error(ctx, slot,
|
|
1802
|
+
request->setup_error ? request->setup_error
|
|
1803
|
+
: "Invalid request configuration"))
|
|
1804
|
+
return 1;
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
if (curl_multi_add_handle(ctx->session.multi, request->easy) != CURLM_OK) {
|
|
1809
|
+
if (lazy_finalize_error(ctx, slot, "Failed to schedule the request"))
|
|
1810
|
+
return 1;
|
|
1811
|
+
continue;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
request->attempts++;
|
|
1815
|
+
request->active = 1;
|
|
1816
|
+
request->done = 0;
|
|
1817
|
+
ctx->states[slot] = LAZY_SLOT_ACTIVE;
|
|
1818
|
+
ctx->session.active_count++;
|
|
1494
1819
|
}
|
|
1495
1820
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1821
|
+
return 0;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
static int lazy_activate_retries(lazy_execute_ctx_t *ctx) {
|
|
1825
|
+
long long now = fast_now_ms();
|
|
1826
|
+
|
|
1827
|
+
for (int slot = 0;
|
|
1828
|
+
slot < ctx->capacity && ctx->session.active_count < ctx->session.max_connections; slot++) {
|
|
1829
|
+
if (ctx->states[slot] != LAZY_SLOT_RETRY_WAIT || ctx->retry_at_ms[slot] > now)
|
|
1830
|
+
continue;
|
|
1831
|
+
|
|
1832
|
+
request_ctx_t *request = &ctx->session.requests[slot];
|
|
1833
|
+
VALUE request_value = rb_ary_entry(ctx->anchors, slot);
|
|
1834
|
+
|
|
1835
|
+
if (!request_ctx_reset_for_retry(request)) {
|
|
1836
|
+
if (lazy_finalize_error(ctx, slot, "Failed to allocate a curl handle for the retry"))
|
|
1837
|
+
return 1;
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
if (!setup_easy_handle(request, request_value, &ctx->opts)) {
|
|
1842
|
+
if (request->setup_error_fatal)
|
|
1843
|
+
lazy_raise_setup_error(request);
|
|
1844
|
+
if (lazy_finalize_error(ctx, slot,
|
|
1845
|
+
request->setup_error ? request->setup_error
|
|
1846
|
+
: "Invalid request configuration"))
|
|
1847
|
+
return 1;
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
if (curl_multi_add_handle(ctx->session.multi, request->easy) != CURLM_OK) {
|
|
1852
|
+
if (lazy_finalize_error(ctx, slot, "Failed to schedule the retry"))
|
|
1853
|
+
return 1;
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
request->attempts++;
|
|
1858
|
+
request->active = 1;
|
|
1859
|
+
request->done = 0;
|
|
1860
|
+
ctx->states[slot] = LAZY_SLOT_ACTIVE;
|
|
1861
|
+
ctx->retry_at_ms[slot] = 0;
|
|
1862
|
+
ctx->session.active_count++;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
return 0;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
static long long lazy_next_retry_at(const lazy_execute_ctx_t *ctx) {
|
|
1869
|
+
long long next = 0;
|
|
1870
|
+
for (int slot = 0; slot < ctx->capacity; slot++) {
|
|
1871
|
+
if (ctx->states[slot] != LAZY_SLOT_RETRY_WAIT)
|
|
1872
|
+
continue;
|
|
1873
|
+
if (next == 0 || ctx->retry_at_ms[slot] < next)
|
|
1874
|
+
next = ctx->retry_at_ms[slot];
|
|
1875
|
+
}
|
|
1876
|
+
return next;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
static int lazy_process_completed(lazy_execute_ctx_t *ctx) {
|
|
1880
|
+
CURLMsg *msg;
|
|
1881
|
+
int msgs_left;
|
|
1882
|
+
|
|
1883
|
+
while ((msg = curl_multi_info_read(ctx->session.multi, &msgs_left))) {
|
|
1884
|
+
if (msg->msg != CURLMSG_DONE)
|
|
1885
|
+
continue;
|
|
1886
|
+
|
|
1887
|
+
request_ctx_t *request = NULL;
|
|
1888
|
+
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char **)&request);
|
|
1889
|
+
if (!request || request->done)
|
|
1890
|
+
continue;
|
|
1891
|
+
|
|
1892
|
+
int slot = (int)(request - ctx->session.requests);
|
|
1893
|
+
if (slot < 0 || slot >= ctx->capacity)
|
|
1894
|
+
continue;
|
|
1895
|
+
|
|
1896
|
+
if (request->active) {
|
|
1897
|
+
curl_multi_remove_handle(ctx->session.multi, request->easy);
|
|
1898
|
+
request->active = 0;
|
|
1899
|
+
if (ctx->session.active_count > 0)
|
|
1900
|
+
ctx->session.active_count--;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
request->done = 1;
|
|
1904
|
+
request->curl_result = msg->data.result;
|
|
1905
|
+
if (msg->data.result == CURLE_OK)
|
|
1906
|
+
curl_easy_getinfo(request->easy, CURLINFO_RESPONSE_CODE, &request->http_status);
|
|
1907
|
+
|
|
1908
|
+
if (request->attempts <= ctx->retry_cfg.max_retries &&
|
|
1909
|
+
should_retry(request, &ctx->retry_cfg)) {
|
|
1910
|
+
ctx->states[slot] = LAZY_SLOT_RETRY_WAIT;
|
|
1911
|
+
ctx->retry_at_ms[slot] =
|
|
1912
|
+
fast_now_ms() + retry_backoff_ms(&ctx->retry_cfg, request->attempts - 1);
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
VALUE response =
|
|
1917
|
+
(msg->data.result == CURLE_OK)
|
|
1918
|
+
? build_response(request)
|
|
1919
|
+
: build_error_response_with_code(curl_easy_strerror(msg->data.result),
|
|
1920
|
+
(int)msg->data.result, request->attempts);
|
|
1921
|
+
int stop = record_response(&ctx->cctx, request->index, response);
|
|
1922
|
+
lazy_release_slot(ctx, slot);
|
|
1923
|
+
if (stop)
|
|
1924
|
+
return 1;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
return 0;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
typedef struct {
|
|
1931
|
+
multi_session_t *session;
|
|
1932
|
+
long long wake_at_ms;
|
|
1933
|
+
} lazy_poll_args_t;
|
|
1934
|
+
|
|
1935
|
+
static void *lazy_poll_without_gvl(void *arg) {
|
|
1936
|
+
lazy_poll_args_t *poll = (lazy_poll_args_t *)arg;
|
|
1937
|
+
multi_session_t *session = poll->session;
|
|
1938
|
+
long long started = fast_now_ms();
|
|
1939
|
+
int before = session->still_running;
|
|
1940
|
+
|
|
1941
|
+
while (!session_cancelled(session)) {
|
|
1942
|
+
int numfds = 0;
|
|
1943
|
+
curl_multi_poll(session->multi, NULL, 0, POLL_TIMEOUT_MS, &numfds);
|
|
1944
|
+
curl_multi_perform(session->multi, &session->still_running);
|
|
1945
|
+
|
|
1946
|
+
if (session->still_running == 0 || session->still_running < before)
|
|
1947
|
+
break;
|
|
1948
|
+
if (poll->wake_at_ms > 0 && fast_now_ms() >= poll->wake_at_ms)
|
|
1949
|
+
break;
|
|
1950
|
+
if (fast_now_ms() - started >= POLL_SLICE_MS)
|
|
1951
|
+
break;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
return NULL;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
static void lazy_wait(lazy_execute_ctx_t *ctx) {
|
|
1958
|
+
long long next_retry = lazy_next_retry_at(ctx);
|
|
1959
|
+
|
|
1960
|
+
if (ctx->session.active_count > 0) {
|
|
1961
|
+
lazy_poll_args_t poll = {.session = &ctx->session, .wake_at_ms = next_retry};
|
|
1962
|
+
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
1963
|
+
VALUE scheduler = current_fiber_scheduler();
|
|
1964
|
+
if (scheduler != Qnil)
|
|
1965
|
+
run_via_fiber_worker(scheduler, lazy_poll_without_gvl, &poll);
|
|
1966
|
+
else
|
|
1967
|
+
#endif
|
|
1968
|
+
rb_thread_call_without_gvl(lazy_poll_without_gvl, &poll, unblock_perform,
|
|
1969
|
+
&ctx->session);
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
if (next_retry > 0) {
|
|
1974
|
+
long long delay = next_retry - fast_now_ms();
|
|
1975
|
+
if (delay > 0)
|
|
1976
|
+
retry_delay_sleep((long)delay);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
static int lazy_deadline_reached(lazy_execute_ctx_t *ctx) {
|
|
1981
|
+
if (ctx->opts.deadline_ms <= 0 || fast_now_ms() < ctx->opts.deadline_ms)
|
|
1982
|
+
return 0;
|
|
1983
|
+
|
|
1984
|
+
ctx->deadline_hit = 1;
|
|
1985
|
+
ctx->stop = 1;
|
|
1986
|
+
return 1;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
static void lazy_progress(lazy_execute_ctx_t *ctx) {
|
|
1990
|
+
if (ctx->stop || lazy_deadline_reached(ctx))
|
|
1991
|
+
return;
|
|
1992
|
+
|
|
1993
|
+
if (lazy_activate_retries(ctx) || lazy_activate_pending(ctx)) {
|
|
1994
|
+
ctx->stop = 1;
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
curl_multi_perform(ctx->session.multi, &ctx->session.still_running);
|
|
1999
|
+
if (lazy_process_completed(ctx)) {
|
|
2000
|
+
ctx->stop = 1;
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
if (ctx->occupied_count == 0)
|
|
2005
|
+
return;
|
|
2006
|
+
|
|
2007
|
+
lazy_wait(ctx);
|
|
2008
|
+
if (lazy_process_completed(ctx))
|
|
2009
|
+
ctx->stop = 1;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
static VALUE lazy_each_request(VALUE yielded, VALUE arg, int argc, const VALUE *argv,
|
|
2013
|
+
VALUE blockarg) {
|
|
2014
|
+
(void)blockarg;
|
|
2015
|
+
lazy_execute_ctx_t *ctx = (lazy_execute_ctx_t *)arg;
|
|
2016
|
+
VALUE request = argc == 1 ? argv[0] : yielded;
|
|
2017
|
+
|
|
2018
|
+
while (!ctx->stop && ctx->occupied_count >= ctx->capacity)
|
|
2019
|
+
lazy_progress(ctx);
|
|
2020
|
+
|
|
2021
|
+
if (ctx->stop || lazy_deadline_reached(ctx))
|
|
2022
|
+
rb_iter_break();
|
|
1517
2023
|
|
|
1518
|
-
|
|
2024
|
+
if (ctx->next_index > INT_MAX)
|
|
2025
|
+
rb_raise(rb_eArgError, "lazy request index exceeds supported range");
|
|
2026
|
+
|
|
2027
|
+
int slot = ctx->free_slots[--ctx->free_count];
|
|
2028
|
+
request_ctx_init(&ctx->session.requests[slot], ctx->next_index++);
|
|
2029
|
+
rb_ary_store(ctx->anchors, slot, request);
|
|
2030
|
+
ctx->states[slot] = LAZY_SLOT_PENDING;
|
|
2031
|
+
ctx->occupied_count++;
|
|
2032
|
+
lazy_pending_push(ctx, slot);
|
|
2033
|
+
|
|
2034
|
+
if (lazy_activate_retries(ctx) || lazy_activate_pending(ctx)) {
|
|
2035
|
+
ctx->stop = 1;
|
|
2036
|
+
rb_iter_break();
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
curl_multi_perform(ctx->session.multi, &ctx->session.still_running);
|
|
2040
|
+
if (lazy_process_completed(ctx)) {
|
|
2041
|
+
ctx->stop = 1;
|
|
2042
|
+
rb_iter_break();
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
return Qnil;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
static void lazy_finalize_deadline(lazy_execute_ctx_t *ctx) {
|
|
2049
|
+
if (ctx->cctx.stream || ctx->cctx.target > 0)
|
|
2050
|
+
return;
|
|
2051
|
+
|
|
2052
|
+
for (int slot = 0; slot < ctx->capacity; slot++) {
|
|
2053
|
+
if (ctx->states[slot] == LAZY_SLOT_FREE)
|
|
2054
|
+
continue;
|
|
2055
|
+
|
|
2056
|
+
request_ctx_t *request = &ctx->session.requests[slot];
|
|
2057
|
+
if (request->active) {
|
|
2058
|
+
curl_multi_remove_handle(ctx->session.multi, request->easy);
|
|
2059
|
+
request->active = 0;
|
|
2060
|
+
if (ctx->session.active_count > 0)
|
|
2061
|
+
ctx->session.active_count--;
|
|
1519
2062
|
}
|
|
2063
|
+
|
|
2064
|
+
VALUE response =
|
|
2065
|
+
build_error_response("Total timeout exceeded", ERR_DEADLINE, request->attempts);
|
|
2066
|
+
record_response(&ctx->cctx, request->index, response);
|
|
2067
|
+
lazy_release_slot(ctx, slot);
|
|
1520
2068
|
}
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
static VALUE lazy_execute_body(VALUE arg) {
|
|
2072
|
+
lazy_execute_ctx_t *ctx = (lazy_execute_ctx_t *)arg;
|
|
1521
2073
|
|
|
1522
|
-
|
|
2074
|
+
if (ctx->opts.total_timeout_ms > 0)
|
|
2075
|
+
ctx->opts.deadline_ms = fast_now_ms() + ctx->opts.total_timeout_ms;
|
|
2076
|
+
|
|
2077
|
+
rb_block_call(ctx->source, rb_intern("each"), 0, NULL, lazy_each_request, (VALUE)ctx);
|
|
2078
|
+
|
|
2079
|
+
while (!ctx->stop && ctx->occupied_count > 0)
|
|
2080
|
+
lazy_progress(ctx);
|
|
2081
|
+
|
|
2082
|
+
if (ctx->deadline_hit)
|
|
2083
|
+
lazy_finalize_deadline(ctx);
|
|
2084
|
+
|
|
2085
|
+
RB_GC_GUARD(ctx->source);
|
|
2086
|
+
RB_GC_GUARD(ctx->anchors);
|
|
2087
|
+
return ctx->cctx.stream ? Qnil : ctx->cctx.results;
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
static VALUE cleanup_lazy_execute(VALUE arg) {
|
|
2091
|
+
lazy_execute_ctx_t *ctx = (lazy_execute_ctx_t *)arg;
|
|
2092
|
+
|
|
2093
|
+
if (ctx->session.requests) {
|
|
2094
|
+
for (int slot = 0; slot < ctx->capacity; slot++) {
|
|
2095
|
+
request_ctx_t *request = &ctx->session.requests[slot];
|
|
2096
|
+
if (request->easy && request->active && ctx->session.multi)
|
|
2097
|
+
curl_multi_remove_handle(ctx->session.multi, request->easy);
|
|
2098
|
+
request_ctx_free(request);
|
|
2099
|
+
}
|
|
2100
|
+
free(ctx->session.requests);
|
|
2101
|
+
ctx->session.requests = NULL;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
free(ctx->states);
|
|
2105
|
+
ctx->states = NULL;
|
|
2106
|
+
free(ctx->free_slots);
|
|
2107
|
+
ctx->free_slots = NULL;
|
|
2108
|
+
free(ctx->pending_slots);
|
|
2109
|
+
ctx->pending_slots = NULL;
|
|
2110
|
+
free(ctx->retry_at_ms);
|
|
2111
|
+
ctx->retry_at_ms = NULL;
|
|
2112
|
+
|
|
2113
|
+
if (ctx->session.multi) {
|
|
2114
|
+
curl_multi_cleanup(ctx->session.multi);
|
|
2115
|
+
ctx->session.multi = NULL;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
if (ctx->retry_cfg.retry_http_codes) {
|
|
2119
|
+
free(ctx->retry_cfg.retry_http_codes);
|
|
2120
|
+
ctx->retry_cfg.retry_http_codes = NULL;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
return Qnil;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
static void initialize_lazy_completion(lazy_execute_ctx_t *ctx, VALUE requests, VALUE options,
|
|
2127
|
+
int target, int stream, int max_conn) {
|
|
2128
|
+
ctx->capacity = max_conn + ctx->buffer_size;
|
|
2129
|
+
ctx->source = requests;
|
|
2130
|
+
ctx->anchors = rb_ary_new2(ctx->capacity);
|
|
2131
|
+
ctx->next_index = 0;
|
|
2132
|
+
|
|
2133
|
+
ctx->cctx.results = stream ? Qnil : rb_ary_new();
|
|
2134
|
+
ctx->cctx.accept = target > 0 && !NIL_P(options) ? hash_aref_key(options, KEY_ACCEPT) : Qnil;
|
|
2135
|
+
ctx->cctx.completed = 0;
|
|
2136
|
+
ctx->cctx.target = target;
|
|
2137
|
+
ctx->cctx.stream = stream;
|
|
2138
|
+
|
|
2139
|
+
if (!NIL_P(ctx->cctx.accept) && !rb_respond_to(ctx->cctx.accept, rb_intern("call")))
|
|
2140
|
+
rb_raise(rb_eArgError, "accept must respond to #call");
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
static void allocate_lazy_state(lazy_execute_ctx_t *ctx, int max_conn) {
|
|
2144
|
+
materialize_retry_codes(&ctx->retry_cfg);
|
|
2145
|
+
multi_session_init(&ctx->session, curl_multi_init(), ctx->capacity, max_conn,
|
|
2146
|
+
ctx->opts.timeout_sec);
|
|
2147
|
+
if (!ctx->session.multi) {
|
|
2148
|
+
cleanup_lazy_execute((VALUE)ctx);
|
|
2149
|
+
rb_raise(rb_eNoMemError, "failed to initialize curl multi handle");
|
|
2150
|
+
}
|
|
2151
|
+
multi_session_configure(ctx->session.multi, max_conn);
|
|
2152
|
+
|
|
2153
|
+
ctx->session.requests = calloc((size_t)ctx->capacity, sizeof(*ctx->session.requests));
|
|
2154
|
+
ctx->states = calloc((size_t)ctx->capacity, sizeof(*ctx->states));
|
|
2155
|
+
ctx->free_slots = malloc(sizeof(*ctx->free_slots) * (size_t)ctx->capacity);
|
|
2156
|
+
ctx->pending_slots = malloc(sizeof(*ctx->pending_slots) * (size_t)ctx->capacity);
|
|
2157
|
+
ctx->retry_at_ms = calloc((size_t)ctx->capacity, sizeof(*ctx->retry_at_ms));
|
|
2158
|
+
|
|
2159
|
+
if (!ctx->session.requests || !ctx->states || !ctx->free_slots || !ctx->pending_slots ||
|
|
2160
|
+
!ctx->retry_at_ms) {
|
|
2161
|
+
cleanup_lazy_execute((VALUE)ctx);
|
|
2162
|
+
rb_raise(rb_eNoMemError, "failed to allocate lazy request state");
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
for (int slot = 0; slot < ctx->capacity; slot++)
|
|
2166
|
+
ctx->free_slots[ctx->free_count++] = slot;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
static VALUE internal_lazy_execute(VALUE requests, VALUE options, int target, int stream) {
|
|
2170
|
+
if (!rb_respond_to(requests, rb_intern("each")))
|
|
2171
|
+
rb_raise(rb_eArgError, "requests must be an Array or respond to #each");
|
|
2172
|
+
|
|
2173
|
+
lazy_execute_ctx_t ctx;
|
|
2174
|
+
memset(&ctx, 0, sizeof(ctx));
|
|
2175
|
+
|
|
2176
|
+
int max_conn;
|
|
2177
|
+
parse_options(options, &ctx.opts, &max_conn, &ctx.retry_cfg);
|
|
2178
|
+
parse_retry_codes(options, &ctx.retry_cfg);
|
|
2179
|
+
ctx.buffer_size = NIL_P(options) ? max_conn
|
|
2180
|
+
: parse_int_option(options, KEY_BUFFER, "buffer", 1,
|
|
2181
|
+
MAX_BUFFER, max_conn, NULL);
|
|
2182
|
+
|
|
2183
|
+
configure_retry_mode(&ctx.retry_cfg, target, stream);
|
|
2184
|
+
initialize_lazy_completion(&ctx, requests, options, target, stream, max_conn);
|
|
2185
|
+
allocate_lazy_state(&ctx, max_conn);
|
|
2186
|
+
|
|
2187
|
+
return rb_ensure(lazy_execute_body, (VALUE)&ctx, cleanup_lazy_execute, (VALUE)&ctx);
|
|
1523
2188
|
}
|
|
1524
2189
|
|
|
1525
2190
|
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
@@ -1552,7 +2217,7 @@ static VALUE scheduler_execute_thread(void *arg) {
|
|
|
1552
2217
|
static void cancel_scheduler_execute(scheduler_execute_ctx_t *ctx) {
|
|
1553
2218
|
multi_session_t *session = ctx->ea->session;
|
|
1554
2219
|
|
|
1555
|
-
session
|
|
2220
|
+
cancel_session(session);
|
|
1556
2221
|
#ifdef HAVE_CURL_MULTI_WAKEUP
|
|
1557
2222
|
if (session->multi)
|
|
1558
2223
|
curl_multi_wakeup(session->multi);
|
|
@@ -1597,6 +2262,9 @@ static VALUE execute_with_fiber_scheduler(VALUE arg) {
|
|
|
1597
2262
|
#endif
|
|
1598
2263
|
|
|
1599
2264
|
static VALUE internal_execute(VALUE requests, VALUE options, int target, int stream) {
|
|
2265
|
+
if (!RB_TYPE_P(requests, T_ARRAY))
|
|
2266
|
+
return internal_lazy_execute(requests, options, target, stream);
|
|
2267
|
+
|
|
1600
2268
|
Check_Type(requests, T_ARRAY);
|
|
1601
2269
|
|
|
1602
2270
|
long count_long = RARRAY_LEN(requests);
|
|
@@ -1616,57 +2284,17 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
1616
2284
|
int max_conn;
|
|
1617
2285
|
retry_config_t retry_cfg;
|
|
1618
2286
|
parse_options(options, &opts, &max_conn, &retry_cfg);
|
|
2287
|
+
parse_retry_codes(options, &retry_cfg);
|
|
1619
2288
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
request_ctx_init(&probe, i);
|
|
1623
|
-
if (!request_ctx_prepare_easy(&probe)) {
|
|
1624
|
-
request_ctx_free(&probe);
|
|
1625
|
-
continue;
|
|
1626
|
-
}
|
|
1627
|
-
setup_easy_handle(&probe, rb_ary_entry(requests, i), &opts);
|
|
1628
|
-
int fatal = probe.setup_error_fatal;
|
|
1629
|
-
const char *message = probe.setup_error;
|
|
1630
|
-
char buffer[128];
|
|
1631
|
-
if (fatal && message) {
|
|
1632
|
-
snprintf(buffer, sizeof(buffer), "%s (request %d)", message, i);
|
|
1633
|
-
}
|
|
1634
|
-
request_ctx_free(&probe);
|
|
1635
|
-
if (fatal) {
|
|
1636
|
-
VALUE klass = NIL_P(fast_validation_error) ? rb_eArgError : fast_validation_error;
|
|
1637
|
-
rb_raise(klass, "%s", buffer);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
if (stream || target > 0) {
|
|
1642
|
-
if (retry_cfg.retries_explicit && retry_cfg.max_retries > 0 && stream)
|
|
1643
|
-
rb_warn(
|
|
1644
|
-
"FastCurl: retries are not supported in stream_execute, ignoring retries option");
|
|
1645
|
-
if (retry_cfg.retries_explicit && retry_cfg.max_retries > 0 && target > 0)
|
|
1646
|
-
rb_warn(
|
|
1647
|
-
"FastCurl: retries are not supported in first_execute, ignoring retries option");
|
|
1648
|
-
retry_cfg.max_retries = 0;
|
|
1649
|
-
}
|
|
2289
|
+
validate_execute_requests(requests, count, target, options, &opts);
|
|
2290
|
+
configure_retry_mode(&retry_cfg, target, stream);
|
|
1650
2291
|
|
|
2292
|
+
materialize_retry_codes(&retry_cfg);
|
|
1651
2293
|
multi_session_t session;
|
|
1652
2294
|
int *invalid = NULL;
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
if (!session.multi)
|
|
1658
|
-
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to initialize curl multi handle");
|
|
1659
|
-
|
|
1660
|
-
multi_session_configure(session.multi, max_conn);
|
|
1661
|
-
|
|
1662
|
-
session.requests = calloc((size_t)count, sizeof(request_ctx_t));
|
|
1663
|
-
if (!session.requests)
|
|
1664
|
-
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to allocate request contexts");
|
|
1665
|
-
|
|
1666
|
-
invalid = calloc((size_t)count, sizeof(int));
|
|
1667
|
-
cleanup.invalid = invalid;
|
|
1668
|
-
if (!invalid)
|
|
1669
|
-
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to allocate tracking array");
|
|
2295
|
+
cleanup_ctx_t cleanup = {0};
|
|
2296
|
+
prepare_execute_session(&session, &invalid, &cleanup, &retry_cfg, count, max_conn,
|
|
2297
|
+
opts.timeout_sec);
|
|
1670
2298
|
execute_args_t ea = {
|
|
1671
2299
|
.requests = requests,
|
|
1672
2300
|
.options = options,
|
|
@@ -1680,7 +2308,8 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
1680
2308
|
|
|
1681
2309
|
#ifdef FAST_CURL_HAVE_FIBER_SCHEDULER
|
|
1682
2310
|
VALUE scheduler = current_fiber_scheduler();
|
|
1683
|
-
|
|
2311
|
+
VALUE accept = target > 0 && !NIL_P(options) ? hash_aref_key(options, KEY_ACCEPT) : Qnil;
|
|
2312
|
+
if (scheduler != Qnil && !stream && NIL_P(accept)) {
|
|
1684
2313
|
scheduler_execute_ctx_t scheduler_ctx = {
|
|
1685
2314
|
.ea = &ea,
|
|
1686
2315
|
.scheduler = scheduler,
|