Skip to content

tool: add a retry delay for all transfers to the same host on http error 429 - #21355

Closed
htasta2 wants to merge 8 commits into
curl:masterfrom
htasta2:429-delay
Closed

htasta2 wants to merge 8 commits into
curl:masterfrom
htasta2:429-delay

Conversation

@htasta2

@htasta2 htasta2 commented Apr 17, 2026 •

Copy link
Copy Markdown

Don't start new transfers to the same host if this host has returned a 429 error.

The --retry-delay setting is used for all transfers to the same host on error 429.

@vszakats vszakats left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest moving the 3 new functions within tool_operate.c as static ones.

And remove the double newlines (nit).

@htasta2

htasta2 commented Apr 17, 2026

Copy link
Copy Markdown
Author

I suggest moving the 3 new functions within tool_operate.c as static ones.

I put them in there because of this sentence in the CONTRIBUTE.md

If you bring completely new functionality, try writing it in a new source file.

I'll move them.

And remove the double newlines (nit).

What exactly do you mean with this? What is "nit"?

@vszakats

Copy link
Copy Markdown
Member

What exactly do you mean with this? What is "nit"?

It's a minor issue, e.g. whitespace.

@htasta2
htasta2 force-pushed the 429-delay branch 2 times, most recently from e546506 to 61ea829 Compare April 17, 2026 12:33
@htasta2

htasta2 commented Apr 17, 2026

Copy link
Copy Markdown
Author

It's all in tool_operate.c now and no more double new line between functions.

Comment thread src/tool_operate.c Resolved
@htasta2
htasta2 force-pushed the 429-delay branch 2 times, most recently from a048e91 to f69d70b Compare April 17, 2026 18:48
Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
@htasta2

htasta2 commented Apr 20, 2026

Copy link
Copy Markdown
Author

It's using origin now instead of host as per your request.

Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
Comment thread src/tool_operate.c Resolved
@htasta2
htasta2 force-pushed the 429-delay branch 3 times, most recently from fd2ec2f to 3098f22 Compare April 21, 2026 08:53
@htasta2

htasta2 commented Apr 21, 2026 •

Copy link
Copy Markdown
Author

Issues fixed.

Can I pull the http response out of the if/else or is there a deeper reason why it's declared in both those blocks instead of outside?

@htasta2
htasta2 requested a review from bagder April 21, 2026 09:11
@bagder

bagder commented Apr 26, 2026

Copy link
Copy Markdown
Member

It is helpful if you select "resolve conversation" for the remarks you think are are addressed. Otherwise it looks like all remarks are still pending...

Comment thread src/tool_operate.c Resolved
@htasta2
htasta2 force-pushed the 429-delay branch 2 times, most recently from 4de7304 to 9c93701 Compare April 27, 2026 07:38
@htasta2

htasta2 commented Apr 27, 2026

Copy link
Copy Markdown
Author

Resolved all conversations and it's using curl_msnprintf() now.

No checks for the return value of this function as I've seen it done plenty of times in other places like that.

@htasta2
htasta2 requested a review from bagder April 27, 2026 07:43

@bagder bagder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A primary reason for the revert was the complaint on lifetime management. The 429 list may live longer than the individual transfers so the origin pointers might then point to freed memory. Won't they?

Comment thread src/tool_operate.c Outdated
goto clean;

urlerr:
origin = curlx_strdup("-/-/-");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on why it sets a dummy origin if one of the URL API functions fail? When is that useful?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's useful when the URL is invalid. This will be handled at a later stage and with a dummy it will get to that later stage where other valid URLs will still be processed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs a test case to prove that this is the best way. It does not make sense to me.

Comment thread src/tool_operate.c Resolved
@htasta2

htasta2 commented Jun 2, 2026

Copy link
Copy Markdown
Author

A primary reason for the revert was the complaint on lifetime management. The 429 list may live longer than the individual transfers so the origin pointers might then point to freed memory. Won't they?

The list is cleaned up right after the transfers are cleaned up, I don't see where the list items would be used after the transfers are over.

outcast36 pushed a commit to greearb/curl that referenced this pull request Jun 3, 2026
@htasta2

htasta2 commented Jun 8, 2026

Copy link
Copy Markdown
Author

I changed it so the list is using a copy of the origin from the transfers.
This way the list could be used even after all transfers have completed and freed.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread src/tool_operate.c Resolved

@bagder bagder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a test or two for this feature to make sure it works now and to make sure we don't accidentally break it the future.

Comment thread src/tool_operate.c Outdated
goto clean;

urlerr:
origin = curlx_strdup("-/-/-");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs a test case to prove that this is the best way. It does not make sense to me.

Comment thread src/tool_operate.c Outdated
break;

/* store the origin in the per transfer struct for 429 delay checks */
result = set_per_transfer_origin(per->url, &per->origin);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why create this unconditionally and use lots of allocations for all transfers unconditionally when it is only needed for 429 responses? Why not wait and create the origin once and if it is needed?

@htasta2 htasta2 Jul 1, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I return an error at this point instead of creating a generic host/port/scheme curl will exit with the error instead of processing the valid URL after the invalid one.

For example if I give a list like this:

https://example.com
htps:exampl.com
http://not-an-example.com

Nothing will be downloaded despite 2 of the 3 URLs being valid.


I created this unconditionally because otherwise the URL will be parsed all the time. I had it initially like that, I can go back to that one.

If I have to redo this, then I also have to update the translate of URL errors to general curl errors as I ran into such an issue when I had it like that. So please tell me clearly which way you want it so I don't have to then redo it again.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created this unconditionally because otherwise the URL will be parsed all the time.

Why would it? if you get a 429 and ->origin is a NULL, you can populate the ->origin then. If it would come back to that condition later, ->origin would already be populated and you don't need to parse the URL again.

The point being that you avoid URL parsing for all the URLs that never get a 429.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I understand now. So still use the per struct but only fill it on 429 error. Yeah, that makes sense. I didn't use the per struct at all before that's why there was a ton of URL parsing which rubbed me the wrong way.

I will change it that way, but as I said to do this properly and to get rid of the "-/-/-" thingy which rubbed you the wrong way 😝 I need to redo the URLerror to CURLerror translation function because some errors are not translated properly and just result in CURLE_OK.

Comment thread src/tool_operate.c Outdated
Comment on lines +310 to +311
len_origin = strlen(host) + strlen(port) + strlen(scheme) + 3;
origin = curlx_malloc(len_origin);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid strlen, malloc and snprintf by:

Suggested change
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) {

@htasta2 htasta2 Jul 1, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You told me to use msnprintf, that's why I used it.

what do you think about using curl_msnprintf() instead to avoid three memcpy()s ?

What about this in docs/examples/.checksrc:

# Use of curl printf functions is discouraged
banfunc curl_maprintf

@vszakats vszakats Jul 2, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this in docs/examples/.checksrc:

This applies to example code. It's built differently and reflects
different practices than core/test code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You told me to use msnprintf, that's why I used it.

Then the conditions were different or I didn't understand what you were asking. We prefer avoiding "raw" allocs + fiddling and rather use functions that handle it themselves.

@htasta2

htasta2 commented Jul 1, 2026 •

Copy link
Copy Markdown
Author

As for the test. We discussed this in the IRC and it's non trivial to test this.

This is why I wrote a small test script for myself on my server and local machine to check if the behaviour is how I expect it to be.

@bagder

bagder commented Jul 2, 2026

Copy link
Copy Markdown
Member

As for the test. We discussed this in the IRC and it's non trivial to test this.

We still need tests. I think its worth spending some extra effort on that. A test that first returns a 429 and then a 200 shouldn't be too hard to add and would at least confirm that the basics do what they're supposed to do.

@htasta2

htasta2 commented Jul 2, 2026

Copy link
Copy Markdown
Author

As for the test. We discussed this in the IRC and it's non trivial to test this.

We still need tests. I think its worth spending some extra effort on that. A test that first returns a 429 and then a 200 shouldn't be too hard to add and would at least confirm that the basics do what they're supposed to do.

That doesn't properly test it. The test needs to check if others are still going through while the 429 one is blocked for a certain amount of time otherwise the code isn't really tested. Because of the async and parallel nature of it, the output of the test isn't trivial to check.

I'll be gone for the weekend, but after that I can show how I "manually" tested it so far. Maybe you can then add a testcase to your server that will properly test this, I wasn't able to get anything done with the existing functions, I tried several months ago.

@bagder

bagder commented Jul 2, 2026

Copy link
Copy Markdown
Member

That doesn't properly test it.

It would be a first miniature attempt to at least exercise some of the code. I won't stop you from doing better ones.

@bagder bagder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still concerned about the lack of tests for this.

@icing any thoughts on how we can make a test case for this?

Comment thread src/tool_operate.c

/* 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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If curl is not doing parallel downloads, this PR does nothing for the 429 responses so it seems unnecessary to add them to a list that is never used. Doesn't it?

Comment thread src/tool_operate.c
if(item && (time(NULL) < item->startat)) {
per->startat = item->startat;
per->added = FALSE;
sleeping = TRUE;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also log something to the output to help explain to users what's going on ?

Comment thread tests/data/test142
</protocol>
<limits>
Allocations: 180
Allocations: 190

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of these Allocations changes should be needed anymore, right?

@htasta2

htasta2 commented Jul 31, 2026

Copy link
Copy Markdown
Author

They way it currently is, is broken.

It is how I initially said. Either you use memory or you do the calculation every time.

Now it's only using memory when the error happens but all the transfers who are about to fire do not have the information and therefore fire away. So every transfer still needs the URL to be parsed and then checked against the list for it to work this way.

As I said, I don't want to run from Pontius to Pilatus and back all the time, that's why I didn't change anything further.
We need to talk this through on the IRC so the mechanism is really understood and I get the go for the actual solution.

@bagder

bagder commented Oct 23, 2026

Copy link
Copy Markdown
Member

There's been almost three months and this now merge-conflicts. Are you still working on this @htasta2 ? Please do not think that conversations on IRC is better than here. They are ephemeral and hard for people to join in, while discussions here stick to this particular PR.

@bagder

bagder commented Oct 29, 2026

Copy link
Copy Markdown
Member

Abandoned

@bagder bagder closed this Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

5 participants