-
Notifications
You must be signed in to change notification settings - Fork 39
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
client: ensure requests are restarted in a race-free way. #71
client: ensure requests are restarted in a race-free way. #71
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.
LGTM modulo a couple of thoughts and suggestions, thanks.
if err != nil { | ||
return | ||
if requestData.ReqReader != nil && requestData.GetReqReader == nil { | ||
requestData.ReqReader, requestData.GetReqReader = gooseio.MakeGetReqReader(requestData.ReqReader, int64(requestData.ReqLength)) |
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 it did this before too, but side-effecting the contents of requestData is really quite icky. A caller might be reasonably entitled to assume that the ReqReader they put into the RequestData is the same after the call as before.
if err != nil { | ||
return nil, errors.Newf(err, "failed creating the request %s", URL) | ||
} | ||
req.GetBody = getReqReader |
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.
For some readers, GetBody is populated automatically; could use that if it does it. shrug
internal/gooseio/seekable.go
Outdated
return br, br.getReqReader | ||
} | ||
|
||
type bufferingReader 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.
It would be nice to have a high level description of what this type does.
internal/gooseio/seekable.go
Outdated
rp, wp int | ||
} | ||
|
||
func (r *bufferingReader) Read(p []byte) (n int, err 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.
Much as I appreciate a nice bit of slice twiddling, I think it might be possible to avoid it in this case. How about something like this?
type bufferingReader struct {
mu sync.Mutex
buf bytes.Buffer
r io.Reader
}
func (r *bufferingReader) getReqReader() (io.ReadCloser, error) {
r.mu.Lock()
return &unlockingReadCloser{
mu: &r.mu,
Reader: io.MultiReader(
bytes.NewReader(r.buf.Bytes()),
io.TeeReader(r.r, &r.buf),
),
}, nil
}
type seekingReader struct {
mu sync.Mutex
r io.ReadSeeker
}
func (r *seekingReader) getReqReader() (io.ReadCloser, error) {
r.mu.Lock()
if _, err := r.r.Seek(0, 0); err != nil {
r.mu.Unlock()
return nil, err
}
return &unlockingReadCloser{
mu: &r.mu,
Reader: r.r,
}, nil
}
type unlockingReadCloser struct {
mu *sync.Mutex
io.Reader
}
func (c *unlockingReadCloser) Close() error {
if c.mu != nil {
c.mu.Unlock()
c.mu = nil
}
return nil
}
82e5502
to
1fb000a
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.
Looks great, but maybe a final tweak might be good?
internal/gooseio/seekable.go
Outdated
} | ||
if length > maxBufSize { | ||
return nil, fmt.Errorf("body of length %d is too large to hold in memory (max %d bytes)", length, maxBufSize) | ||
if size > maxBufSize { |
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 means that we flatly refuse to process any requests that are larger than the requested size. I thought we could do better than that, because it's quite possible that we will receive the error before all of maxBufSize bytes have been read.
The way I was previously envisaging can succeed in that case, I 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.
This is still better than the previous behavior which wouldn't even try the first time if it thought it was too long.
internal/gooseio/seekable.go
Outdated
mu.Lock() | ||
return &unlockingReadCloser{ | ||
mu: &mu, | ||
Reader: io.MultiReader(bytes.NewReader(buf.Bytes()), io.TeeReader(io.LimitReader(r, size-int64(buf.Len())), &buf)), |
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 is quite long. Maybe put the various readers on different lines?
A comment would probably also be appropriate.
1fb000a
to
1f1e1bd
Compare
HTTP clients can return whilst they are still reading a request body. Ensure that any request with a body is not restarted until the previous request has closed it.
1f1e1bd
to
03fdd0e
Compare
HTTP clients can return whilst they are still reading a request
body. Ensure that any request with a body is not restarted until the
previous request has closed it.