-
Notifications
You must be signed in to change notification settings - Fork 224
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
add support for HTTP retries #436
Conversation
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.
Yeah I think this is the right track. Nice work!! Can you add some tests maybe in the v2/testing area ? This is pretty hard to test isolated unless you know some tricks.
if e.Retries == 0 { | ||
return e.Result.Error() | ||
} | ||
return fmt.Sprintf("%s (%dx)", e.Result.Error(), e.Retries) |
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.
This might do funky things with the ack wrapping so a test for this is needed
if e.Retries == 0 { | ||
return e.Result.Error() | ||
} | ||
return fmt.Sprintf("%s (%dx)", e.Result.Error(), e.Retries) |
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.
This might do funky things with the ack wrapping so a test for this is needed
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 like it! Just two points:
- Since
WithRetry
is an HTTP specific feature, should we move it into protocol/http? @n3wscott wdyt? - I think
Backoff
should not live intypes
, since it's the module that contains the cloudevents spec types, not the data structure about the logic of the client. Probably a better place forBackoff
is in the same module ofWithRetry
or inclient
module
protocol.Result | ||
|
||
// Retries is the number of times the request was tried | ||
Retries int |
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 we want to return back the total time spent trying too if we do exp backoff, what do you think?
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.
to make sure we are on the same page, this is the time spent trying excluding the succesful (if any) request, right. I'm pretty sure the answer is yes...
v2/types/backoff.go
Outdated
import "time" | ||
|
||
// Backoff holds parameters applied to retries | ||
type Backoff struct { |
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.
Rather than make a custom type, I would hide that detail from the user with a change to the WithBackoff method, I will comment there.
v2/context/context.go
Outdated
} | ||
|
||
// RetryFrom looks in the given context and returns the backoff parameters if found, otherwise nil | ||
func RetryFrom(ctx context.Context) *types.Backoff { |
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 know slinky recommended moving this to http but there are settings in other protocols that have similar settings, I feel like it is ok to keep it in the general spot here.
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.
Ok i'm fine with that
v2/context/context.go
Outdated
var retryKey = retryKeyType{} | ||
|
||
// WithRetry returns back a new context with the retry backoff parameters. | ||
func WithRetry(ctx context.Context, backoff types.Backoff) context.Context { |
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.
We might want more than one strategy for backoff, this is linear and we might also want exponential backoff.
Also, rather than a custom type (or an internal custom type is ok) I would not expose that to the user. I recommend being explicit about the backoff and retries:
func WithLinearBackoff(ctx context.Context, period time.Duration, maxTries uint) context.Context {
is one way. I think internally we should make a type, it can be defined here in this file:
type BackoffConfig struct {
Strategy string
Period time.Duration
MaxRetries int
}
And WithLinearBackoff makes a BackoffConfig{Strategy:"linear", ...
.
I suggest this to let the api self documenting and not require the caller to understand another object to make.
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.
sound good. Fixing....
v2/protocol/http/protocol.go
Outdated
retries++ | ||
|
||
// Linear backoff. | ||
time.Sleep(backoff.Delay) |
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 would make this method more of a wrapper for doOnce with a some kind of pattern of:
for {
select {
case <-ctx.Done():
// canceled.
case <-timer.Tick():
strongTypedErr: doOnce(ctx, request)
... custom logic to see if we should stop retry
}
}
That way the logic of producing the protocol.NewReceipt
is only one place, and you are still free to collect them in the outer retry method.
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 tried but the reason I didn't call doOnce
is because of if retry && !err.(*url.Error).Timeout() {
. When doOnce
returns an error, there is no way to unwrap the original error.
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.
which can be easily solved by changing doOnce
signature.
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.
ended up trying go 1.13 unwrap API. Not tested yet.
v2/protocol/http/protocol.go
Outdated
@@ -133,6 +145,64 @@ func (p *Protocol) Request(ctx context.Context, m binding.Message) (binding.Mess | |||
return NewMessage(resp.Header, resp.Body), NewResult(resp.StatusCode, "%w", result) | |||
} | |||
|
|||
func (p *Protocol) doWithRetry(backoff *types.Backoff) func(*http.Request) (binding.Message, error) { |
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 needs context.Context passed in to allow for the app to cancel in the case of shutdown or term signals.
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.
good point! thanks!
this is ready for a second round of review. I added some tests under |
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 move Backoff
outside types
? I think the proper place is content
module
@slinkydeveloper there is no |
yep, |
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, waiting for scott review
v2/context/context.go
Outdated
// Opaque key type used to store linear backoff parameters | ||
type linearBackoffKeyType struct{} | ||
|
||
var linearBackoffKey = linearBackoffKeyType{} |
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.
this should just be a backoffKey
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.
using retriesKey
.
v2/protocol/http/protocol.go
Outdated
@@ -118,6 +118,16 @@ func (p *Protocol) Request(ctx context.Context, m binding.Message) (binding.Mess | |||
if err = WriteRequest(ctx, m, req, p.transformers); err != nil { | |||
return nil, err | |||
} | |||
|
|||
do := p.doOnce | |||
if delay, tries := cecontext.LinearBackoffFrom(ctx); tries > 0 { |
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 would change this method to BackoffFrom
and return the Backoff
struct, then have a switch for BackoffStrategyLinear
or BackoffStrategyNone
Signed-off-by: Lionel Villard <villard@us.ibm.com>
Signed-off-by: Lionel Villard <villard@us.ibm.com>
Signed-off-by: Lionel Villard <villard@us.ibm.com>
Signed-off-by: Lionel Villard <villard@us.ibm.com>
Signed-off-by: Lionel Villard <villard@us.ibm.com>
Signed-off-by: Lionel Villard <villard@us.ibm.com>
Signed-off-by: Lionel Villard <villard@us.ibm.com>
Signed-off-by: Lionel Villard <villard@us.ibm.com>
LGTM!! Thanks, this is gonna be awesome!! |
Add support for retrying HTTP requests when a certain class of error occurs.
There is no tests yet. Opening the PR to get early feedback
@n3wscott