podman
1// 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 http26
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 dataChunkPools = [...]sync.Pool{24{New: func() interface{} { return new([1 << 10]byte) }},25{New: func() interface{} { return new([2 << 10]byte) }},26{New: func() interface{} { return new([4 << 10]byte) }},27{New: func() interface{} { return new([8 << 10]byte) }},28{New: func() interface{} { return new([16 << 10]byte) }},29}
30
31func getDataBufferChunk(size int64) []byte {32switch {33case size <= 1<<10:34return dataChunkPools[0].Get().(*[1 << 10]byte)[:]35case size <= 2<<10:36return dataChunkPools[1].Get().(*[2 << 10]byte)[:]37case size <= 4<<10:38return dataChunkPools[2].Get().(*[4 << 10]byte)[:]39case size <= 8<<10:40return dataChunkPools[3].Get().(*[8 << 10]byte)[:]41default:42return dataChunkPools[4].Get().(*[16 << 10]byte)[:]43}44}
45
46func putDataBufferChunk(p []byte) {47switch len(p) {48case 1 << 10:49dataChunkPools[0].Put((*[1 << 10]byte)(p))50case 2 << 10:51dataChunkPools[1].Put((*[2 << 10]byte)(p))52case 4 << 10:53dataChunkPools[2].Put((*[4 << 10]byte)(p))54case 8 << 10:55dataChunkPools[3].Put((*[8 << 10]byte)(p))56case 16 << 10:57dataChunkPools[4].Put((*[16 << 10]byte)(p))58default:59panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))60}61}
62
63// dataBuffer is an io.ReadWriter backed by a list of data chunks.
64// Each dataBuffer is used to read DATA frames on a single stream.
65// The buffer is divided into chunks so the server can limit the
66// total memory used by a single connection without limiting the
67// request body size on any single stream.
68type dataBuffer struct {69chunks [][]byte70r int // next byte to read is chunks[0][r]71w int // next byte to write is chunks[len(chunks)-1][w]72size int // total buffered bytes73expected int64 // we expect at least this many bytes in future Write calls (ignored if <= 0)74}
75
76var errReadEmpty = errors.New("read from empty dataBuffer")77
78// Read copies bytes from the buffer into p.
79// It is an error to read when no data is available.
80func (b *dataBuffer) Read(p []byte) (int, error) {81if b.size == 0 {82return 0, errReadEmpty83}84var ntotal int85for len(p) > 0 && b.size > 0 {86readFrom := b.bytesFromFirstChunk()87n := copy(p, readFrom)88p = p[n:]89ntotal += n90b.r += n91b.size -= n92// If the first chunk has been consumed, advance to the next chunk.93if b.r == len(b.chunks[0]) {94putDataBufferChunk(b.chunks[0])95end := len(b.chunks) - 196copy(b.chunks[:end], b.chunks[1:])97b.chunks[end] = nil98b.chunks = b.chunks[:end]99b.r = 0100}101}102return ntotal, nil103}
104
105func (b *dataBuffer) bytesFromFirstChunk() []byte {106if len(b.chunks) == 1 {107return b.chunks[0][b.r:b.w]108}109return b.chunks[0][b.r:]110}
111
112// Len returns the number of bytes of the unread portion of the buffer.
113func (b *dataBuffer) Len() int {114return b.size115}
116
117// Write appends p to the buffer.
118func (b *dataBuffer) Write(p []byte) (int, error) {119ntotal := len(p)120for len(p) > 0 {121// If the last chunk is empty, allocate a new chunk. Try to allocate122// enough to fully copy p plus any additional bytes we expect to123// receive. However, this may allocate less than len(p).124want := int64(len(p))125if b.expected > want {126want = b.expected127}128chunk := b.lastChunkOrAlloc(want)129n := copy(chunk[b.w:], p)130p = p[n:]131b.w += n132b.size += n133b.expected -= int64(n)134}135return ntotal, nil136}
137
138func (b *dataBuffer) lastChunkOrAlloc(want int64) []byte {139if len(b.chunks) != 0 {140last := b.chunks[len(b.chunks)-1]141if b.w < len(last) {142return last143}144}145chunk := getDataBufferChunk(want)146b.chunks = append(b.chunks, chunk)147b.w = 0148return chunk149}
150