From c8908b6e50fa942d9f6546b7bfbe8143cdfe9be3 Mon Sep 17 00:00:00 2001 From: htasta Date: Fri, 17 Apr 2026 12:35:33 +0200 Subject: [PATCH 1/8] tool: add a retry delay for all transfers to the same host on http error 429 --- src/tool_operate.c | 135 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) diff --git a/src/tool_operate.c b/src/tool_operate.c index 8ccd791bf5..3725c6f1a6 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -184,6 +184,104 @@ static curl_off_t VmsSpecialSize(const char *name, } #endif /* __VMS */ +/* linked-list structure for the 429 delay */ +struct curl_429_list { + char *hostname; + time_t startat; + struct curl_429_list *next; +}; + +/* a list of hosts and their delay timer for 429 errors */ +static struct curl_429_list *http_429_list = NULL; + +/* + * Set the delay for new transfers for a given host. + * If the host is not found, a new item is appended to the list. + * If the list is NULL a new list will be created. + * On error the function returns NULL, otherwise the list is returned. + */ +static struct curl_429_list *curl_429_delay_set(struct curl_429_list *list, + const char *host, + uint32_t delayms) +{ + struct curl_429_list *item; + struct curl_429_list *last_item; + struct curl_429_list *new_item; + time_t startat = time(NULL) + (delayms / 1000); + + /* if there is no list yet we create a new list */ + if(!list) { + new_item = curlx_malloc(sizeof(struct curl_429_list)); + if(!new_item) + return NULL; + + new_item->hostname = curlx_strdup(host); + new_item->startat = startat; + new_item->next = NULL; + return new_item; + } + + /* loop through the list to check if we already got this host */ + item = list; + do { + if(curl_strequal(item->hostname, host)) { + item->startat = startat; + return list; + } + last_item = item; + item = item->next; + } while(item); + + /* host was not found so we create a new item and append it to the list */ + new_item = curlx_malloc(sizeof(struct curl_429_list)); + if(!new_item) + return NULL; + + new_item->hostname = curlx_strdup(host); + new_item->startat = startat; + new_item->next = NULL; + last_item->next = new_item; + return list; +} + +/* + * Check the list if it contains the given host and return it. + * Returns NULL if the host was not found in the list. + */ +static struct curl_429_list *curl_429_delay_get(struct curl_429_list *list, + const char *host) +{ + struct curl_429_list *item = list; + + /* loop through the list to find this host */ + while(item) { + if(curl_strequal(item->hostname, host)) { + return item; + } + item = item->next; + } + + return NULL; /* host not found */ +} + +/* Free all the elements and their data. */ +static void curl_429_delay_free_all(struct curl_429_list *list) +{ + struct curl_429_list *next; + struct curl_429_list *item; + + if(!list) + return; + + item = list; + do { + next = item->next; + curlx_safefree(item->hostname); + curlx_free(item); + item = next; + } while(next); +} + struct per_transfer *transfers; /* first node */ static struct per_transfer *transfersl; /* last node */ @@ -436,6 +534,7 @@ static CURLcode retrycheck(struct OperationConfig *config, bool *retryp, uint32_t *delayms) { + bool is_http_429_error = FALSE; CURL *curl = per->curl; struct OutStruct *outs = &per->outs; enum retryreason reason = RETRY_NO; @@ -468,6 +567,7 @@ static CURLcode retrycheck(struct OperationConfig *config, switch(response) { case 408: /* Request Timeout */ case 429: /* Too Many Requests (RFC6585) */ + is_http_429_error = TRUE; case 500: /* Internal Server Error */ case 502: /* Bad Gateway */ case 503: /* Service Unavailable */ @@ -522,6 +622,21 @@ static CURLcode retrycheck(struct OperationConfig *config, /* no retry */ return CURLE_OK; per->retry_remaining--; + per->num_retries++; + *delayms = sleeptime; + + /* The retry delay for 429 applies to all requests to the same host. + Update the global 429 delay list for this host. */ + if(is_http_429_error) { + char *host = NULL; + CURLU *hurl = curl_url(); + curl_url_set(hurl, CURLUPART_URL, per->url, CURLU_GUESS_SCHEME); + curl_url_get(hurl, CURLUPART_HOST, &host, CURLU_URLDECODE); + http_429_list = curl_429_delay_set(http_429_list, host, *delayms); + if(!http_429_list) + return CURLE_OUT_OF_MEMORY; + curl_url_cleanup(hurl); + } /* Skip truncation of outfile if auto-resume is enabled for download and the partially received data is good. Only for HTTP GET requests in @@ -594,8 +709,6 @@ static CURLcode retrycheck(struct OperationConfig *config, outs->bytes = 0; /* clear for next round */ } } - per->num_retries++; - *delayms = sleeptime; result = CURLE_OK; } return result; @@ -1570,6 +1683,20 @@ static CURLcode add_parallel_transfers(CURLM *multi, CURLSH *share, sleeping = TRUE; continue; } + if(http_429_list) { + char *host = NULL; + CURLU *hurl = curl_url(); + curl_url_set(hurl, CURLUPART_URL, per->url, CURLU_GUESS_SCHEME); + curl_url_get(hurl, CURLUPART_HOST, &host, CURLU_URLDECODE); + struct curl_429_list *item = curl_429_delay_get(http_429_list, host); + curl_url_cleanup(hurl); + if(item && (time(NULL) < item->startat)) { + per->startat = item->startat; + per->added = FALSE; + sleeping = TRUE; + continue; + } + } per->added = TRUE; result = pre_transfer(per); @@ -1993,6 +2120,8 @@ static CURLcode parallel_transfers(CURLSH *share) &s->more_transfers, &s->added_transfers); if(result) { curl_multi_cleanup(s->multi); + curl_429_delay_free_all(http_429_list); + http_429_list = NULL; return result; } @@ -2053,6 +2182,8 @@ static CURLcode parallel_transfers(CURLSH *share) } curl_multi_cleanup(s->multi); + curl_429_delay_free_all(http_429_list); + http_429_list = NULL; return result; } From 262a2c843d21fac23bd88ad4db6591730afd3883 Mon Sep 17 00:00:00 2001 From: htasta Date: Mon, 20 Apr 2026 19:00:42 +0200 Subject: [PATCH 2/8] tool: use origin instead of host for 429 delay identification --- src/tool_operate.c | 168 +++++++++++++++++++++++++++++---------------- src/tool_operate.h | 1 + 2 files changed, 110 insertions(+), 59 deletions(-) diff --git a/src/tool_operate.c b/src/tool_operate.c index 3725c6f1a6..8904859d13 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -186,82 +186,77 @@ static curl_off_t VmsSpecialSize(const char *name, /* linked-list structure for the 429 delay */ struct curl_429_list { - char *hostname; + char *origin; time_t startat; struct curl_429_list *next; }; -/* a list of hosts and their delay timer for 429 errors */ +/* a list of origins and their delay timer for 429 errors */ static struct curl_429_list *http_429_list = NULL; /* - * Set the delay for new transfers for a given host. - * If the host is not found, a new item is appended to the list. + * Set the delay for new transfers for a given origin. + * If the origin is not found, a new item is appended to the list. * If the list is NULL a new list will be created. * On error the function returns NULL, otherwise the list is returned. */ static struct curl_429_list *curl_429_delay_set(struct curl_429_list *list, - const char *host, + const char *origin, uint32_t delayms) { - struct curl_429_list *item; - struct curl_429_list *last_item; - struct curl_429_list *new_item; - time_t startat = time(NULL) + (delayms / 1000); + struct curl_429_list *item = NULL; + struct curl_429_list *last_item = NULL; + struct curl_429_list *new_item = NULL; + time_t startat = delayms ? time(NULL) + (delayms / 1000) : 0; - /* if there is no list yet we create a new list */ - if(!list) { - new_item = curlx_malloc(sizeof(struct curl_429_list)); - if(!new_item) - return NULL; - - new_item->hostname = curlx_strdup(host); - new_item->startat = startat; - new_item->next = NULL; - return new_item; + /* try to find the origin in the existing list and update it's timer */ + if(list) { + item = list; + do { + if(curl_strequal(item->origin, origin)) { + item->startat = startat; + return list; + } + last_item = item; + item = item->next; + } while(item); } - /* loop through the list to check if we already got this host */ - item = list; - do { - if(curl_strequal(item->hostname, host)) { - item->startat = startat; - return list; - } - last_item = item; - item = item->next; - } while(item); - - /* host was not found so we create a new item and append it to the list */ + /* origin not found, so we create a new item */ new_item = curlx_malloc(sizeof(struct curl_429_list)); if(!new_item) return NULL; - - new_item->hostname = curlx_strdup(host); + new_item->origin = curlx_strdup(origin); + if(!new_item->origin) { + curlx_free(new_item); + return NULL; + } new_item->startat = startat; new_item->next = NULL; + if(!list) + return new_item; /* the new item is the newly created list */ last_item->next = new_item; return list; } /* - * Check the list if it contains the given host and return it. - * Returns NULL if the host was not found in the list. + * Check the list if it contains the given origin and return it. + * Returns NULL if the origin was not found in the list. */ static struct curl_429_list *curl_429_delay_get(struct curl_429_list *list, - const char *host) + const char *origin) { struct curl_429_list *item = list; - /* loop through the list to find this host */ + /* loop through the list to find this origin */ while(item) { - if(curl_strequal(item->hostname, host)) { + if(curl_strequal(item->origin, origin)) { return item; } item = item->next; } - return NULL; /* host not found */ + return NULL; /* origin not found */ } /* Free all the elements and their data. */ @@ -276,12 +271,70 @@ static void curl_429_delay_free_all(struct curl_429_list *list) item = list; do { next = item->next; - curlx_safefree(item->hostname); + curlx_safefree(item->origin); curlx_free(item); item = next; } while(next); } +/* + * extract the host, port and scheme from the URL and write it to porigin + * porigin needs to be freed by the user of this function + * if the URL is invalid origin is set to "-/-/-" + */ +static CURLcode set_per_transfer_origin(const char *url, char **porigin) +{ + char *host = NULL; + char *port = NULL; + char *scheme = NULL; + char *origin = NULL; + size_t len_origin = 0; + CURLcode err = CURLE_OK; + CURLUcode uerr = CURLUE_OK; + CURLU *uh = curl_url(); + + uerr = curl_url_set(uh, CURLUPART_URL, url, CURLU_GUESS_SCHEME); + if(uerr) + goto urlerr; + uerr = curl_url_get(uh, CURLUPART_HOST, &host, CURLU_URLDECODE); + if(uerr) + goto urlerr; + uerr = curl_url_get(uh, CURLUPART_PORT, &port, CURLU_DEFAULT_PORT); + if(uerr) + goto urlerr; + uerr = curl_url_get(uh, CURLUPART_SCHEME, &scheme, CURLU_DEFAULT_SCHEME); + if(uerr) + goto urlerr; + + len_origin = strlen(host) + strlen(port) + strlen(scheme) + 3; + origin = curlx_malloc(len_origin); + if(!origin) { + err = CURLE_OUT_OF_MEMORY; + goto clean; + } + curl_msnprintf(origin, len_origin, "%s/%s/%s", host, port, scheme); + *porigin = origin; + err = CURLE_OK; + goto clean; + +urlerr: + origin = curlx_strdup("-/-/-"); + if(!origin) { + err = CURLE_OUT_OF_MEMORY; + goto clean; + } + *porigin = origin; + err = CURLE_OK; + goto clean; + +clean: + curl_free(host); + curl_free(port); + curl_free(scheme); + curl_url_cleanup(uh); + return err; +} + struct per_transfer *transfers; /* first node */ static struct per_transfer *transfersl; /* last node */ @@ -335,6 +388,7 @@ static struct per_transfer *del_per_transfer(struct per_transfer *per) curlx_free(per->uploadfile); curlx_free(per->outfile); curlx_free(per->url); + curlx_free(per->origin); curl_easy_cleanup(per->curl); curlx_free(per); @@ -534,7 +588,7 @@ static CURLcode retrycheck(struct OperationConfig *config, bool *retryp, uint32_t *delayms) { - bool is_http_429_error = FALSE; + long http_error = 0; CURL *curl = per->curl; struct OutStruct *outs = &per->outs; enum retryreason reason = RETRY_NO; @@ -563,11 +617,11 @@ static CURLcode retrycheck(struct OperationConfig *config, /* This was HTTP(S) */ long response = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response); + http_error = response; switch(response) { case 408: /* Request Timeout */ case 429: /* Too Many Requests (RFC6585) */ - is_http_429_error = TRUE; case 500: /* Internal Server Error */ case 502: /* Bad Gateway */ case 503: /* Service Unavailable */ @@ -627,15 +681,11 @@ static CURLcode retrycheck(struct OperationConfig *config, /* The retry delay for 429 applies to all requests to the same host. Update the global 429 delay list for this host. */ - if(is_http_429_error) { - char *host = NULL; - CURLU *hurl = curl_url(); - curl_url_set(hurl, CURLUPART_URL, per->url, CURLU_GUESS_SCHEME); - curl_url_get(hurl, CURLUPART_HOST, &host, CURLU_URLDECODE); - http_429_list = curl_429_delay_set(http_429_list, host, *delayms); + if(http_error == 429) { + http_429_list = curl_429_delay_set(http_429_list, + per->origin, *delayms); if(!http_429_list) return CURLE_OUT_OF_MEMORY; - curl_url_cleanup(hurl); } /* Skip truncation of outfile if auto-resume is enabled for download and @@ -1543,6 +1593,11 @@ static CURLcode create_single(struct OperationConfig *config, if(!per->url) break; + /* store the origin in the per transfer struct for 429 delay checks */ + result = set_per_transfer_origin(per->url, &per->origin); + if(result) + return result; + result = setup_outfile(config, per, u, outs, skipped); if(result) return result; @@ -1684,12 +1739,8 @@ static CURLcode add_parallel_transfers(CURLM *multi, CURLSH *share, continue; } if(http_429_list) { - char *host = NULL; - CURLU *hurl = curl_url(); - curl_url_set(hurl, CURLUPART_URL, per->url, CURLU_GUESS_SCHEME); - curl_url_get(hurl, CURLUPART_HOST, &host, CURLU_URLDECODE); - struct curl_429_list *item = curl_429_delay_get(http_429_list, host); - curl_url_cleanup(hurl); + struct curl_429_list *item; + item = curl_429_delay_get(http_429_list, per->origin); if(item && (time(NULL) < item->startat)) { per->startat = item->startat; per->added = FALSE; @@ -2120,8 +2171,6 @@ static CURLcode parallel_transfers(CURLSH *share) &s->more_transfers, &s->added_transfers); if(result) { curl_multi_cleanup(s->multi); - curl_429_delay_free_all(http_429_list); - http_429_list = NULL; return result; } @@ -2182,8 +2231,6 @@ static CURLcode parallel_transfers(CURLSH *share) } curl_multi_cleanup(s->multi); - curl_429_delay_free_all(http_429_list); - http_429_list = NULL; return result; } @@ -2484,6 +2531,9 @@ static CURLcode run_all_transfers(CURLSH *share, global->noprogress = orig_noprogress; global->isatty = orig_isatty; + curl_429_delay_free_all(http_429_list); + http_429_list = NULL; + return result; } diff --git a/src/tool_operate.h b/src/tool_operate.h index 69b304dfc0..bfebb87572 100644 --- a/src/tool_operate.h +++ b/src/tool_operate.h @@ -44,6 +44,7 @@ struct per_transfer { struct curltime start; /* start of this transfer */ struct curltime retrystart; char *url; + char *origin; curl_off_t urlnum; /* the index of the given URL */ char *outfile; int infd; From c494e9e29d61708a6a3c18bfbf1bea183cc2a50d Mon Sep 17 00:00:00 2001 From: htasta Date: Sat, 16 May 2026 09:53:29 +0200 Subject: [PATCH 3/8] tool: dont alloc extra space for origins in the 429 delay list the list's lifetime is tied to the pre->transfers, so when the origin living in per-transfer will be freed the list is freed too. --- src/tool_operate.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tool_operate.c b/src/tool_operate.c index 8904859d13..40fb51fce5 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -201,7 +201,7 @@ static struct curl_429_list *http_429_list = NULL; * On error the function returns NULL, otherwise the list is returned. */ static struct curl_429_list *curl_429_delay_set(struct curl_429_list *list, - const char *origin, + char *origin, uint32_t delayms) { struct curl_429_list *item = NULL; @@ -226,7 +226,7 @@ static struct curl_429_list *curl_429_delay_set(struct curl_429_list *list, new_item = curlx_malloc(sizeof(struct curl_429_list)); if(!new_item) return NULL; - new_item->origin = curlx_strdup(origin); + new_item->origin = origin; if(!new_item->origin) { curlx_free(new_item); return NULL; @@ -271,7 +271,6 @@ static void curl_429_delay_free_all(struct curl_429_list *list) item = list; do { next = item->next; - curlx_safefree(item->origin); curlx_free(item); item = next; } while(next); From 87af4d594a0610120a5db92d78b14cc39c6757a9 Mon Sep 17 00:00:00 2001 From: htasta Date: Tue, 26 May 2026 21:18:00 +0200 Subject: [PATCH 4/8] tool: fixed double closing of file handle bug --- src/tool_operate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tool_operate.c b/src/tool_operate.c index 40fb51fce5..6042ff7b3a 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -855,6 +855,7 @@ static CURLcode post_close_output(struct per_transfer *per, /* Close the outs file */ if(outs->fopened && outs->stream) { rc = curlx_fclose(outs->stream); + outs->stream = NULL; if(!result && rc) { /* something went wrong in the writing process */ result = CURLE_WRITE_ERROR; From 2e59f65e4d1e924e7772ab497bb2a52e269b0c3c Mon Sep 17 00:00:00 2001 From: htasta Date: Sat, 16 May 2026 11:14:06 +0200 Subject: [PATCH 5/8] tests: adjusted the alloc limit in tests The HTTP error 429 delay list needs some extra allocs. The limit is increased in tests: 142,440,445,767 --- tests/data/test142 | 2 +- tests/data/test440 | 2 +- tests/data/test445 | 2 +- tests/data/test767 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/data/test142 b/tests/data/test142 index a7b85b1b5f..790d7a9446 100644 --- a/tests/data/test142 +++ b/tests/data/test142 @@ -188,7 +188,7 @@ RETR %TESTNUMBER QUIT -Allocations: 180 +Allocations: 190 Maximum allocated: 150000 diff --git a/tests/data/test440 b/tests/data/test440 index 3ed08f4730..a1517fe699 100644 --- a/tests/data/test440 +++ b/tests/data/test440 @@ -75,7 +75,7 @@ https://this.hsts.example./%TESTNUMBER 7 -Allocations: 160 +Allocations: 170 diff --git a/tests/data/test445 b/tests/data/test445 index a652cde588..d4eadb71ad 100644 --- a/tests/data/test445 +++ b/tests/data/test445 @@ -55,7 +55,7 @@ Refuse tunneling protocols through HTTP proxy 7 -Allocations: 1500 +Allocations: 1580 diff --git a/tests/data/test767 b/tests/data/test767 index a0d96f1b6d..201bcec4e1 100644 --- a/tests/data/test767 +++ b/tests/data/test767 @@ -49,7 +49,7 @@ Accept: */* -Allocations: 135 +Allocations: 140 Maximum allocated: 136000 From 8c31029f9e801d3a2869795ccb3100900f9a350b Mon Sep 17 00:00:00 2001 From: htasta Date: Mon, 8 Jun 2026 14:16:45 +0200 Subject: [PATCH 6/8] tool: copy origin for 429 list so it can be used even after transfers are freed --- src/tool_operate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tool_operate.c b/src/tool_operate.c index 6042ff7b3a..c1bce045cd 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -226,7 +226,7 @@ static struct curl_429_list *curl_429_delay_set(struct curl_429_list *list, new_item = curlx_malloc(sizeof(struct curl_429_list)); if(!new_item) return NULL; - new_item->origin = origin; + new_item->origin = curlx_strdup(origin); if(!new_item->origin) { curlx_free(new_item); return NULL; @@ -271,6 +271,7 @@ static void curl_429_delay_free_all(struct curl_429_list *list) item = list; do { next = item->next; + curlx_free(item->origin); curlx_free(item); item = next; } while(next); From 07e02f91b2dd41d2a3e1dfb2b1faa51192d2524b Mon Sep 17 00:00:00 2001 From: htasta Date: Thu, 2 Jul 2026 10:48:46 +0200 Subject: [PATCH 7/8] tool: User curl_maprintf to create host/port/scheme for 429 list --- src/tool_operate.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tool_operate.c b/src/tool_operate.c index c1bce045cd..37f3aa3b2b 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -209,7 +209,7 @@ static struct curl_429_list *curl_429_delay_set(struct curl_429_list *list, struct curl_429_list *new_item = NULL; time_t startat = delayms ? time(NULL) + (delayms / 1000) : 0; - /* try to find the origin in the existing list and update it's timer */ + /* try to find the origin in the existing list and update its timer */ if(list) { item = list; do { @@ -288,7 +288,6 @@ static CURLcode set_per_transfer_origin(const char *url, char **porigin) char *port = NULL; char *scheme = NULL; char *origin = NULL; - size_t len_origin = 0; CURLcode err = CURLE_OK; CURLUcode uerr = CURLUE_OK; CURLU *uh = curl_url(); @@ -306,13 +305,11 @@ static CURLcode set_per_transfer_origin(const char *url, char **porigin) if(uerr) goto urlerr; - len_origin = strlen(host) + strlen(port) + strlen(scheme) + 3; - origin = curlx_malloc(len_origin); + origin = curl_maprintf("%s/%s/%s", host, port, scheme); if(!origin) { err = CURLE_OUT_OF_MEMORY; goto clean; } - curl_msnprintf(origin, len_origin, "%s/%s/%s", host, port, scheme); *porigin = origin; err = CURLE_OK; goto clean; From 5625eec8740d47f65e2cd099384b8c4ad5ae7409 Mon Sep 17 00:00:00 2001 From: htasta Date: Mon, 6 Jul 2026 20:53:13 +0200 Subject: [PATCH 8/8] tool: only populate the origin field of the per_transfer struct if 429 error happens --- src/tool_operate.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/tool_operate.c b/src/tool_operate.c index 37f3aa3b2b..4096f008b8 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -280,7 +280,6 @@ static void curl_429_delay_free_all(struct curl_429_list *list) /* * extract the host, port and scheme from the URL and write it to porigin * porigin needs to be freed by the user of this function - * if the URL is invalid origin is set to "-/-/-" */ static CURLcode set_per_transfer_origin(const char *url, char **porigin) { @@ -315,13 +314,7 @@ static CURLcode set_per_transfer_origin(const char *url, char **porigin) goto clean; urlerr: - origin = curlx_strdup("-/-/-"); - if(!origin) { - err = CURLE_OUT_OF_MEMORY; - goto clean; - } - *porigin = origin; - err = CURLE_OK; + err = urlerr_cvt(uerr); goto clean; clean: @@ -676,9 +669,14 @@ static CURLcode retrycheck(struct OperationConfig *config, per->num_retries++; *delayms = sleeptime; - /* The retry delay for 429 applies to all requests to the same host. - Update the global 429 delay list for this host. */ + /* The retry delay for 429 applies to all requests to the same origin. + Update the global 429 delay list for this origin. */ if(http_error == 429) { + if(!per->origin) { + CURLcode err = set_per_transfer_origin(per->url, &per->origin); + if(err) + return err; + } http_429_list = curl_429_delay_set(http_429_list, per->origin, *delayms); if(!http_429_list) @@ -1591,11 +1589,6 @@ static CURLcode create_single(struct OperationConfig *config, if(!per->url) break; - /* store the origin in the per transfer struct for 429 delay checks */ - result = set_per_transfer_origin(per->url, &per->origin); - if(result) - return result; - result = setup_outfile(config, per, u, outs, skipped); if(result) return result;