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

Request should not fail on small ReadBufferSize #1873

Open
hbhoyar-uber opened this issue Sep 24, 2024 · 4 comments
Open

Request should not fail on small ReadBufferSize #1873

hbhoyar-uber opened this issue Sep 24, 2024 · 4 comments

Comments

@hbhoyar-uber
Copy link

Can we not fail request if the req/resp header size is greater than the read buffer size.

I can see a bufio bug was mentioned for tryRead function but looks like it's resolved now. Can we have the above mentioned behaviour?

Also, some callback could also be configured to inform that buffer size was low. Library users could emit metrics and increase the buffer size appropriately to have best balance between memory usage and CPU usage.

@valyala Could you review the request?

@hbhoyar-uber hbhoyar-uber changed the title Header Read should not fail on small buffer size Request should not fail on small ReadBufferSize Sep 24, 2024
@erikdubbelboer
Copy link
Collaborator

I'm afraid that is not possible with how fasthttp is build. It always parses the whole headers on each try and therefor needs the whole headers in the read buffer. Changing this would require quite a big refactor of the header parsing code.

@hbhoyar-uber
Copy link
Author

Thanks @erikdubbelboer for reviewing. I tried to refactor the code with minimal changes to support this and looks like it's working. The code might look ugly but can be refactored better. Can you review it once and if it looks okay, I can raise a PR.

This feature could be enabled by a optional flag and callback could be provided for observability if this case occurs so that the user can tune the buffer size.

        var buf []byte
	for {
		var fullBytes []byte
		h.resetSkipNormalize()
		b, err := r.Peek(n)
		if len(b) == 0 {
			return handlePeekError(r, n, err)
		}

		b = mustPeekBuffered(r)
		if len(buf) != 0 {
                         // create full buffer with existing bytes
			fullBytes = append(buf, b...)
		} else {
                         // first iteration, use only read bytes
			fullBytes = b
		}

		headersLen, errParse := h.parse(fullBytes)
		if errParse == nil {
                         // discard only header bytes read in current iteration
			mustDiscard(r, headersLen-len(buf))
			return nil
		}

		headerErr := headerError("request", err, errParse, fullBytes, h.secureErrorLogMessage)
		smallBufErr := &ErrSmallBuffer{}
		if !errors.As(headerErr, &smallBufErr) {
			return headerErr
		}

		if len(buf) == 0 {
                         // append to existing buffer to release reference of b from bufio.Reader
			buf = append(buf, b...)
		} else {
			buf = fullBytes
		}

                 // discard all currently read bytes
		mustDiscard(r, len(b))
	}

@erikdubbelboer
Copy link
Collaborator

I'm pretty sure this is not going to work. You're storing the return value of bufio.Reader.Peek() in buf. But the documentation for bufio.Reader.Peek() says: The bytes stop being valid at the next read call.

@hbhoyar-uber
Copy link
Author

I tested this locally and it was working properly.

The return value of Peek() is not directly stored. It is being copied in buf in each iteration.
First iteration has special handling to defer copying later if header parsing fails. All other iterations, copy at the start.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants