-
-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feature] custom retry callback #332
Conversation
1a0782e
to
25ad054
Compare
25ad054
to
12969ad
Compare
index.js
Outdated
@@ -29,7 +29,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) { | |||
]) | |||
|
|||
const retryMethods = new Set(opts.retryMethods || [ | |||
'GET', 'HEAD', 'OPTIONS', 'TRACE' | |||
'GET', 'HEAD', 'OPTIONS', 'TRACE', 'POST', 'PATCH' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes this a breaking change when it doesn't need to be. I think it'd be simpler if we just passed this in gadget side?
index.js
Outdated
if (res && res.headers['retry-after']) { | ||
retryAfter = res.headers['retry-after'] | ||
const defaultRetryAfter = () => { | ||
let retryAfter = 42 * Math.random() * (retries + 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about including the 503 retry behaviour in this function as well? To me it doesn't really seem extra special such that a custom handler may want to be a client of that logic as well.
index.js
Outdated
if (!reply.sent) { | ||
// always retry on 503 errors | ||
|
||
const defaultRetry = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why two separate functions for the duration and if we should retry or not? I think it'd be simpler with one function that returns null or a number, null meaning don't retry, and a number meaning retry after that many milliseconds.
README.md
Outdated
const customRetryLogic = (req, res, registerDefaultRetry, defaultRetryAfter) => { | ||
//If this block is not included all non 500 errors will not be retried | ||
if (registerDefaultRetry()){ | ||
return defaultRetryAfter; | ||
} | ||
|
||
//Custom retry logic | ||
if (res && res.statusCode === 500 && req.method === 'GET') { | ||
return 300 | ||
} | ||
return null | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be clearer not to describe this as "registering" another retry handler (both in this PR and in the test code), we're not really adding a handler to an event emitter or something, but just passing an option.
Also, I think that a custom retry handler needs to be passed the number of attempts so far if it wants to calculate an exponential delay. What do you think about this API instaead
const customRetryLogic = (req, res, registerDefaultRetry, defaultRetryAfter) => { | |
//If this block is not included all non 500 errors will not be retried | |
if (registerDefaultRetry()){ | |
return defaultRetryAfter; | |
} | |
//Custom retry logic | |
if (res && res.statusCode === 500 && req.method === 'GET') { | |
return 300 | |
} | |
return null | |
} | |
const customRetryLogic = (req, res, attemptCount, getDefaultDelay) => { | |
const defaultDelay = getDefaultDelay(); | |
if (defaultDelay) return defaultDelay(); | |
if (res && res.statusCode === 500 && req.method === 'GET') { | |
return 300 | |
} | |
return null | |
} |
index.js
Outdated
@@ -251,29 +252,51 @@ function isFastifyMultipartRegistered (fastify) { | |||
return (fastify.hasContentTypeParser('multipart') || fastify.hasContentTypeParser('multipart/form-data')) && fastify.hasRequestDecorator('multipart') | |||
} | |||
|
|||
function createRequestRetry (requestImpl, reply, retriesCount, retryOnError, maxRetriesOn503) { | |||
function createRequestRetry (requestImpl, reply, retriesCount, retryOnError, maxRetriesOn503, customRetry) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method signature to this function seems a little complicated. I think it might be simpler if the caller just built one "give me the retry delay" function, and passed it into this thing. That function can do whatever it needs to based on the options, and kind of bottles up all the logic, so this thing doesn't really need to care about the "when" of retrying, and is just about actually doing it based on the output of that function.
} | ||
}) | ||
|
||
test("a server 503's with a custom handler for 500 and the custom handler registers the default so it passes", async (t) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are down with the language change
test("a server 503's with a custom handler for 500 and the custom handler registers the default so it passes", async (t) => { | |
test("custom retry delay functions can invoke the default delay", async (t) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
8e8dc98
to
12969ad
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM after comments!
@@ -14,7 +14,7 @@ npm i @fastify/reply-from | |||
``` | |||
|
|||
## Compatibility with @fastify/multipart | |||
`@fastify/reply-from` and [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) should not be registered as sibling plugins nor should they be registered in plugins which have a parent-child relationship.<br> The two plugins are incompatible, in the sense that the behavior of `@fastify/reply-from` might not be the expected one when the above-mentioned conditions are not respected.<br> This is due to the fact that `@fastify/multipart` consumes the multipart content by parsing it, hence this content is not forwarded to the target service by `@fastify/reply-from`.<br> | |||
`@fastify/reply-from` and [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) should not be registered as sibling plugins nor should they be registered in plugins which have a parent-child relationship.`<br>` The two plugins are incompatible, in the sense that the behavior of `@fastify/reply-from` might not be the expected one when the above-mentioned conditions are not respected.`<br>` This is due to the fact that `@fastify/multipart` consumes the multipart content by parsing it, hence this content is not forwarded to the target service by `@fastify/reply-from`.`<br>` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you avoid making these unrelated changes in this PR 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I ran lint incorrectly my bad !
Will fix.
This PR implements two fundamental changes.
Edit:
1. I have addedsee review by airhornsPATCH
andPOST
as given defaultretryMethods
2. I have added the option to pass a callback to handle retries in a custom fashion as defined by the client.
Point 2. Described
The motivation for this change is that out of the box
reply-from
does not retry 500's so gracefully handling such cases is not possible. As a result with this change you are able to write your own callback to retry 500's as you wish (or any other kind of error - using 500's here as an exemplar).This can be done quite simply.
Note this new API is an object which has two keys.
handler
andretries
where handler is a required key and retries is optional but strongly encouraged.There is an important intricacy to this API.
If no
customRetry
object is passed then business as usual in the library. Retries work as they are expected for default cases.But if a
customRetry
is passed then it is up to responsibility of the callback to be aware that if they expect default behaviour to co-exist they must register it. If they don't (the above example DID NOT) all default cases of errors (such as 503s) won't be retried.Example to get default behaviour in a
customRetry
CC: @airhorns
Checklist
npm run test
andnpm run benchmark
and the Code of conduct