Skip to content
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

Spike: Add optional retry middleware #51

Closed
wants to merge 12 commits into from
Closed

Conversation

alpe
Copy link
Contributor

@alpe alpe commented Jan 9, 2024

Resolves #48

the implementation is based on 2 elements:

  • server middleware to retry a failed request with http status code 502, 503, 504

not considered is a priority queue for retries

@alpe alpe changed the title 48 retry Spike: retry #48 Jan 9, 2024
pkg/proxy/handler.go Outdated Show resolved Hide resolved
Copy link
Contributor

@samos123 samos123 left a comment

Choose a reason for hiding this comment

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

Nice! This looks good to me. @nstogner is the expert in this area so will wait for his approval.

pkg/proxy/handler.go Outdated Show resolved Hide resolved
pkg/proxy/middleware.go Outdated Show resolved Hide resolved
pkg/proxy/handler.go Outdated Show resolved Hide resolved
pkg/endpoints/endpoints.go Outdated Show resolved Hide resolved
pkg/proxy/middleware.go Outdated Show resolved Hide resolved
pkg/proxy/middleware.go Outdated Show resolved Hide resolved
for i := 0; ; i++ {
capturedResp = &responseBuffer{
header: make(http.Header),
body: bytes.NewBuffer([]byte{}),
Copy link
Contributor

Choose a reason for hiding this comment

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

As we spoke about, I think it would be good to know whether we are adding to in-memory buffering or not by doing this. I think it is still worth having this feature even if we are making memory requirements worse. This makes me think that we might actually want to make retrying configurable:

  • enabled/disabled
  • list of status codes to retry? maybe this configurable item would be excessive?

cmd/lingo/main.go Outdated Show resolved Hide resolved
pkg/proxy/middleware.go Outdated Show resolved Hide resolved
@alpe alpe changed the title Spike: retry #48 Add optional retry middleware Jan 17, 2024
@alpe alpe marked this pull request as ready for review January 17, 2024 11:30
Copy link
Contributor

@nstogner nstogner left a comment

Choose a reason for hiding this comment

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

I didnt get enough time to fully go through this PR and understand it. I added a few questions will revisit.

pkg/endpoints/endpoints.go Outdated Show resolved Hide resolved
if err != nil {
return "", r, nil
var body []byte
if mb, ok := r.Body.(*lazyBodyCapturer); ok && mb.capturedBody != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this change doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With this change, I am re-using the buffer of the retry middleware so that the body must not be re-read (and buffered) again. It is an optimization

pkg/proxy/middleware.go Outdated Show resolved Hide resolved
return d
}

func (r *responseWriterDelegator) discardedResponse() bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this method could go away in favor of just checking r.discardErrResp?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need this to satisfy the response type in newResponseWriterDelegator() xResponseWriter. The source writer either implements ReaderFrom or not. So does the returned implementation.

headerBuf: make(http.Header),
discardErrResp: discardErrResp, // abort early on timeout, context cancel
}
if _, ok := writer.(io.ReaderFrom); ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

In Lingo when would the writer be an io.ReaderFrom and when would it not be?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The response type in the Go stdlib implements the interface. So should do all decorators to make use of internal or future optimizations. it is used in io.Copy for example. I saw a presentation once but I don't have the link anymore. I also double checked with the prometheus decorator to ensure it is still a good practice

Same for WriteTo on the lazyBodyCapturerWriteTo

// If the maximum number of retries is 0, it returns the next handler without adding any retries.
// If the list of retryable status codes is empty, it uses a default set of status codes (http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout).
// The function creates a RetryMiddleware struct with the given parameters and returns it as an http.Handler.
func NewRetryMiddleware(maxRetries int, other http.Handler, optRetryStatusCodes ...int) http.Handler {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to limit the max bytes read. See https://cwe.mitre.org/data/definitions/400.html example 6

Copy link
Contributor

Choose a reason for hiding this comment

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

I can see this being useful.

@alpe alpe changed the title Add optional retry middleware Spike: Add optional retry middleware Feb 5, 2024
@alpe
Copy link
Contributor Author

alpe commented Feb 7, 2024

Closing this now in favour of #61

@alpe alpe closed this Feb 7, 2024
nstogner added a commit that referenced this pull request Feb 15, 2024
Allow for retrying failed requests to backends. Default to 1 retry per
lingo-request.

Fixes #48 

Builds on work done by @alpe in #64 and #51.

Co-authored-by: Alex Peters <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lingo should retry on proxy failure
3 participants