My goal was to provide a small helper to retry fallible functions such as network requests and database calls. It supports jittering (adding randomness to wait time between retires) and a few simple retry strategies such as fixed, linear, and exponential.
I just started coding in Zig so I would appreciate any feedback such as
Is try retry.zretry(doWork, .{}, options) idiomatic Zig?
if passing function arguments as a tuple .{arg1, arg2} is awkward
should the caller need to provide the randomness or should the library create it if you need jittering
if you needed this sort of retry logic, would you import a module or implement it yourself?
if you find it useful, a star would be much appreciated.
I think this is great, good job. Personally, I’d implement it myself, mostly for the sake of minimal dependencies, but I can see this being used so I wouldn’t take that as a definitive answer.
A few things popped up in my head while I was reading the code:
You could return specific errors for the case where max attempts have been exhausted, or expose that in a dignostics object;
The former would be relevant if you’d add a distinction between errors that are retry-able those that should not be retried (as they’d almost always would mean failing again, i.e. HTTP 403);
I think the strategy for jittering is a bit off; You usually don’t want to randomize between 0 and delay_ms as that can have the effect of exhausting the attempts too quickly and not giving the proper time for state healing. What you’d typically want to do is to have a low threshold like 2~5ms, or perhaps 1~5% of the delay, for the sake of adding a minimal variance into requests that could be retried together, avoiding a thundering herd effect;
The pattern try wrapper(fn, args) is fairly common (see std.Io.async, for example);
I’d personally break options from the retry function, into a reusable Retrier struct, mostly for the ergonomics of not passing options in the same function call as the thing I want to retry on - perhaps RetryOptions could itself function as a reusable Retrier - not a great deal of problem though, that falls in the “taste” category
Overall really nice, good to see work in this direction
Thank you for your feedback
I’ll make sure to refactor the jittering strategy to a lower threshold as you mentioned and add the distinction between the errors that should and shouldn’t be retried.
I agree that this would be simple enough that I’d avoid having a dependency for it.
I’d add one comment to the project.
Not all errors are equal, there are some errors that can be retry-able while other errors should allow to stop immediately (not sure what the shape of the API would be however)
There’s not much of an API for checking membership of an error within an error set, but I managed to implement it locally here by adding a *const fn (anyerror) bool to the RetryOptions struct with a custom logic, so one could configure which errors are retry-able with a switch. Alternatively, this works but looks clunky:
fn is_retriable(err: anyerror) bool {
const name = @errorName(err);
const targets = @typeInfo(RetriableErrors).error_set.?;
for (targets) |target| if (std.mem.eql(u8, name, target.name)) return true;
return false;
}
So one could have retriable_errors as an error set in the arguments instead of a function pointer. As long as one doesn’t need to @errorCast, which can be Illegal Behavior if the error sets don’t match, I think it should be fine.
So from what I’m hearing is it that Zig programmers are less likely to add a dependency than a higher level language such as Javascript?
On npm similar scripts are getting millions of downloads
Correct. To some extent it’s a matter of taste, and this forum in particular is full of programmers who enjoy solving problems themselves, owning their stack, and generally practicing a philosophy of self-reliance. I doubt you will find too many hearty endorsements of the current npm ecosystem here.
All that said, I imagine you’re doing this project for both the pure fun of programming and to build your zig / library-development skills, so by all means, make a cool little library! Just don’t use the number of GitHub stars as any sort of value judgement on the work you’re doing.
I definitely understand the appeal of the philosophy of owning your own stack and having more explicit control over your code. That’s why I’ve been experimenting with zig for fun.