fast_curl 0.5.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 +12 -12
- data/ext/fast_curl/fast_curl.c +351 -233
- data/lib/fast_curl/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0c5dac84b62da99fa420dc28437ca3284ddd4f73fd970c7730df3c8a2a7ba5b
|
|
4
|
+
data.tar.gz: 7455385e6a1b1c76467e67909bb252a70a6eac465846aacdf988d9e934fb7d79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f5b3c2c3797436d23ec37e9f2997c340a5e50b05a4ef9dc82a699093b01fc7a3097c720e14adba1fe6ad3e132e29d90893e01c4bd8a81fbdde3043b9067a727
|
|
7
|
+
data.tar.gz: 5a990531123a9bd72ccc082a5076a5dd1f6eed90f455e92c77c336be33ed964f3ad9a6cef3d59e6322cf378223399505edcaa6d27dd7a0619d71f5c600870bba
|
data/README.md
CHANGED
|
@@ -278,27 +278,27 @@ grows linearly while parallel clients stay near ~1s plus network overhead.
|
|
|
278
278
|
|
|
279
279
|
| Scenario | Net::HTTP sequential | fast_curl (thread) | fast_curl (fiber/Async) | Async::HTTP::Internet |
|
|
280
280
|
|---------------------------------|---------------------:|-------------------:|------------------------:|----------------------:|
|
|
281
|
-
| 4 requests × 1s, conn=4 |
|
|
282
|
-
| 10 requests × 1s, conn=10 |
|
|
283
|
-
| 20 requests × 1s, conn=5 |
|
|
284
|
-
| 200 requests × 1s, conn=20 | — |
|
|
281
|
+
| 4 requests × 1s, conn=4 | 5.82s | 1.68s | 1.65s | 1.93s |
|
|
282
|
+
| 10 requests × 1s, conn=10 | 17.51s | 1.70s | 1.68s | 1.89s |
|
|
283
|
+
| 20 requests × 1s, conn=5 | 34.66s | 5.69s | 6.71s | 5.79s |
|
|
284
|
+
| 200 requests × 1s, conn=20 | — | 14.29s | 14.29s | 7.27s |
|
|
285
285
|
|
|
286
286
|
### Speedup vs Net::HTTP (median)
|
|
287
287
|
|
|
288
288
|
| Scenario | fast_curl (thread) | fast_curl (fiber) | Async::HTTP |
|
|
289
289
|
|-----------------------------------|-------------------:|------------------:|------------:|
|
|
290
|
-
| 4 requests × 1s | **3.5x** | 3.
|
|
291
|
-
| 10 requests × 1s |
|
|
292
|
-
| 20 requests × 1s (queued, conn=5) |
|
|
290
|
+
| 4 requests × 1s | **3.5x** | 3.5x | 3.0x |
|
|
291
|
+
| 10 requests × 1s | **10.3x** | 10.4x | 9.2x |
|
|
292
|
+
| 20 requests × 1s (queued, conn=5) | **6.1x** | 5.2x | 6.0x |
|
|
293
293
|
|
|
294
294
|
### Memory & allocations per request batch (lower is better)
|
|
295
295
|
|
|
296
296
|
| Scenario | fast_curl (thread) allocated | fast_curl (fiber) allocated | Async::HTTP allocated |
|
|
297
297
|
|---------------------------------|-----------------------------:|----------------------------:|----------------------:|
|
|
298
|
-
| 4 requests × 1s | **
|
|
299
|
-
| 10 requests × 1s | **
|
|
300
|
-
| 20 requests × 1s, conn=5 | **
|
|
301
|
-
| 200 requests × 1s, conn=20 | **5,
|
|
298
|
+
| 4 requests × 1s | **125 obj** | 179 obj | 1,921 obj |
|
|
299
|
+
| 10 requests × 1s | **281 obj** | 335 obj | 3,344 obj |
|
|
300
|
+
| 20 requests × 1s, conn=5 | **541 obj** | 595 obj | 5,898 obj |
|
|
301
|
+
| 200 requests × 1s, conn=20 | **5,222 obj** | 5,275 obj | 50,534 obj |
|
|
302
302
|
|
|
303
303
|
Ruby heap delta stays near zero across all scenarios for fast_curl — most allocation
|
|
304
304
|
happens in C, not on the Ruby heap.
|
|
@@ -307,7 +307,7 @@ happens in C, not on the Ruby heap.
|
|
|
307
307
|
|
|
308
308
|
| Scenario | Time |
|
|
309
309
|
|--------------------------------------------------------------|------:|
|
|
310
|
-
| 4 mixed requests (404, 500, DNS fail, 30s delay), timeout=2s |
|
|
310
|
+
| 4 mixed requests (404, 500, DNS fail, 30s delay), timeout=2s | 0.68s |
|
|
311
311
|
|
|
312
312
|
Bounded by `timeout=2s` rather than by the slow request.
|
|
313
313
|
|
data/ext/fast_curl/fast_curl.c
CHANGED
|
@@ -20,13 +20,16 @@
|
|
|
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
|
|
@@ -258,29 +261,63 @@ typedef struct {
|
|
|
258
261
|
|
|
259
262
|
typedef struct {
|
|
260
263
|
header_entry_t *entries;
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
size_t count;
|
|
265
|
+
size_t cap;
|
|
266
|
+
size_t total_bytes;
|
|
263
267
|
} header_list_t;
|
|
264
268
|
|
|
265
269
|
static inline void header_list_init(header_list_t *h) {
|
|
266
270
|
h->entries = NULL;
|
|
267
271
|
h->count = 0;
|
|
268
272
|
h->cap = 0;
|
|
273
|
+
h->total_bytes = 0;
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
static void header_list_free(header_list_t *h) {
|
|
272
|
-
for (
|
|
277
|
+
for (size_t i = 0; i < h->count; i++)
|
|
273
278
|
free(h->entries[i].str);
|
|
274
279
|
free(h->entries);
|
|
275
280
|
h->entries = NULL;
|
|
276
281
|
h->count = 0;
|
|
277
282
|
h->cap = 0;
|
|
283
|
+
h->total_bytes = 0;
|
|
278
284
|
}
|
|
279
285
|
|
|
280
286
|
static void header_list_reset(header_list_t *h) {
|
|
281
|
-
for (
|
|
287
|
+
for (size_t i = 0; i < h->count; i++)
|
|
282
288
|
free(h->entries[i].str);
|
|
283
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;
|
|
284
321
|
}
|
|
285
322
|
|
|
286
323
|
static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
@@ -294,22 +331,18 @@ static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userda
|
|
|
294
331
|
while (stripped > 0 && (ptr[stripped - 1] == '\r' || ptr[stripped - 1] == '\n'))
|
|
295
332
|
stripped--;
|
|
296
333
|
|
|
297
|
-
|
|
334
|
+
int status_line = stripped >= 5 && memcmp(ptr, "HTTP/", 5) == 0;
|
|
335
|
+
if (status_line)
|
|
298
336
|
header_list_reset(h);
|
|
299
|
-
return total;
|
|
300
|
-
}
|
|
301
337
|
|
|
302
|
-
if (
|
|
338
|
+
if (!header_list_add_bytes(h, total))
|
|
339
|
+
return 0;
|
|
340
|
+
|
|
341
|
+
if (status_line || stripped == 0)
|
|
303
342
|
return total;
|
|
304
343
|
|
|
305
|
-
if (h
|
|
306
|
-
|
|
307
|
-
header_entry_t *ne = realloc(h->entries, sizeof(header_entry_t) * new_cap);
|
|
308
|
-
if (!ne)
|
|
309
|
-
return 0;
|
|
310
|
-
h->entries = ne;
|
|
311
|
-
h->cap = new_cap;
|
|
312
|
-
}
|
|
344
|
+
if (!header_list_reserve(h))
|
|
345
|
+
return 0;
|
|
313
346
|
|
|
314
347
|
char *entry = malloc(stripped + 1);
|
|
315
348
|
if (!entry)
|
|
@@ -415,7 +448,7 @@ typedef struct {
|
|
|
415
448
|
int still_running;
|
|
416
449
|
long timeout_ms;
|
|
417
450
|
int max_connections;
|
|
418
|
-
|
|
451
|
+
int cancelled;
|
|
419
452
|
|
|
420
453
|
int active_count;
|
|
421
454
|
int pending_pos;
|
|
@@ -423,12 +456,21 @@ typedef struct {
|
|
|
423
456
|
int *pending_indices;
|
|
424
457
|
} multi_session_t;
|
|
425
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
|
+
|
|
426
467
|
typedef struct {
|
|
427
468
|
int max_retries;
|
|
428
469
|
int retries_explicit;
|
|
429
470
|
long retry_delay_ms;
|
|
430
471
|
int *retry_http_codes;
|
|
431
472
|
int retry_http_count;
|
|
473
|
+
VALUE retry_code_values;
|
|
432
474
|
int retry_non_idempotent;
|
|
433
475
|
} retry_config_t;
|
|
434
476
|
|
|
@@ -571,7 +613,7 @@ static VALUE build_response(request_ctx_t *ctx) {
|
|
|
571
613
|
curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &status);
|
|
572
614
|
|
|
573
615
|
VALUE headers_hash = new_headers_hash();
|
|
574
|
-
for (
|
|
616
|
+
for (size_t i = 0; i < ctx->headers.count; i++) {
|
|
575
617
|
const char *hdr = ctx->headers.entries[i].str;
|
|
576
618
|
size_t hdr_len = ctx->headers.entries[i].len;
|
|
577
619
|
const char *colon = memchr(hdr, ':', hdr_len);
|
|
@@ -598,7 +640,7 @@ static VALUE build_response(request_ctx_t *ctx) {
|
|
|
598
640
|
}
|
|
599
641
|
|
|
600
642
|
VALUE body_str =
|
|
601
|
-
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("");
|
|
602
644
|
|
|
603
645
|
VALUE result = rb_hash_new();
|
|
604
646
|
rb_hash_aset(result, SYM(KEY_STATUS), LONG2NUM(status));
|
|
@@ -643,7 +685,7 @@ static int is_valid_url(const char *url) {
|
|
|
643
685
|
return 0;
|
|
644
686
|
if (strncmp(url, "https://", 8) == 0)
|
|
645
687
|
return 1;
|
|
646
|
-
if (
|
|
688
|
+
if (strncmp(url, "http://", 7) == 0)
|
|
647
689
|
return 1;
|
|
648
690
|
return 0;
|
|
649
691
|
}
|
|
@@ -886,7 +928,7 @@ static int setup_easy_handle(request_ctx_t *ctx, VALUE request, const request_op
|
|
|
886
928
|
SET_SETUP_ERROR(ctx, "Missing :url", 0);
|
|
887
929
|
return 0;
|
|
888
930
|
}
|
|
889
|
-
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))) {
|
|
890
932
|
SET_SETUP_ERROR(ctx, "Invalid URL", 1);
|
|
891
933
|
return 0;
|
|
892
934
|
}
|
|
@@ -934,6 +976,67 @@ static int setup_easy_handle(request_ctx_t *ctx, VALUE request, const request_op
|
|
|
934
976
|
return 1;
|
|
935
977
|
}
|
|
936
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
|
+
|
|
937
1040
|
/* Poll until something actually completes, or the slice budget expires.
|
|
938
1041
|
Previously this returned after every single 50ms poll, which under a fiber
|
|
939
1042
|
scheduler meant a fresh OS thread twenty times a second. Cancellation still
|
|
@@ -943,7 +1046,7 @@ static void *poll_without_gvl(void *arg) {
|
|
|
943
1046
|
long long started = fast_now_ms();
|
|
944
1047
|
int before = s->still_running;
|
|
945
1048
|
|
|
946
|
-
while (!s
|
|
1049
|
+
while (!session_cancelled(s)) {
|
|
947
1050
|
int numfds = 0;
|
|
948
1051
|
curl_multi_poll(s->multi, NULL, 0, POLL_TIMEOUT_MS, &numfds);
|
|
949
1052
|
curl_multi_perform(s->multi, &s->still_running);
|
|
@@ -958,7 +1061,7 @@ static void *poll_without_gvl(void *arg) {
|
|
|
958
1061
|
|
|
959
1062
|
static void unblock_perform(void *arg) {
|
|
960
1063
|
multi_session_t *s = (multi_session_t *)arg;
|
|
961
|
-
s
|
|
1064
|
+
cancel_session(s);
|
|
962
1065
|
#ifdef HAVE_CURL_MULTI_WAKEUP
|
|
963
1066
|
curl_multi_wakeup(s->multi);
|
|
964
1067
|
#endif
|
|
@@ -1144,7 +1247,8 @@ static void run_multi_loop(multi_session_t *session, completion_ctx_t *cctx, VAL
|
|
|
1144
1247
|
if (process_completed(session, cctx))
|
|
1145
1248
|
return;
|
|
1146
1249
|
|
|
1147
|
-
while (!
|
|
1250
|
+
while (!session_cancelled(session) &&
|
|
1251
|
+
(session->active_count > 0 || pending_remaining(session))) {
|
|
1148
1252
|
if (opts->deadline_ms > 0 && fast_now_ms() >= opts->deadline_ms)
|
|
1149
1253
|
break;
|
|
1150
1254
|
|
|
@@ -1229,6 +1333,7 @@ static void retry_config_init(retry_config_t *retry_cfg) {
|
|
|
1229
1333
|
retry_cfg->retry_delay_ms = DEFAULT_RETRY_DELAY;
|
|
1230
1334
|
retry_cfg->retry_http_codes = NULL;
|
|
1231
1335
|
retry_cfg->retry_http_count = 0;
|
|
1336
|
+
retry_cfg->retry_code_values = Qnil;
|
|
1232
1337
|
retry_cfg->retry_non_idempotent = 0;
|
|
1233
1338
|
}
|
|
1234
1339
|
|
|
@@ -1287,22 +1392,33 @@ static void parse_retry_codes(VALUE options, retry_config_t *retry_cfg) {
|
|
|
1287
1392
|
rb_raise(rb_eArgError, "retry_codes is too large");
|
|
1288
1393
|
|
|
1289
1394
|
len = (int)len_long;
|
|
1395
|
+
VALUE normalized = rb_ary_new_capa(len);
|
|
1290
1396
|
for (int i = 0; i < len; i++) {
|
|
1291
1397
|
int code = NUM2INT(rb_ary_entry(codes, i));
|
|
1292
1398
|
if (code < 100 || code > 599)
|
|
1293
1399
|
rb_raise(rb_eArgError, "retry_codes must contain valid HTTP status codes");
|
|
1400
|
+
rb_ary_push(normalized, INT2NUM(code));
|
|
1294
1401
|
}
|
|
1295
1402
|
|
|
1296
|
-
|
|
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)
|
|
1297
1410
|
return;
|
|
1298
1411
|
|
|
1299
|
-
|
|
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);
|
|
1300
1417
|
if (!retry_cfg->retry_http_codes)
|
|
1301
1418
|
rb_raise(rb_eNoMemError, "failed to allocate retry codes");
|
|
1302
1419
|
|
|
1303
|
-
retry_cfg->retry_http_count
|
|
1304
|
-
|
|
1305
|
-
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));
|
|
1306
1422
|
}
|
|
1307
1423
|
|
|
1308
1424
|
static void parse_options(VALUE options, request_options_t *opts, int *max_conn,
|
|
@@ -1347,7 +1463,17 @@ static void parse_options(VALUE options, request_options_t *opts, int *max_conn,
|
|
|
1347
1463
|
retry_cfg->retry_delay_ms =
|
|
1348
1464
|
parse_long_option(options, KEY_RETRY_DELAY, "retry_delay", 0, MAX_RETRY_DELAY_MS,
|
|
1349
1465
|
retry_cfg->retry_delay_ms, NULL);
|
|
1350
|
-
|
|
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;
|
|
1351
1477
|
}
|
|
1352
1478
|
|
|
1353
1479
|
static void multi_session_init(multi_session_t *session, CURLM *multi, int count, int max_conn,
|
|
@@ -1410,6 +1536,27 @@ static FAST_CURL_NORETURN void cleanup_and_raise(cleanup_ctx_t *cleanup, VALUE e
|
|
|
1410
1536
|
rb_raise(exception, "%s", message);
|
|
1411
1537
|
}
|
|
1412
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
|
+
|
|
1413
1560
|
typedef struct {
|
|
1414
1561
|
VALUE requests;
|
|
1415
1562
|
VALUE options;
|
|
@@ -1421,130 +1568,144 @@ typedef struct {
|
|
|
1421
1568
|
request_options_t *opts;
|
|
1422
1569
|
} execute_args_t;
|
|
1423
1570
|
|
|
1424
|
-
static
|
|
1425
|
-
|
|
1426
|
-
|
|
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) {
|
|
1427
1591
|
multi_session_t *session = ea->session;
|
|
1428
|
-
int *invalid = ea->invalid;
|
|
1429
|
-
retry_config_t *retry_cfg = ea->retry_cfg;
|
|
1430
|
-
request_options_t *opts = ea->opts;
|
|
1431
|
-
int deadline_hit = 0;
|
|
1432
1592
|
int count = session->count;
|
|
1433
|
-
|
|
1434
|
-
|
|
1593
|
+
request_options_t *opts = ea->opts;
|
|
1594
|
+
retry_config_t *retry_cfg = ea->retry_cfg;
|
|
1435
1595
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1596
|
+
if (opts->deadline_ms > 0 && fast_now_ms() >= opts->deadline_ms) {
|
|
1597
|
+
*deadline_hit = 1;
|
|
1598
|
+
return 0;
|
|
1599
|
+
}
|
|
1438
1600
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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
|
+
}
|
|
1445
1608
|
|
|
1446
|
-
if (
|
|
1447
|
-
|
|
1609
|
+
if (retry_count == 0)
|
|
1610
|
+
return 0;
|
|
1448
1611
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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;
|
|
1452
1621
|
}
|
|
1622
|
+
retry_delay_sleep(backoff);
|
|
1453
1623
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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
|
+
}
|
|
1458
1634
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
if (opts->deadline_ms > 0 && fast_now_ms() >= opts->deadline_ms) {
|
|
1462
|
-
deadline_hit = 1;
|
|
1463
|
-
break;
|
|
1464
|
-
}
|
|
1635
|
+
retry_indices[runnable_count++] = idx;
|
|
1636
|
+
}
|
|
1465
1637
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
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
|
+
}
|
|
1469
1645
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
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
|
+
}
|
|
1477
1654
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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
|
+
}
|
|
1482
1678
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
if (left <= 0) {
|
|
1487
|
-
deadline_hit = 1;
|
|
1488
|
-
free(retry_indices);
|
|
1489
|
-
break;
|
|
1490
|
-
}
|
|
1491
|
-
if (backoff > (long)left)
|
|
1492
|
-
backoff = (long)left;
|
|
1493
|
-
}
|
|
1494
|
-
retry_delay_sleep(backoff);
|
|
1679
|
+
rb_ary_store(cctx->results, i, build_result_pair(i, response));
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1495
1682
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1683
|
+
static VALUE internal_execute_body(VALUE arg) {
|
|
1684
|
+
execute_args_t *ea = (execute_args_t *)arg;
|
|
1685
|
+
multi_session_t *session = ea->session;
|
|
1686
|
+
retry_config_t *retry_cfg = ea->retry_cfg;
|
|
1687
|
+
request_options_t *opts = ea->opts;
|
|
1688
|
+
int count = session->count;
|
|
1500
1689
|
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
rc->done = 1;
|
|
1504
|
-
rc->setup_error = "Failed to allocate a curl handle for the retry";
|
|
1505
|
-
continue;
|
|
1506
|
-
}
|
|
1690
|
+
for (int i = 0; i < count; i++)
|
|
1691
|
+
request_ctx_init(&session->requests[i], i);
|
|
1507
1692
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1693
|
+
completion_ctx_t cctx;
|
|
1694
|
+
completion_context_init(&cctx, ea, count);
|
|
1510
1695
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
run_multi_loop(session, &cctx, requests, invalid, opts, retry_indices,
|
|
1514
|
-
runnable_count);
|
|
1515
|
-
}
|
|
1696
|
+
if (opts->total_timeout_ms > 0)
|
|
1697
|
+
opts->deadline_ms = fast_now_ms() + opts->total_timeout_ms;
|
|
1516
1698
|
|
|
1517
|
-
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1699
|
+
run_multi_loop(session, &cctx, ea->requests, ea->invalid, opts, NULL, count);
|
|
1520
1700
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
VALUE response;
|
|
1525
|
-
|
|
1526
|
-
if (invalid[i]) {
|
|
1527
|
-
response = build_error_response(rc->setup_error ? rc->setup_error
|
|
1528
|
-
: "Invalid request configuration",
|
|
1529
|
-
ERR_INVALID_REQUEST, rc->attempts);
|
|
1530
|
-
} else if (!rc->done) {
|
|
1531
|
-
response =
|
|
1532
|
-
deadline_hit || (opts->deadline_ms > 0 && fast_now_ms() >= opts->deadline_ms)
|
|
1533
|
-
? build_error_response("Total timeout exceeded", ERR_DEADLINE, rc->attempts)
|
|
1534
|
-
: build_error_response("Request was not completed", ERR_NOT_COMPLETED,
|
|
1535
|
-
rc->attempts);
|
|
1536
|
-
} else if (rc->curl_result == CURLE_OK) {
|
|
1537
|
-
response = build_response(rc);
|
|
1538
|
-
} else {
|
|
1539
|
-
response = build_error_response_with_code(curl_easy_strerror(rc->curl_result),
|
|
1540
|
-
(int)rc->curl_result, rc->attempts);
|
|
1541
|
-
}
|
|
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);
|
|
1542
1704
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
}
|
|
1705
|
+
if (!ea->stream && ea->target <= 0)
|
|
1706
|
+
store_execution_results(ea, &cctx, deadline_hit);
|
|
1546
1707
|
|
|
1547
|
-
return stream ? Qnil : cctx.results;
|
|
1708
|
+
return ea->stream ? Qnil : cctx.results;
|
|
1548
1709
|
}
|
|
1549
1710
|
|
|
1550
1711
|
typedef enum {
|
|
@@ -1777,7 +1938,7 @@ static void *lazy_poll_without_gvl(void *arg) {
|
|
|
1777
1938
|
long long started = fast_now_ms();
|
|
1778
1939
|
int before = session->still_running;
|
|
1779
1940
|
|
|
1780
|
-
while (!session
|
|
1941
|
+
while (!session_cancelled(session)) {
|
|
1781
1942
|
int numfds = 0;
|
|
1782
1943
|
curl_multi_poll(session->multi, NULL, 0, POLL_TIMEOUT_MS, &numfds);
|
|
1783
1944
|
curl_multi_perform(session->multi, &session->still_running);
|
|
@@ -1962,6 +2123,49 @@ static VALUE cleanup_lazy_execute(VALUE arg) {
|
|
|
1962
2123
|
return Qnil;
|
|
1963
2124
|
}
|
|
1964
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
|
+
|
|
1965
2169
|
static VALUE internal_lazy_execute(VALUE requests, VALUE options, int target, int stream) {
|
|
1966
2170
|
if (!rb_respond_to(requests, rb_intern("each")))
|
|
1967
2171
|
rb_raise(rb_eArgError, "requests must be an Array or respond to #each");
|
|
@@ -1971,54 +2175,14 @@ static VALUE internal_lazy_execute(VALUE requests, VALUE options, int target, in
|
|
|
1971
2175
|
|
|
1972
2176
|
int max_conn;
|
|
1973
2177
|
parse_options(options, &ctx.opts, &max_conn, &ctx.retry_cfg);
|
|
2178
|
+
parse_retry_codes(options, &ctx.retry_cfg);
|
|
1974
2179
|
ctx.buffer_size = NIL_P(options) ? max_conn
|
|
1975
2180
|
: parse_int_option(options, KEY_BUFFER, "buffer", 1,
|
|
1976
2181
|
MAX_BUFFER, max_conn, NULL);
|
|
1977
2182
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
"FastCurl: retries are not supported in stream_execute, ignoring retries option");
|
|
1982
|
-
if (ctx.retry_cfg.retries_explicit && ctx.retry_cfg.max_retries > 0 && target > 0)
|
|
1983
|
-
rb_warn(
|
|
1984
|
-
"FastCurl: retries are not supported in first_execute, ignoring retries option");
|
|
1985
|
-
ctx.retry_cfg.max_retries = 0;
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
ctx.capacity = max_conn + ctx.buffer_size;
|
|
1989
|
-
ctx.source = requests;
|
|
1990
|
-
ctx.anchors = rb_ary_new2(ctx.capacity);
|
|
1991
|
-
ctx.next_index = 0;
|
|
1992
|
-
|
|
1993
|
-
ctx.cctx.results = stream ? Qnil : rb_ary_new();
|
|
1994
|
-
ctx.cctx.accept = target > 0 && !NIL_P(options) ? hash_aref_key(options, KEY_ACCEPT) : Qnil;
|
|
1995
|
-
ctx.cctx.completed = 0;
|
|
1996
|
-
ctx.cctx.target = target;
|
|
1997
|
-
ctx.cctx.stream = stream;
|
|
1998
|
-
|
|
1999
|
-
if (!NIL_P(ctx.cctx.accept) && !rb_respond_to(ctx.cctx.accept, rb_intern("call")))
|
|
2000
|
-
rb_raise(rb_eArgError, "accept must respond to #call");
|
|
2001
|
-
|
|
2002
|
-
multi_session_init(&ctx.session, curl_multi_init(), ctx.capacity, max_conn,
|
|
2003
|
-
ctx.opts.timeout_sec);
|
|
2004
|
-
if (!ctx.session.multi)
|
|
2005
|
-
rb_raise(rb_eNoMemError, "failed to initialize curl multi handle");
|
|
2006
|
-
multi_session_configure(ctx.session.multi, max_conn);
|
|
2007
|
-
|
|
2008
|
-
ctx.session.requests = calloc((size_t)ctx.capacity, sizeof(request_ctx_t));
|
|
2009
|
-
ctx.states = calloc((size_t)ctx.capacity, sizeof(int));
|
|
2010
|
-
ctx.free_slots = malloc(sizeof(int) * (size_t)ctx.capacity);
|
|
2011
|
-
ctx.pending_slots = malloc(sizeof(int) * (size_t)ctx.capacity);
|
|
2012
|
-
ctx.retry_at_ms = calloc((size_t)ctx.capacity, sizeof(long long));
|
|
2013
|
-
|
|
2014
|
-
if (!ctx.session.requests || !ctx.states || !ctx.free_slots || !ctx.pending_slots ||
|
|
2015
|
-
!ctx.retry_at_ms) {
|
|
2016
|
-
cleanup_lazy_execute((VALUE)&ctx);
|
|
2017
|
-
rb_raise(rb_eNoMemError, "failed to allocate lazy request state");
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
for (int slot = 0; slot < ctx.capacity; slot++)
|
|
2021
|
-
ctx.free_slots[ctx.free_count++] = slot;
|
|
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);
|
|
2022
2186
|
|
|
2023
2187
|
return rb_ensure(lazy_execute_body, (VALUE)&ctx, cleanup_lazy_execute, (VALUE)&ctx);
|
|
2024
2188
|
}
|
|
@@ -2053,7 +2217,7 @@ static VALUE scheduler_execute_thread(void *arg) {
|
|
|
2053
2217
|
static void cancel_scheduler_execute(scheduler_execute_ctx_t *ctx) {
|
|
2054
2218
|
multi_session_t *session = ctx->ea->session;
|
|
2055
2219
|
|
|
2056
|
-
session
|
|
2220
|
+
cancel_session(session);
|
|
2057
2221
|
#ifdef HAVE_CURL_MULTI_WAKEUP
|
|
2058
2222
|
if (session->multi)
|
|
2059
2223
|
curl_multi_wakeup(session->multi);
|
|
@@ -2120,63 +2284,17 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
2120
2284
|
int max_conn;
|
|
2121
2285
|
retry_config_t retry_cfg;
|
|
2122
2286
|
parse_options(options, &opts, &max_conn, &retry_cfg);
|
|
2287
|
+
parse_retry_codes(options, &retry_cfg);
|
|
2123
2288
|
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
if (!NIL_P(accept) && !rb_respond_to(accept, rb_intern("call")))
|
|
2127
|
-
rb_raise(rb_eArgError, "accept must respond to #call");
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
for (int i = 0; i < count; i++) {
|
|
2131
|
-
request_ctx_t probe;
|
|
2132
|
-
request_ctx_init(&probe, i);
|
|
2133
|
-
if (!request_ctx_prepare_easy(&probe)) {
|
|
2134
|
-
request_ctx_free(&probe);
|
|
2135
|
-
continue;
|
|
2136
|
-
}
|
|
2137
|
-
setup_easy_handle(&probe, rb_ary_entry(requests, i), &opts);
|
|
2138
|
-
int fatal = probe.setup_error_fatal;
|
|
2139
|
-
const char *message = probe.setup_error;
|
|
2140
|
-
char buffer[128];
|
|
2141
|
-
if (fatal && message) {
|
|
2142
|
-
snprintf(buffer, sizeof(buffer), "%s (request %d)", message, i);
|
|
2143
|
-
}
|
|
2144
|
-
request_ctx_free(&probe);
|
|
2145
|
-
if (fatal) {
|
|
2146
|
-
VALUE klass = NIL_P(fast_validation_error) ? rb_eArgError : fast_validation_error;
|
|
2147
|
-
rb_raise(klass, "%s", buffer);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
if (stream || target > 0) {
|
|
2152
|
-
if (retry_cfg.retries_explicit && retry_cfg.max_retries > 0 && stream)
|
|
2153
|
-
rb_warn(
|
|
2154
|
-
"FastCurl: retries are not supported in stream_execute, ignoring retries option");
|
|
2155
|
-
if (retry_cfg.retries_explicit && retry_cfg.max_retries > 0 && target > 0)
|
|
2156
|
-
rb_warn(
|
|
2157
|
-
"FastCurl: retries are not supported in first_execute, ignoring retries option");
|
|
2158
|
-
retry_cfg.max_retries = 0;
|
|
2159
|
-
}
|
|
2289
|
+
validate_execute_requests(requests, count, target, options, &opts);
|
|
2290
|
+
configure_retry_mode(&retry_cfg, target, stream);
|
|
2160
2291
|
|
|
2292
|
+
materialize_retry_codes(&retry_cfg);
|
|
2161
2293
|
multi_session_t session;
|
|
2162
2294
|
int *invalid = NULL;
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
if (!session.multi)
|
|
2168
|
-
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to initialize curl multi handle");
|
|
2169
|
-
|
|
2170
|
-
multi_session_configure(session.multi, max_conn);
|
|
2171
|
-
|
|
2172
|
-
session.requests = calloc((size_t)count, sizeof(request_ctx_t));
|
|
2173
|
-
if (!session.requests)
|
|
2174
|
-
cleanup_and_raise(&cleanup, rb_eNoMemError, "failed to allocate request contexts");
|
|
2175
|
-
|
|
2176
|
-
invalid = calloc((size_t)count, sizeof(int));
|
|
2177
|
-
cleanup.invalid = invalid;
|
|
2178
|
-
if (!invalid)
|
|
2179
|
-
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);
|
|
2180
2298
|
execute_args_t ea = {
|
|
2181
2299
|
.requests = requests,
|
|
2182
2300
|
.options = options,
|
data/lib/fast_curl/version.rb
CHANGED