fast_curl 0.1.1 → 0.2.0
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 +20 -0
- data/ext/fast_curl/fast_curl.c +319 -40
- data/lib/fast_curl/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 279ce69cb40c48246c4e6276260a928f4e03ae86e691f876492ba25a39eb00c7
|
|
4
|
+
data.tar.gz: 577583ed2fde2e2653f1e27f22caca08846fd32bb49524a59f44e5b3ba3acee9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e0edb04a304a46adaab5368289d2332672fcd7654506c6449cde10ea5b5620cc4ba71549f60fccb0308d2ef67c8dc4e4e54304efa8c84dfc1631f360f89cdc8
|
|
7
|
+
data.tar.gz: 54693fc0a4189cf60e48f35b92d4f82ba1971b170cf54b610ed27378b5c296c50fa88798b4d0ac135ca3734b1cce7abf0b5214ed60e18aa90f0f158d8f708199
|
data/README.md
CHANGED
|
@@ -76,6 +76,23 @@ FastCurl.stream_get(urls, connections: 50) do |index, response|
|
|
|
76
76
|
end
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### Retry functionality (v0.2.0+)
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# Automatic retry on network errors (timeout, connection issues)
|
|
83
|
+
results = FastCurl.get([
|
|
84
|
+
{ url: "https://unreliable-api.com/data" }
|
|
85
|
+
], retries: 3, retry_delay: 1000) # 3 retries with 1s delay
|
|
86
|
+
|
|
87
|
+
# Retry on specific HTTP status codes
|
|
88
|
+
results = FastCurl.get([
|
|
89
|
+
{ url: "https://api.example.com/data" }
|
|
90
|
+
], retries: 2, retry_codes: [500, 502, 503], retry_delay: 500)
|
|
91
|
+
|
|
92
|
+
# Disable retries (default is 1 retry)
|
|
93
|
+
results = FastCurl.get(urls, retries: 0)
|
|
94
|
+
```
|
|
95
|
+
|
|
79
96
|
### Inside Async
|
|
80
97
|
|
|
81
98
|
```ruby
|
|
@@ -119,6 +136,9 @@ end
|
|
|
119
136
|
|---|---|---|
|
|
120
137
|
| `connections` | 20 | Max parallel connections |
|
|
121
138
|
| `timeout` | 30 | Per-request timeout in seconds |
|
|
139
|
+
| `retries` | 1 | Number of retry attempts (0-10) |
|
|
140
|
+
| `retry_delay` | 0 | Delay between retries in milliseconds |
|
|
141
|
+
| `retry_codes` | [] | HTTP status codes to retry on |
|
|
122
142
|
|
|
123
143
|
## Performance
|
|
124
144
|
|
data/ext/fast_curl/fast_curl.c
CHANGED
|
@@ -11,12 +11,21 @@
|
|
|
11
11
|
#define MAX_RESPONSE_SIZE (100 * 1024 * 1024)
|
|
12
12
|
#define MAX_REDIRECTS 5
|
|
13
13
|
#define MAX_TIMEOUT 300
|
|
14
|
+
#define MAX_RETRIES 10
|
|
15
|
+
#define DEFAULT_RETRIES 1
|
|
16
|
+
#define DEFAULT_RETRY_DELAY 0
|
|
14
17
|
#define INITIAL_BUF_CAP 8192
|
|
15
18
|
#define INITIAL_HEADER_CAP 16
|
|
16
19
|
#define POLL_TIMEOUT_MS 50
|
|
17
20
|
#define FIBER_POLL_TIMEOUT_MS 10
|
|
18
21
|
#define HEADER_LINE_BUF_SIZE 512
|
|
19
22
|
|
|
23
|
+
static const CURLcode DEFAULT_RETRYABLE_CURLE[] = {
|
|
24
|
+
CURLE_COULDNT_CONNECT, CURLE_OPERATION_TIMEDOUT, CURLE_SEND_ERROR, CURLE_RECV_ERROR,
|
|
25
|
+
CURLE_GOT_NOTHING, CURLE_PARTIAL_FILE, CURLE_SSL_CONNECT_ERROR};
|
|
26
|
+
#define DEFAULT_RETRYABLE_CURLE_COUNT \
|
|
27
|
+
(int)(sizeof(DEFAULT_RETRYABLE_CURLE) / sizeof(DEFAULT_RETRYABLE_CURLE[0]))
|
|
28
|
+
|
|
20
29
|
static ID id_status;
|
|
21
30
|
static ID id_headers;
|
|
22
31
|
static ID id_body;
|
|
@@ -27,6 +36,9 @@ static ID id_timeout;
|
|
|
27
36
|
static ID id_connections;
|
|
28
37
|
static ID id_count;
|
|
29
38
|
static ID id_keys;
|
|
39
|
+
static ID id_retries;
|
|
40
|
+
static ID id_retry_delay;
|
|
41
|
+
static ID id_retry_codes;
|
|
30
42
|
static VALUE sym_status;
|
|
31
43
|
static VALUE sym_headers;
|
|
32
44
|
static VALUE sym_body;
|
|
@@ -36,6 +48,9 @@ static VALUE sym_method;
|
|
|
36
48
|
static VALUE sym_timeout;
|
|
37
49
|
static VALUE sym_connections;
|
|
38
50
|
static VALUE sym_count;
|
|
51
|
+
static VALUE sym_retries;
|
|
52
|
+
static VALUE sym_retry_delay;
|
|
53
|
+
static VALUE sym_retry_codes;
|
|
39
54
|
|
|
40
55
|
typedef struct {
|
|
41
56
|
char *data;
|
|
@@ -60,6 +75,10 @@ static inline void buffer_free(buffer_t *buf) {
|
|
|
60
75
|
buf->cap = 0;
|
|
61
76
|
}
|
|
62
77
|
|
|
78
|
+
static inline void buffer_reset(buffer_t *buf) {
|
|
79
|
+
buf->len = 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
63
82
|
static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
64
83
|
buffer_t *buf = (buffer_t *)userdata;
|
|
65
84
|
size_t total = size * nmemb;
|
|
@@ -114,6 +133,12 @@ static void header_list_free(header_list_t *h) {
|
|
|
114
133
|
h->cap = 0;
|
|
115
134
|
}
|
|
116
135
|
|
|
136
|
+
static void header_list_reset(header_list_t *h) {
|
|
137
|
+
for (int i = 0; i < h->count; i++)
|
|
138
|
+
free(h->entries[i].str);
|
|
139
|
+
h->count = 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
117
142
|
static size_t header_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
118
143
|
header_list_t *h = (header_list_t *)userdata;
|
|
119
144
|
size_t total = size * nmemb;
|
|
@@ -154,6 +179,8 @@ typedef struct {
|
|
|
154
179
|
header_list_t headers;
|
|
155
180
|
struct curl_slist *req_headers;
|
|
156
181
|
int done;
|
|
182
|
+
CURLcode curl_result;
|
|
183
|
+
long http_status;
|
|
157
184
|
} request_ctx_t;
|
|
158
185
|
|
|
159
186
|
static inline void request_ctx_init(request_ctx_t *ctx, int index) {
|
|
@@ -163,6 +190,8 @@ static inline void request_ctx_init(request_ctx_t *ctx, int index) {
|
|
|
163
190
|
header_list_init(&ctx->headers);
|
|
164
191
|
ctx->req_headers = NULL;
|
|
165
192
|
ctx->done = 0;
|
|
193
|
+
ctx->curl_result = CURLE_OK;
|
|
194
|
+
ctx->http_status = 0;
|
|
166
195
|
}
|
|
167
196
|
|
|
168
197
|
static void request_ctx_free(request_ctx_t *ctx) {
|
|
@@ -178,6 +207,26 @@ static void request_ctx_free(request_ctx_t *ctx) {
|
|
|
178
207
|
}
|
|
179
208
|
}
|
|
180
209
|
|
|
210
|
+
static int request_ctx_reset_for_retry(request_ctx_t *ctx) {
|
|
211
|
+
if (ctx->easy) {
|
|
212
|
+
curl_easy_cleanup(ctx->easy);
|
|
213
|
+
ctx->easy = NULL;
|
|
214
|
+
}
|
|
215
|
+
buffer_reset(&ctx->body);
|
|
216
|
+
header_list_reset(&ctx->headers);
|
|
217
|
+
if (ctx->req_headers) {
|
|
218
|
+
curl_slist_free_all(ctx->req_headers);
|
|
219
|
+
ctx->req_headers = NULL;
|
|
220
|
+
}
|
|
221
|
+
ctx->easy = curl_easy_init();
|
|
222
|
+
if (!ctx->easy)
|
|
223
|
+
return 0;
|
|
224
|
+
ctx->done = 0;
|
|
225
|
+
ctx->curl_result = CURLE_OK;
|
|
226
|
+
ctx->http_status = 0;
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
229
|
+
|
|
181
230
|
typedef struct {
|
|
182
231
|
CURLM *multi;
|
|
183
232
|
request_ctx_t *requests;
|
|
@@ -187,6 +236,13 @@ typedef struct {
|
|
|
187
236
|
int max_connections;
|
|
188
237
|
} multi_session_t;
|
|
189
238
|
|
|
239
|
+
typedef struct {
|
|
240
|
+
int max_retries;
|
|
241
|
+
long retry_delay_ms;
|
|
242
|
+
int *retry_http_codes;
|
|
243
|
+
int retry_http_count;
|
|
244
|
+
} retry_config_t;
|
|
245
|
+
|
|
190
246
|
static VALUE build_response(request_ctx_t *ctx) {
|
|
191
247
|
long status = 0;
|
|
192
248
|
curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &status);
|
|
@@ -318,8 +374,8 @@ static CURLcode setup_method_and_body(CURL *easy, VALUE method, VALUE body) {
|
|
|
318
374
|
}
|
|
319
375
|
|
|
320
376
|
if (!NIL_P(body)) {
|
|
321
|
-
CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDS, StringValuePtr(body));
|
|
322
377
|
CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDSIZE, RSTRING_LEN(body));
|
|
378
|
+
CURL_SETOPT_CHECK(easy, CURLOPT_COPYPOSTFIELDS, StringValuePtr(body));
|
|
323
379
|
}
|
|
324
380
|
|
|
325
381
|
return CURLE_OK;
|
|
@@ -480,25 +536,26 @@ static int process_completed(multi_session_t *session, completion_ctx_t *cctx) {
|
|
|
480
536
|
if (!ctx || ctx->done)
|
|
481
537
|
continue;
|
|
482
538
|
ctx->done = 1;
|
|
483
|
-
|
|
484
|
-
VALUE response;
|
|
539
|
+
ctx->curl_result = msg->data.result;
|
|
485
540
|
if (msg->data.result == CURLE_OK) {
|
|
486
|
-
|
|
487
|
-
} else {
|
|
488
|
-
response = build_error_response_with_code(curl_easy_strerror(msg->data.result),
|
|
489
|
-
(int)msg->data.result);
|
|
541
|
+
curl_easy_getinfo(ctx->easy, CURLINFO_RESPONSE_CODE, &ctx->http_status);
|
|
490
542
|
}
|
|
491
543
|
|
|
492
|
-
VALUE pair = rb_ary_new_from_args(2, INT2NUM(ctx->index), response);
|
|
493
|
-
|
|
494
544
|
if (cctx->stream) {
|
|
545
|
+
VALUE response;
|
|
546
|
+
if (msg->data.result == CURLE_OK) {
|
|
547
|
+
response = build_response(ctx);
|
|
548
|
+
} else {
|
|
549
|
+
response = build_error_response_with_code(curl_easy_strerror(msg->data.result),
|
|
550
|
+
(int)msg->data.result);
|
|
551
|
+
}
|
|
552
|
+
VALUE pair = rb_ary_new_from_args(2, INT2NUM(ctx->index), response);
|
|
495
553
|
rb_yield(pair);
|
|
496
|
-
|
|
497
|
-
|
|
554
|
+
cctx->completed++;
|
|
555
|
+
} else {
|
|
556
|
+
cctx->completed++;
|
|
498
557
|
}
|
|
499
558
|
|
|
500
|
-
cctx->completed++;
|
|
501
|
-
|
|
502
559
|
if (cctx->target > 0 && cctx->completed >= cctx->target)
|
|
503
560
|
return 1;
|
|
504
561
|
}
|
|
@@ -506,9 +563,102 @@ static int process_completed(multi_session_t *session, completion_ctx_t *cctx) {
|
|
|
506
563
|
return 0;
|
|
507
564
|
}
|
|
508
565
|
|
|
509
|
-
static void
|
|
566
|
+
static void run_multi_loop(multi_session_t *session, completion_ctx_t *cctx) {
|
|
567
|
+
if (has_fiber_scheduler()) {
|
|
568
|
+
for (;;) {
|
|
569
|
+
CURLMcode mc = curl_multi_perform(session->multi, &session->still_running);
|
|
570
|
+
if (mc != CURLM_OK)
|
|
571
|
+
break;
|
|
572
|
+
if (process_completed(session, cctx))
|
|
573
|
+
break;
|
|
574
|
+
if (session->still_running == 0)
|
|
575
|
+
break;
|
|
576
|
+
|
|
577
|
+
int numfds = 0;
|
|
578
|
+
curl_multi_poll(session->multi, NULL, 0, FIBER_POLL_TIMEOUT_MS, &numfds);
|
|
579
|
+
rb_thread_schedule();
|
|
580
|
+
}
|
|
581
|
+
process_completed(session, cctx);
|
|
582
|
+
} else {
|
|
583
|
+
if (cctx->stream || cctx->target > 0) {
|
|
584
|
+
curl_multi_perform(session->multi, &session->still_running);
|
|
585
|
+
while (session->still_running > 0) {
|
|
586
|
+
rb_thread_call_without_gvl(poll_without_gvl, session, unblock_perform, session);
|
|
587
|
+
if (process_completed(session, cctx))
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
process_completed(session, cctx);
|
|
591
|
+
} else {
|
|
592
|
+
session->still_running = 1;
|
|
593
|
+
curl_multi_perform(session->multi, &session->still_running);
|
|
594
|
+
rb_thread_call_without_gvl(perform_without_gvl, session, unblock_perform, session);
|
|
595
|
+
process_completed(session, cctx);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
static int is_default_retryable_curle(CURLcode code) {
|
|
601
|
+
for (int i = 0; i < DEFAULT_RETRYABLE_CURLE_COUNT; i++) {
|
|
602
|
+
if (DEFAULT_RETRYABLE_CURLE[i] == code)
|
|
603
|
+
return 1;
|
|
604
|
+
}
|
|
605
|
+
return 0;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
static int should_retry(request_ctx_t *ctx, retry_config_t *retry_cfg) {
|
|
609
|
+
if (ctx->curl_result != CURLE_OK) {
|
|
610
|
+
return is_default_retryable_curle(ctx->curl_result);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (retry_cfg->retry_http_count > 0) {
|
|
614
|
+
for (int i = 0; i < retry_cfg->retry_http_count; i++) {
|
|
615
|
+
if (retry_cfg->retry_http_codes[i] == (int)ctx->http_status)
|
|
616
|
+
return 1;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return 0;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
typedef struct {
|
|
624
|
+
long delay_ms;
|
|
625
|
+
} sleep_arg_t;
|
|
626
|
+
|
|
627
|
+
static void *sleep_without_gvl(void *arg) {
|
|
628
|
+
sleep_arg_t *sa = (sleep_arg_t *)arg;
|
|
629
|
+
struct timespec ts;
|
|
630
|
+
ts.tv_sec = sa->delay_ms / 1000;
|
|
631
|
+
ts.tv_nsec = (sa->delay_ms % 1000) * 1000000L;
|
|
632
|
+
nanosleep(&ts, NULL);
|
|
633
|
+
return NULL;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
static void retry_delay_sleep(long delay_ms) {
|
|
637
|
+
if (delay_ms <= 0)
|
|
638
|
+
return;
|
|
639
|
+
|
|
640
|
+
if (has_fiber_scheduler()) {
|
|
641
|
+
long remaining = delay_ms;
|
|
642
|
+
while (remaining > 0) {
|
|
643
|
+
long chunk = remaining > FIBER_POLL_TIMEOUT_MS ? FIBER_POLL_TIMEOUT_MS : remaining;
|
|
644
|
+
sleep_arg_t sa = {.delay_ms = chunk};
|
|
645
|
+
sleep_without_gvl(&sa);
|
|
646
|
+
rb_thread_schedule();
|
|
647
|
+
remaining -= chunk;
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
sleep_arg_t sa = {.delay_ms = delay_ms};
|
|
651
|
+
rb_thread_call_without_gvl(sleep_without_gvl, &sa, unblock_perform, NULL);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
static void parse_options(VALUE options, long *timeout, int *max_conn, retry_config_t *retry_cfg) {
|
|
510
656
|
*timeout = 30;
|
|
511
657
|
*max_conn = 20;
|
|
658
|
+
retry_cfg->max_retries = DEFAULT_RETRIES;
|
|
659
|
+
retry_cfg->retry_delay_ms = DEFAULT_RETRY_DELAY;
|
|
660
|
+
retry_cfg->retry_http_codes = NULL;
|
|
661
|
+
retry_cfg->retry_http_count = 0;
|
|
512
662
|
|
|
513
663
|
if (NIL_P(options) || !rb_obj_is_kind_of(options, rb_cHash))
|
|
514
664
|
return;
|
|
@@ -532,6 +682,40 @@ static void parse_options(VALUE options, long *timeout, int *max_conn) {
|
|
|
532
682
|
conn_val = 20;
|
|
533
683
|
*max_conn = conn_val;
|
|
534
684
|
}
|
|
685
|
+
|
|
686
|
+
VALUE r = rb_hash_aref(options, sym_retries);
|
|
687
|
+
if (!NIL_P(r)) {
|
|
688
|
+
int retries_val = NUM2INT(r);
|
|
689
|
+
if (retries_val < 0)
|
|
690
|
+
retries_val = 0;
|
|
691
|
+
if (retries_val > MAX_RETRIES)
|
|
692
|
+
retries_val = MAX_RETRIES;
|
|
693
|
+
retry_cfg->max_retries = retries_val;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
VALUE rd = rb_hash_aref(options, sym_retry_delay);
|
|
697
|
+
if (!NIL_P(rd)) {
|
|
698
|
+
long delay_val = NUM2LONG(rd);
|
|
699
|
+
if (delay_val < 0)
|
|
700
|
+
delay_val = 0;
|
|
701
|
+
if (delay_val > 30000)
|
|
702
|
+
delay_val = 30000;
|
|
703
|
+
retry_cfg->retry_delay_ms = delay_val;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
VALUE rc = rb_hash_aref(options, sym_retry_codes);
|
|
707
|
+
if (!NIL_P(rc) && rb_obj_is_kind_of(rc, rb_cArray)) {
|
|
708
|
+
int len = (int)RARRAY_LEN(rc);
|
|
709
|
+
if (len > 0) {
|
|
710
|
+
retry_cfg->retry_http_codes = malloc(sizeof(int) * len);
|
|
711
|
+
if (retry_cfg->retry_http_codes) {
|
|
712
|
+
retry_cfg->retry_http_count = len;
|
|
713
|
+
for (int i = 0; i < len; i++) {
|
|
714
|
+
retry_cfg->retry_http_codes[i] = NUM2INT(rb_ary_entry(rc, i));
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
535
719
|
}
|
|
536
720
|
|
|
537
721
|
static VALUE internal_execute(VALUE requests, VALUE options, int target, int stream) {
|
|
@@ -542,7 +726,19 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
542
726
|
|
|
543
727
|
long timeout_sec;
|
|
544
728
|
int max_conn;
|
|
545
|
-
|
|
729
|
+
retry_config_t retry_cfg;
|
|
730
|
+
parse_options(options, &timeout_sec, &max_conn, &retry_cfg);
|
|
731
|
+
|
|
732
|
+
/* Disable retries for stream and first_execute modes */
|
|
733
|
+
if (stream || target > 0) {
|
|
734
|
+
if (retry_cfg.max_retries > 0 && stream)
|
|
735
|
+
rb_warn(
|
|
736
|
+
"FastCurl: retries are not supported in stream_execute, ignoring retries option");
|
|
737
|
+
if (retry_cfg.max_retries > 0 && target > 0)
|
|
738
|
+
rb_warn(
|
|
739
|
+
"FastCurl: retries are not supported in first_execute, ignoring retries option");
|
|
740
|
+
retry_cfg.max_retries = 0;
|
|
741
|
+
}
|
|
546
742
|
|
|
547
743
|
multi_session_t session;
|
|
548
744
|
session.multi = curl_multi_init();
|
|
@@ -559,9 +755,20 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
559
755
|
session.requests = calloc(count, sizeof(request_ctx_t));
|
|
560
756
|
if (!session.requests) {
|
|
561
757
|
curl_multi_cleanup(session.multi);
|
|
758
|
+
if (retry_cfg.retry_http_codes)
|
|
759
|
+
free(retry_cfg.retry_http_codes);
|
|
562
760
|
rb_raise(rb_eNoMemError, "failed to allocate request contexts");
|
|
563
761
|
}
|
|
564
762
|
|
|
763
|
+
int *invalid = calloc(count, sizeof(int));
|
|
764
|
+
if (!invalid) {
|
|
765
|
+
free(session.requests);
|
|
766
|
+
curl_multi_cleanup(session.multi);
|
|
767
|
+
if (retry_cfg.retry_http_codes)
|
|
768
|
+
free(retry_cfg.retry_http_codes);
|
|
769
|
+
rb_raise(rb_eNoMemError, "failed to allocate tracking array");
|
|
770
|
+
}
|
|
771
|
+
|
|
565
772
|
int valid_requests = 0;
|
|
566
773
|
for (int i = 0; i < count; i++) {
|
|
567
774
|
VALUE req = rb_ary_entry(requests, i);
|
|
@@ -569,12 +776,14 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
569
776
|
|
|
570
777
|
if (!setup_easy_handle(&session.requests[i], req, timeout_sec)) {
|
|
571
778
|
session.requests[i].done = 1;
|
|
779
|
+
invalid[i] = 1;
|
|
572
780
|
continue;
|
|
573
781
|
}
|
|
574
782
|
|
|
575
783
|
CURLMcode mc = curl_multi_add_handle(session.multi, session.requests[i].easy);
|
|
576
784
|
if (mc != CURLM_OK) {
|
|
577
785
|
session.requests[i].done = 1;
|
|
786
|
+
invalid[i] = 1;
|
|
578
787
|
continue;
|
|
579
788
|
}
|
|
580
789
|
|
|
@@ -595,45 +804,103 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
595
804
|
rb_ary_store(cctx.results, i, Qnil);
|
|
596
805
|
}
|
|
597
806
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
807
|
+
run_multi_loop(&session, &cctx);
|
|
808
|
+
|
|
809
|
+
/* === Retry loop (execute mode only) === */
|
|
810
|
+
if (!stream && retry_cfg.max_retries > 0) {
|
|
811
|
+
int prev_all_failed = 0;
|
|
812
|
+
|
|
813
|
+
for (int attempt = 0; attempt < retry_cfg.max_retries; attempt++) {
|
|
814
|
+
int retry_count = 0;
|
|
815
|
+
int *retry_indices = malloc(sizeof(int) * count);
|
|
816
|
+
if (!retry_indices)
|
|
602
817
|
break;
|
|
603
|
-
|
|
818
|
+
|
|
819
|
+
for (int i = 0; i < count; i++) {
|
|
820
|
+
if (invalid[i])
|
|
821
|
+
continue;
|
|
822
|
+
if (!session.requests[i].done)
|
|
823
|
+
continue;
|
|
824
|
+
if (should_retry(&session.requests[i], &retry_cfg)) {
|
|
825
|
+
retry_indices[retry_count++] = i;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (retry_count == 0) {
|
|
830
|
+
free(retry_indices);
|
|
604
831
|
break;
|
|
605
|
-
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
int done_count = 0;
|
|
835
|
+
for (int i = 0; i < count; i++) {
|
|
836
|
+
if (!invalid[i] && session.requests[i].done)
|
|
837
|
+
done_count++;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
int all_failed_this_round = (retry_count == done_count);
|
|
841
|
+
|
|
842
|
+
if (all_failed_this_round && prev_all_failed) {
|
|
843
|
+
free(retry_indices);
|
|
606
844
|
break;
|
|
845
|
+
}
|
|
607
846
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
|
|
847
|
+
prev_all_failed = all_failed_this_round;
|
|
848
|
+
|
|
849
|
+
retry_delay_sleep(retry_cfg.retry_delay_ms);
|
|
850
|
+
|
|
851
|
+
for (int r = 0; r < retry_count; r++) {
|
|
852
|
+
int idx = retry_indices[r];
|
|
853
|
+
request_ctx_t *ctx = &session.requests[idx];
|
|
854
|
+
|
|
855
|
+
curl_multi_remove_handle(session.multi, ctx->easy);
|
|
856
|
+
|
|
857
|
+
if (!request_ctx_reset_for_retry(ctx)) {
|
|
858
|
+
ctx->done = 1;
|
|
859
|
+
invalid[idx] = 1;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
VALUE req = rb_ary_entry(requests, idx);
|
|
864
|
+
if (!setup_easy_handle(ctx, req, timeout_sec)) {
|
|
865
|
+
ctx->done = 1;
|
|
866
|
+
invalid[idx] = 1;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
CURLMcode mc = curl_multi_add_handle(session.multi, ctx->easy);
|
|
871
|
+
if (mc != CURLM_OK) {
|
|
872
|
+
ctx->done = 1;
|
|
873
|
+
invalid[idx] = 1;
|
|
874
|
+
}
|
|
620
875
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
process_completed(&session, &cctx);
|
|
876
|
+
|
|
877
|
+
free(retry_indices);
|
|
878
|
+
|
|
879
|
+
cctx.completed = 0;
|
|
880
|
+
run_multi_loop(&session, &cctx);
|
|
627
881
|
}
|
|
628
882
|
}
|
|
629
883
|
|
|
630
884
|
if (!stream) {
|
|
631
885
|
for (int i = 0; i < count; i++) {
|
|
632
|
-
|
|
886
|
+
request_ctx_t *ctx = &session.requests[i];
|
|
887
|
+
|
|
888
|
+
if (invalid[i]) {
|
|
633
889
|
VALUE error_response = build_error_response("Invalid request configuration");
|
|
634
890
|
VALUE pair = rb_ary_new_from_args(2, INT2NUM(i), error_response);
|
|
635
891
|
rb_ary_store(cctx.results, i, pair);
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
VALUE response;
|
|
896
|
+
if (ctx->curl_result == CURLE_OK) {
|
|
897
|
+
response = build_response(ctx);
|
|
898
|
+
} else {
|
|
899
|
+
response = build_error_response_with_code(curl_easy_strerror(ctx->curl_result),
|
|
900
|
+
(int)ctx->curl_result);
|
|
636
901
|
}
|
|
902
|
+
VALUE pair = rb_ary_new_from_args(2, INT2NUM(i), response);
|
|
903
|
+
rb_ary_store(cctx.results, i, pair);
|
|
637
904
|
}
|
|
638
905
|
}
|
|
639
906
|
|
|
@@ -642,7 +909,10 @@ static VALUE internal_execute(VALUE requests, VALUE options, int target, int str
|
|
|
642
909
|
request_ctx_free(&session.requests[i]);
|
|
643
910
|
}
|
|
644
911
|
free(session.requests);
|
|
912
|
+
free(invalid);
|
|
645
913
|
curl_multi_cleanup(session.multi);
|
|
914
|
+
if (retry_cfg.retry_http_codes)
|
|
915
|
+
free(retry_cfg.retry_http_codes);
|
|
646
916
|
|
|
647
917
|
return stream ? Qnil : cctx.results;
|
|
648
918
|
}
|
|
@@ -690,6 +960,9 @@ void Init_fast_curl(void) {
|
|
|
690
960
|
id_connections = rb_intern("connections");
|
|
691
961
|
id_count = rb_intern("count");
|
|
692
962
|
id_keys = rb_intern("keys");
|
|
963
|
+
id_retries = rb_intern("retries");
|
|
964
|
+
id_retry_delay = rb_intern("retry_delay");
|
|
965
|
+
id_retry_codes = rb_intern("retry_codes");
|
|
693
966
|
|
|
694
967
|
sym_status = ID2SYM(id_status);
|
|
695
968
|
rb_gc_register_address(&sym_status);
|
|
@@ -709,6 +982,12 @@ void Init_fast_curl(void) {
|
|
|
709
982
|
rb_gc_register_address(&sym_connections);
|
|
710
983
|
sym_count = ID2SYM(id_count);
|
|
711
984
|
rb_gc_register_address(&sym_count);
|
|
985
|
+
sym_retries = ID2SYM(id_retries);
|
|
986
|
+
rb_gc_register_address(&sym_retries);
|
|
987
|
+
sym_retry_delay = ID2SYM(id_retry_delay);
|
|
988
|
+
rb_gc_register_address(&sym_retry_delay);
|
|
989
|
+
sym_retry_codes = ID2SYM(id_retry_codes);
|
|
990
|
+
rb_gc_register_address(&sym_retry_codes);
|
|
712
991
|
|
|
713
992
|
VALUE mFastCurl = rb_define_module("FastCurl");
|
|
714
993
|
|
data/lib/fast_curl/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fast_curl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- roman-haidarov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -96,7 +96,8 @@ dependencies:
|
|
|
96
96
|
version: '1.8'
|
|
97
97
|
description: Parallel HTTP requests via libcurl curl_multi API. Releases GVL during
|
|
98
98
|
I/O, compatible with Async gem and Fiber scheduler. Supports execute (all), first_execute
|
|
99
|
-
(first N), stream_execute (yield as ready).
|
|
99
|
+
(first N), stream_execute (yield as ready). Built-in retry functionality for network
|
|
100
|
+
errors and custom HTTP status codes.
|
|
100
101
|
email:
|
|
101
102
|
- roman.haidarov@gmail.com
|
|
102
103
|
executables: []
|