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

fix ctx.SendStream(io.Reader) huge memory usage #2091

Merged
merged 2 commits into from Sep 15, 2022
Merged

Conversation

trim21
Copy link
Contributor

@trim21 trim21 commented Sep 15, 2022

Description

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
Explain the details for making this change. What existing problem does the pull request solve?

ctx.SendStream(r) with huge size reader will cause huge memory usage and may cause out of memory instead of streaming response.

package main

import (
	"fmt"
	"io"
	"net"

	"github.com/gofiber/fiber/v2"
)

type zeroReader struct {
	max     int
	current int
}

func (r *zeroReader) Read(p []byte) (int, error) {
	fmt.Println("read", len(p), float64(r.current)/float64(r.max), r.current, r.max)
	if r.current >= r.max {
		return 0, io.EOF
	}

	toRead := r.max - r.current
	if toRead > len(p) {
		toRead = len(p)
	}

	for i := 0; i < toRead; i++ {
		p[i] = 0
	}

	r.current += toRead

	return toRead, nil
}

func main() {
	ln, err := net.Listen("tcp", "127.0.0.1:9999")
	if err != nil {
		panic(err)
	}

	app := fiber.New()
	app.Get("/", func(ctx *fiber.Ctx) error {
		r := &zeroReader{max: 1024 * 1024 * 100}

		return ctx.SendStream(r)
	})

	app.Listener(ln)
}

with curl.exe --limit-rate 10k http://127.0.0.1:9999 --output downloads/bin, you will find fiber read all 1024*1024*100 bytes very fast and use 100mb memory.

this is caused by ctx.SendStream() try to read whole io.Reader and get a content length.

code path:

main.(*zeroReader).Read(0xc00031a000?, {0x0?, 0xc0000841a0?, 0xdee1ba?})
        ./app.go:16 +0x27
github.com/valyala/bytebufferpool.(*ByteBuffer).ReadFrom(0xc000318000, {0x1194680, 0xc000306090})
        github.com/valyala/bytebufferpool@v1.0.0/bytebuffer.go:45 +0x13e
io.copyBuffer({0x1194480, 0xc000318000}, {0x1194680, 0xc000306090}, {0xc00030b000, 0x1000, 0x1000})
        io/io.go:413 +0x14b
io.CopyBuffer({0x1194480?, 0xc000318000?}, {0x1194680?, 0xc000306090?}, {0xc00030b000?, 0xc000063a50?, 0xfbc2bc?})
        io/io.go:400 +0x3c
github.com/valyala/fasthttp.copyZeroAlloc({0x1194480, 0xc000318000}, {0x1194680, 0xc000306090})
        github.com/valyala/fasthttp@v1.40.0/http.go:2047 +0x7e
github.com/valyala/fasthttp.(*Response).Body(0xc000304318)
        github.com/valyala/fasthttp@v1.40.0/http.go:346 +0xd4
github.com/gofiber/fiber/v2.(*Ctx).SendStream(0xc000310000, {0x1194680?, 0xc000306090?}, {0x0?, 0x8?, 0x0?})
        github.com/gofiber/fiber/v2@v2.37.1/ctx.go:1548 +0x87
main.main.func1(0xc000310000)
        ./app.go:47 +0x88
github.com/gofiber/fiber/v2.(*App).next(0xc000005400, 0xc000310000)
        github.com/gofiber/fiber/v2@v2.37.1/router.go:132 +0x1c2
github.com/gofiber/fiber/v2.(*App).handler(0xc000005400, 0xe433b7?)
        github.com/gofiber/fiber/v2@v2.37.1/router.go:159 +0x45
github.com/valyala/fasthttp.(*Server).serveConn(0xc00012a480, {0x11979d8?, 0xc000088008})
        github.com/valyala/fasthttp@v1.40.0/server.go:2311 +0x1268
github.com/valyala/fasthttp.(*workerPool).workerFunc(0xc000000c80, 0xc00008c060)
        github.com/valyala/fasthttp@v1.40.0/workerpool.go:224 +0xa9
github.com/valyala/fasthttp.(*workerPool).getCh.func1()
        github.com/valyala/fasthttp@v1.40.0/workerpool.go:196 +0x38
created by github.com/valyala/fasthttp.(*workerPool).getCh
        github.com/valyala/fasthttp@v1.40.0/workerpool.go:195 +0x1b0
exit status 2

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • For new functionalities I follow the inspiration of the express js framework and built them similar in usage
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation - https://github.com/gofiber/docs for https://docs.gofiber.io/
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • If new dependencies exist, I have checked that they are really necessary and agreed with the maintainers/community (we want to have as few dependencies as possible)
  • I tried to make my code as fast as possible with as few allocations as possible
  • For new code I have written benchmarks so that they can be analyzed and improved

Commit formatting:

Use emojis on commit messages so it provides an easy way of identifying the purpose or intention of a commit. Check out the emoji cheatsheet here: https://gitmoji.carloscuesta.me/

@trim21
Copy link
Contributor Author

trim21 commented Sep 15, 2022

some middleware like etag and logger may still call Response.Body() and read all bytes from reader and causing same problem.

@ReneWerner87 ReneWerner87 merged commit 709c523 into master Sep 15, 2022
@efectn efectn deleted the trim21-patch-1 branch September 15, 2022 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants