123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- // Copyright 2014 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package http2
- import (
- "errors"
- "fmt"
- "sync"
- )
- // Buffer chunks are allocated from a pool to reduce pressure on GC.
- // The maximum wasted space per dataBuffer is 2x the largest size class,
- // which happens when the dataBuffer has multiple chunks and there is
- // one unread byte in both the first and last chunks. We use a few size
- // classes to minimize overheads for servers that typically receive very
- // small request bodies.
- //
- // TODO: Benchmark to determine if the pools are necessary. The GC may have
- // improved enough that we can instead allocate chunks like this:
- // make([]byte, max(16<<10, expectedBytesRemaining))
- var (
- dataChunkSizeClasses = []int{
- 1 << 10,
- 2 << 10,
- 4 << 10,
- 8 << 10,
- 16 << 10,
- }
- dataChunkPools = [...]sync.Pool{
- {New: func() interface{} { return make([]byte, 1<<10) }},
- {New: func() interface{} { return make([]byte, 2<<10) }},
- {New: func() interface{} { return make([]byte, 4<<10) }},
- {New: func() interface{} { return make([]byte, 8<<10) }},
- {New: func() interface{} { return make([]byte, 16<<10) }},
- }
- )
- func getDataBufferChunk(size int64) []byte {
- i := 0
- for ; i < len(dataChunkSizeClasses)-1; i++ {
- if size <= int64(dataChunkSizeClasses[i]) {
- break
- }
- }
- return dataChunkPools[i].Get().([]byte)
- }
- func putDataBufferChunk(p []byte) {
- for i, n := range dataChunkSizeClasses {
- if len(p) == n {
- dataChunkPools[i].Put(p)
- return
- }
- }
- panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
- }
- // dataBuffer is an io.ReadWriter backed by a list of data chunks.
- // Each dataBuffer is used to read DATA frames on a single stream.
- // The buffer is divided into chunks so the server can limit the
- // total memory used by a single connection without limiting the
- // request body size on any single stream.
- type dataBuffer struct {
- chunks [][]byte
- r int // next byte to read is chunks[0][r]
- w int // next byte to write is chunks[len(chunks)-1][w]
- size int // total buffered bytes
- expected int64 // we expect at least this many bytes in future Write calls (ignored if <= 0)
- }
- var errReadEmpty = errors.New("read from empty dataBuffer")
- // Read copies bytes from the buffer into p.
- // It is an error to read when no data is available.
- func (b *dataBuffer) Read(p []byte) (int, error) {
- if b.size == 0 {
- return 0, errReadEmpty
- }
- var ntotal int
- for len(p) > 0 && b.size > 0 {
- readFrom := b.bytesFromFirstChunk()
- n := copy(p, readFrom)
- p = p[n:]
- ntotal += n
- b.r += n
- b.size -= n
- // If the first chunk has been consumed, advance to the next chunk.
- if b.r == len(b.chunks[0]) {
- putDataBufferChunk(b.chunks[0])
- end := len(b.chunks) - 1
- copy(b.chunks[:end], b.chunks[1:])
- b.chunks[end] = nil
- b.chunks = b.chunks[:end]
- b.r = 0
- }
- }
- return ntotal, nil
- }
- func (b *dataBuffer) bytesFromFirstChunk() []byte {
- if len(b.chunks) == 1 {
- return b.chunks[0][b.r:b.w]
- }
- return b.chunks[0][b.r:]
- }
- // Len returns the number of bytes of the unread portion of the buffer.
- func (b *dataBuffer) Len() int {
- return b.size
- }
- // Write appends p to the buffer.
- func (b *dataBuffer) Write(p []byte) (int, error) {
- ntotal := len(p)
- for len(p) > 0 {
- // If the last chunk is empty, allocate a new chunk. Try to allocate
- // enough to fully copy p plus any additional bytes we expect to
- // receive. However, this may allocate less than len(p).
- want := int64(len(p))
- if b.expected > want {
- want = b.expected
- }
- chunk := b.lastChunkOrAlloc(want)
- n := copy(chunk[b.w:], p)
- p = p[n:]
- b.w += n
- b.size += n
- b.expected -= int64(n)
- }
- return ntotal, nil
- }
- func (b *dataBuffer) lastChunkOrAlloc(want int64) []byte {
- if len(b.chunks) != 0 {
- last := b.chunks[len(b.chunks)-1]
- if b.w < len(last) {
- return last
- }
- }
- chunk := getDataBufferChunk(want)
- b.chunks = append(b.chunks, chunk)
- b.w = 0
- return chunk
- }
|