Skip to content

Commit 5ba3ed3

Browse files
committed
Add buf2 package
1 parent c68251b commit 5ba3ed3

4 files changed

Lines changed: 414 additions & 0 deletions

File tree

common/buf2/buffer.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package buf2
2+
3+
import (
4+
"strconv"
5+
"sync"
6+
)
7+
8+
var bufferPool = sync.Pool{
9+
New: func() any {
10+
return &Buffer{}
11+
},
12+
}
13+
14+
type Buffer struct {
15+
start int
16+
end int
17+
chunk *Chunk
18+
}
19+
20+
func New(capacity int) *Buffer {
21+
chunk := NewChunk(capacity)
22+
buffer := bufferPool.Get().(*Buffer)
23+
*buffer = Buffer{chunk: chunk}
24+
return buffer
25+
}
26+
27+
func From(data []byte) *Buffer {
28+
buffer := New(len(data))
29+
buffer.Write(data)
30+
return buffer
31+
}
32+
33+
func (b *Buffer) Cut(start int, end int) *Buffer {
34+
if b == nil {
35+
panic("cannot cut a nil view")
36+
}
37+
b.chunk.IncRef()
38+
newBuffer := bufferPool.Get().(*Buffer)
39+
newBuffer.chunk = b.chunk
40+
newBuffer.start = start
41+
newBuffer.end = end
42+
return newBuffer
43+
}
44+
45+
func (b *Buffer) Release() {
46+
if b == nil {
47+
panic("cannot release a nil view")
48+
}
49+
b.chunk.DecRef()
50+
*b = Buffer{}
51+
bufferPool.Put(b)
52+
}
53+
54+
func (b *Buffer) Reset() {
55+
if b == nil {
56+
panic("cannot reset a nil view")
57+
}
58+
b.start = 0
59+
b.end = 0
60+
}
61+
62+
func (b *Buffer) Full() bool {
63+
return b == nil || b.end == len(b.chunk.data)
64+
}
65+
66+
func (b *Buffer) Clone() *Buffer {
67+
if b == nil {
68+
panic("cannot clone a nil view")
69+
}
70+
b.chunk.IncRef()
71+
newBuffer := bufferPool.Get().(*Buffer)
72+
newBuffer.chunk = b.chunk
73+
newBuffer.start = b.start
74+
newBuffer.end = b.end
75+
return newBuffer
76+
}
77+
78+
func (b *Buffer) Capacity() int {
79+
if b == nil {
80+
return 0
81+
}
82+
return len(b.chunk.data)
83+
}
84+
85+
func (b *Buffer) Size() int {
86+
if b == nil {
87+
return 0
88+
}
89+
return b.end - b.start
90+
}
91+
92+
func (b *Buffer) AsSlice() []byte {
93+
if b.Size() == 0 {
94+
return nil
95+
}
96+
return b.chunk.data[b.start:b.end]
97+
}
98+
99+
func (b *Buffer) ToSlice() []byte {
100+
if b.Size() == 0 {
101+
return nil
102+
}
103+
s := make([]byte, b.Size())
104+
copy(s, b.AsSlice())
105+
return s
106+
}
107+
108+
func (b *Buffer) AvailableSize() int {
109+
if b == nil {
110+
return 0
111+
}
112+
return len(b.chunk.data) - b.end
113+
}
114+
115+
func (b *Buffer) AvailableSlice() []byte {
116+
if b == nil {
117+
panic("cannot get available slice from a nil view")
118+
}
119+
return b.availableSlice()
120+
}
121+
122+
func (b *Buffer) ExtendHeader(n int) []byte {
123+
if b == nil {
124+
panic("cannot extend a nil view")
125+
}
126+
if b.start < n {
127+
panic("buffer overflow: cap " + strconv.Itoa(b.Capacity()) + ",start " + strconv.Itoa(b.start) + ", need " + strconv.Itoa(n))
128+
}
129+
if b.sharesChunk() {
130+
defer b.chunk.DecRef()
131+
b.chunk = b.chunk.Clone()
132+
}
133+
b.start -= n
134+
return b.chunk.data[b.start : b.start+n]
135+
}
136+
137+
func (b *Buffer) Extend(n int) []byte {
138+
if b == nil {
139+
panic("cannot extend a nil view")
140+
}
141+
newEnd := b.end + n
142+
if newEnd > b.Capacity() {
143+
panic("buffer overflow: cap " + strconv.Itoa(b.Capacity()) + ",end " + strconv.Itoa(b.end) + ", need " + strconv.Itoa(n))
144+
}
145+
if b.sharesChunk() {
146+
defer b.chunk.DecRef()
147+
b.chunk = b.chunk.Clone()
148+
}
149+
newSlice := b.chunk.data[b.end : b.end+n]
150+
b.end = newEnd
151+
return newSlice
152+
}
153+
154+
func (b *Buffer) Advance(from int) {
155+
if b == nil {
156+
panic("cannot advance a nil view")
157+
}
158+
b.start += from
159+
}
160+
161+
func (b *Buffer) Truncate(to int) {
162+
if b == nil {
163+
panic("cannot truncate a nil view")
164+
}
165+
b.end = b.start + to
166+
}

common/buf2/buffer_rw.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package buf2
2+
3+
import (
4+
"crypto/rand"
5+
"fmt"
6+
"io"
7+
8+
"github.com/sagernet/sing/common"
9+
)
10+
11+
func (b *Buffer) Read(p []byte) (int, error) {
12+
if len(p) == 0 {
13+
return 0, nil
14+
}
15+
if b.Size() == 0 {
16+
return 0, io.EOF
17+
}
18+
n := copy(p, b.AsSlice())
19+
b.Advance(n)
20+
return n, nil
21+
}
22+
23+
func (b *Buffer) Write(p []byte) (n int, err error) {
24+
if b == nil {
25+
panic("cannot write to a nil view")
26+
}
27+
if len(p) == 0 {
28+
return
29+
}
30+
if b.Full() {
31+
return 0, io.ErrShortBuffer
32+
}
33+
if b.sharesChunk() {
34+
defer b.chunk.DecRef()
35+
b.chunk = b.chunk.Clone()
36+
}
37+
n = copy(b.chunk.data[b.end:], p)
38+
b.end += n
39+
return
40+
}
41+
42+
func (b *Buffer) ReadAt(p []byte, off int) (int, error) {
43+
if off < 0 || off > b.Size() {
44+
return 0, fmt.Errorf("ReadAt(): offset out of bounds: want 0 < off < %d, got off=%d", b.Size(), off)
45+
}
46+
n := copy(p, b.AsSlice()[off:])
47+
return n, nil
48+
}
49+
50+
func (b *Buffer) WriteAt(p []byte, off int) (int, error) {
51+
if b == nil {
52+
panic("cannot write to a nil view")
53+
}
54+
if off < 0 || off > b.Size() {
55+
return 0, fmt.Errorf("write offset out of bounds: want 0 < off < %d, got off=%d", b.Size(), off)
56+
}
57+
if b.sharesChunk() {
58+
defer b.chunk.DecRef()
59+
b.chunk = b.chunk.Clone()
60+
}
61+
n := copy(b.AsSlice()[off:], p)
62+
if n < len(p) {
63+
return n, io.ErrShortWrite
64+
}
65+
return n, nil
66+
}
67+
68+
func (b *Buffer) ReadByte() (byte, error) {
69+
if b.Size() == 0 {
70+
return 0, io.EOF
71+
}
72+
p := b.AsSlice()[0]
73+
b.start++
74+
return p, nil
75+
}
76+
77+
func (b *Buffer) WriteByte(d byte) error {
78+
if b == nil {
79+
panic("cannot write to a nil view")
80+
} else if b.Full() {
81+
return io.ErrShortBuffer
82+
}
83+
if b.sharesChunk() {
84+
defer b.chunk.DecRef()
85+
b.chunk = b.chunk.Clone()
86+
}
87+
b.chunk.data[b.end] = d
88+
b.end++
89+
return nil
90+
}
91+
92+
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error) {
93+
if b.Size() > 0 {
94+
sz := b.Size()
95+
m, e := w.Write(b.AsSlice())
96+
b.Advance(m)
97+
n = int64(m)
98+
if e != nil {
99+
return n, e
100+
}
101+
if m != sz {
102+
return n, io.ErrShortWrite
103+
}
104+
}
105+
return n, nil
106+
}
107+
108+
func (b *Buffer) ReadFrom0(r io.Reader) (n int64, err error) {
109+
if b == nil {
110+
panic("cannot write to a nil view")
111+
}
112+
if b.AvailableSize() == 0 {
113+
return 0, io.ErrShortBuffer
114+
}
115+
nInt, err := r.Read(b.availableSlice())
116+
b.end += nInt
117+
n += int64(nInt)
118+
if err == io.EOF {
119+
return n, nil
120+
}
121+
return
122+
}
123+
124+
func (b *Buffer) WriteRandom(size int) []byte {
125+
buffer := b.Extend(size)
126+
common.Must1(io.ReadFull(rand.Reader, buffer))
127+
return buffer
128+
}
129+
130+
func (b *Buffer) sharesChunk() bool {
131+
return b.chunk.references.Load() > 1
132+
}
133+
134+
func (b *Buffer) availableSlice() []byte {
135+
if b.sharesChunk() {
136+
defer b.chunk.DecRef()
137+
b.chunk = b.chunk.Clone()
138+
}
139+
return b.chunk.data[b.end:]
140+
}

common/buf2/chunk.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package buf2
2+
3+
import (
4+
"github.com/sagernet/sing/common/atomic"
5+
F "github.com/sagernet/sing/common/format"
6+
)
7+
8+
type Chunk struct {
9+
data []byte
10+
references atomic.Int64
11+
}
12+
13+
func NewChunk(size int) *Chunk {
14+
var chunk *Chunk
15+
if size > MaxChunkSize {
16+
chunk = &Chunk{data: make([]byte, size)}
17+
} else {
18+
chunk = getChunkPool(size).Get().(*Chunk)
19+
for i := range chunk.data {
20+
chunk.data[i] = 0
21+
}
22+
}
23+
chunk.references.Store(1)
24+
return chunk
25+
}
26+
27+
//go:nosplit
28+
func (c *Chunk) IncRef() {
29+
v := c.references.Add(1)
30+
if v <= 1 {
31+
panic(F.ToString("Incrementing non-positive count ", v, " on buffer"))
32+
}
33+
}
34+
35+
//go:nosplit
36+
func (c *Chunk) TryIncRef() bool {
37+
const speculativeRef = 1 << 32
38+
if v := c.references.Add(speculativeRef); int32(v) == 0 {
39+
c.references.Add(-speculativeRef)
40+
return false
41+
}
42+
c.references.Add(-speculativeRef + 1)
43+
return true
44+
}
45+
46+
func (c *Chunk) ReadRefs() int64 {
47+
return c.references.Load()
48+
}
49+
50+
//go:nosplit
51+
func (c *Chunk) DecRef() {
52+
v := c.references.Add(-1)
53+
switch {
54+
case v < 0:
55+
panic(F.ToString("Decrementing non-positive ref count ", v, " on buffer"))
56+
case v == 0:
57+
if len(c.data) < MaxChunkSize {
58+
getChunkPool(len(c.data)).Put(c)
59+
}
60+
}
61+
}
62+
63+
func (c *Chunk) Clone() *Chunk {
64+
newChunk := NewChunk(len(c.data))
65+
copy(newChunk.data, c.data)
66+
return newChunk
67+
}

0 commit comments

Comments
 (0)