blob: a3067f8de741357e02d9e51e105e7fe6c22421fe [file] [log] [blame]
Scott Bakereee8dd82019-09-24 12:52:34 -07001// Copyright 2014 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package http2
6
7import (
8 "errors"
9 "fmt"
10 "sync"
11)
12
13// Buffer chunks are allocated from a pool to reduce pressure on GC.
14// The maximum wasted space per dataBuffer is 2x the largest size class,
15// which happens when the dataBuffer has multiple chunks and there is
16// one unread byte in both the first and last chunks. We use a few size
17// classes to minimize overheads for servers that typically receive very
18// small request bodies.
19//
20// TODO: Benchmark to determine if the pools are necessary. The GC may have
21// improved enough that we can instead allocate chunks like this:
22// make([]byte, max(16<<10, expectedBytesRemaining))
23var (
24 dataChunkSizeClasses = []int{
25 1 << 10,
26 2 << 10,
27 4 << 10,
28 8 << 10,
29 16 << 10,
30 }
31 dataChunkPools = [...]sync.Pool{
32 {New: func() interface{} { return make([]byte, 1<<10) }},
33 {New: func() interface{} { return make([]byte, 2<<10) }},
34 {New: func() interface{} { return make([]byte, 4<<10) }},
35 {New: func() interface{} { return make([]byte, 8<<10) }},
36 {New: func() interface{} { return make([]byte, 16<<10) }},
37 }
38)
39
40func getDataBufferChunk(size int64) []byte {
41 i := 0
42 for ; i < len(dataChunkSizeClasses)-1; i++ {
43 if size <= int64(dataChunkSizeClasses[i]) {
44 break
45 }
46 }
47 return dataChunkPools[i].Get().([]byte)
48}
49
50func putDataBufferChunk(p []byte) {
51 for i, n := range dataChunkSizeClasses {
52 if len(p) == n {
53 dataChunkPools[i].Put(p)
54 return
55 }
56 }
57 panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
58}
59
60// dataBuffer is an io.ReadWriter backed by a list of data chunks.
61// Each dataBuffer is used to read DATA frames on a single stream.
62// The buffer is divided into chunks so the server can limit the
63// total memory used by a single connection without limiting the
64// request body size on any single stream.
65type dataBuffer struct {
66 chunks [][]byte
67 r int // next byte to read is chunks[0][r]
68 w int // next byte to write is chunks[len(chunks)-1][w]
69 size int // total buffered bytes
70 expected int64 // we expect at least this many bytes in future Write calls (ignored if <= 0)
71}
72
73var errReadEmpty = errors.New("read from empty dataBuffer")
74
75// Read copies bytes from the buffer into p.
76// It is an error to read when no data is available.
77func (b *dataBuffer) Read(p []byte) (int, error) {
78 if b.size == 0 {
79 return 0, errReadEmpty
80 }
81 var ntotal int
82 for len(p) > 0 && b.size > 0 {
83 readFrom := b.bytesFromFirstChunk()
84 n := copy(p, readFrom)
85 p = p[n:]
86 ntotal += n
87 b.r += n
88 b.size -= n
89 // If the first chunk has been consumed, advance to the next chunk.
90 if b.r == len(b.chunks[0]) {
91 putDataBufferChunk(b.chunks[0])
92 end := len(b.chunks) - 1
93 copy(b.chunks[:end], b.chunks[1:])
94 b.chunks[end] = nil
95 b.chunks = b.chunks[:end]
96 b.r = 0
97 }
98 }
99 return ntotal, nil
100}
101
102func (b *dataBuffer) bytesFromFirstChunk() []byte {
103 if len(b.chunks) == 1 {
104 return b.chunks[0][b.r:b.w]
105 }
106 return b.chunks[0][b.r:]
107}
108
109// Len returns the number of bytes of the unread portion of the buffer.
110func (b *dataBuffer) Len() int {
111 return b.size
112}
113
114// Write appends p to the buffer.
115func (b *dataBuffer) Write(p []byte) (int, error) {
116 ntotal := len(p)
117 for len(p) > 0 {
118 // If the last chunk is empty, allocate a new chunk. Try to allocate
119 // enough to fully copy p plus any additional bytes we expect to
120 // receive. However, this may allocate less than len(p).
121 want := int64(len(p))
122 if b.expected > want {
123 want = b.expected
124 }
125 chunk := b.lastChunkOrAlloc(want)
126 n := copy(chunk[b.w:], p)
127 p = p[n:]
128 b.w += n
129 b.size += n
130 b.expected -= int64(n)
131 }
132 return ntotal, nil
133}
134
135func (b *dataBuffer) lastChunkOrAlloc(want int64) []byte {
136 if len(b.chunks) != 0 {
137 last := b.chunks[len(b.chunks)-1]
138 if b.w < len(last) {
139 return last
140 }
141 }
142 chunk := getDataBufferChunk(want)
143 b.chunks = append(b.chunks, chunk)
144 b.w = 0
145 return chunk
146}