@@ -17,54 +17,96 @@ package base
1717import (
1818 "fmt"
1919 "io"
20-
21- "github.com/aws/aws-sdk-go/aws "
20+ "sync"
21+ "sync/atomic "
2222)
2323
2424var _ FileReadWriter = & BufferReadWriter {}
2525
26- // BufferReadWriter implements FileReadWriter interface for in-memory buffering.
26+ // BufferReadWriter implements FileReadWriter for in-memory buffering.
27+ //
28+ // When created with size > 0, WriteAt takes a fast path using RLock, allowing
29+ // concurrent goroutines writing to non-overlapping byte ranges to proceed in
30+ // parallel with no serialization. The maximum written extent is tracked via an
31+ // atomic so that Bytes(), Size(), Read, and ReadAt return only the data that
32+ // was actually written.
33+ //
34+ // When created with size == 0, every WriteAt that extends the buffer acquires a
35+ // full write lock to grow the backing slice.
36+ //
37+ // Write, Read, ReadAt, and Seek must not be called concurrently with each other
38+ // or with WriteAt.
2739type BufferReadWriter struct {
28- buf * aws.WriteAtBuffer
29- offset int64
40+ mu sync.RWMutex
41+ buf []byte
42+ written atomic.Int64
43+ offset int64
3044}
3145
32- // NewBufferReadWriter creates a new BufferReadWriter with an initial capacity of size bytes.
46+ // NewBufferReadWriter creates a new BufferReadWriter pre-allocated to size bytes.
47+ // Pass the exact blob size when known to enable lock-free
48+ // concurrent WriteAt calls for non-overlapping shard ranges.
3349func NewBufferReadWriter (size uint64 ) * BufferReadWriter {
34- bytesSlice := make ([]byte , 0 , size )
35- buf := aws .NewWriteAtBuffer (bytesSlice )
36- // Although this is default, this is explicitly set to notify that we are reserving
37- // only as much capacity as needed
38- buf .GrowthCoeff = 1
39-
40- return & BufferReadWriter {
41- buf : buf ,
42- offset : 0 ,
43- }
50+ return & BufferReadWriter {buf : make ([]byte , size )}
4451}
4552
46- // Write implements io.Writer by using WriteAt with current write offset.
53+ // Write implements io.Writer using the current sequential write offset.
4754func (b * BufferReadWriter ) Write (p []byte ) (n int , err error ) {
48- n , err = b .buf . WriteAt (p , b .offset )
55+ n , err = b .WriteAt (p , b .offset )
4956 b .offset += int64 (n )
5057 return n , err
5158}
5259
53- // WriteAt implements io.WriterAt for parallel writes.
54- func (b * BufferReadWriter ) WriteAt (p []byte , off int64 ) (n int , err error ) {
60+ // WriteAt implements io.WriterAt.
61+ //
62+ // Fast path (off+len(p) within pre-allocated buffer): multiple goroutines may
63+ // call WriteAt concurrently, provided their byte ranges do not overlap.
64+ // Slow path (write extends beyond current buffer): acquires an exclusive lock
65+ // to grow the buffer, then writes.
66+ func (b * BufferReadWriter ) WriteAt (p []byte , off int64 ) (int , error ) {
5567 if off < 0 {
5668 return 0 , fmt .Errorf ("negative offset" )
5769 }
58- return b .buf .WriteAt (p , off )
70+ end := off + int64 (len (p ))
71+ if end < off {
72+ return 0 , fmt .Errorf ("write at offset %d length %d overflows int64" , off , len (p ))
73+ }
74+
75+ b .mu .RLock ()
76+ if end <= int64 (len (b .buf )) {
77+ n := copy (b .buf [off :], p )
78+ for {
79+ cur := b .written .Load ()
80+ if end <= cur || b .written .CompareAndSwap (cur , end ) {
81+ break
82+ }
83+ }
84+ b .mu .RUnlock ()
85+ return n , nil
86+ }
87+ b .mu .RUnlock ()
88+
89+ b .mu .Lock ()
90+ defer b .mu .Unlock ()
91+ if end > int64 (len (b .buf )) {
92+ grown := make ([]byte , end )
93+ copy (grown , b .buf )
94+ b .buf = grown
95+ }
96+ n := copy (b .buf [off :], p )
97+ if end > b .written .Load () {
98+ b .written .Store (end )
99+ }
100+ return n , nil
59101}
60102
61103// Read implements io.Reader for sequential reads.
62104func (b * BufferReadWriter ) Read (p []byte ) (n int , err error ) {
63- bufBytes := b .buf . Bytes ()
64- if b .offset >= int64 ( len ( bufBytes )) {
105+ written := b .written . Load ()
106+ if b .offset >= written {
65107 return 0 , io .EOF
66108 }
67- n = copy (p , bufBytes [b .offset :])
109+ n = copy (p , b . buf [b .offset :written ])
68110 b .offset += int64 (n )
69111 if n < len (p ) {
70112 err = io .EOF
@@ -77,11 +119,14 @@ func (b *BufferReadWriter) ReadAt(p []byte, off int64) (n int, err error) {
77119 if off < 0 {
78120 return 0 , fmt .Errorf ("negative offset" )
79121 }
80- bufBytes := b .buf .Bytes ()
81- if off >= int64 (len (bufBytes )) {
122+ b .mu .RLock ()
123+ buf := b .buf
124+ written := b .written .Load ()
125+ b .mu .RUnlock ()
126+ if off >= written {
82127 return 0 , io .EOF
83128 }
84- n = copy (p , bufBytes [off :])
129+ n = copy (p , buf [off :written ])
85130 if n < len (p ) {
86131 err = io .EOF
87132 }
@@ -91,23 +136,19 @@ func (b *BufferReadWriter) ReadAt(p []byte, off int64) (n int, err error) {
91136// Seek implements io.Seeker.
92137func (b * BufferReadWriter ) Seek (offset int64 , whence int ) (int64 , error ) {
93138 var newOffset int64
94- bufSize := int64 (len (b .buf .Bytes ()))
95-
96139 switch whence {
97140 case io .SeekStart :
98141 newOffset = offset
99142 case io .SeekCurrent :
100143 newOffset = b .offset + offset
101144 case io .SeekEnd :
102- newOffset = bufSize + offset
145+ newOffset = b . written . Load () + offset
103146 default :
104147 return 0 , fmt .Errorf ("invalid whence: %d" , whence )
105148 }
106-
107149 if newOffset < 0 {
108150 return 0 , fmt .Errorf ("negative position: %d" , newOffset )
109151 }
110-
111152 b .offset = newOffset
112153 return newOffset , nil
113154}
@@ -117,10 +158,8 @@ func (b *BufferReadWriter) Close() error {
117158 return nil
118159}
119160
120- // Size returns the size of the buffer
121- func (b * BufferReadWriter ) Size () int64 {
122- return int64 (len (b .buf .Bytes ()))
123- }
161+ // Size returns the largest end offset written so far.
162+ func (b * BufferReadWriter ) Size () int64 { return b .written .Load () }
124163
125164// Cancel is no-op
126165func (b * BufferReadWriter ) Cancel () error {
@@ -132,7 +171,7 @@ func (b *BufferReadWriter) Commit() error {
132171 return nil
133172}
134173
135- // Bytes returns the full buffer
174+ // Bytes returns the bytes that have been written so far.
136175func (b * BufferReadWriter ) Bytes () []byte {
137- return b .buf . Bytes ()
176+ return b .buf [: b . written . Load ()]
138177}
0 commit comments