Skip to content

Commit d76f801

Browse files
authored
s2: Add EncodeBuffer buffer recycling callback (#982)
WriterBufferDone will perform a callback when EncodeBuffer has finished writing a buffer to the output and the buffer can safely be reused. If the buffer was split into several blocks, it will be sent after the last block. Callbacks will not be done concurrently. Fixes #981
1 parent cfab8bd commit d76f801

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

s2/writer.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,14 @@ type Writer struct {
8383
snappy bool
8484
flushOnWrite bool
8585
appendIndex bool
86+
bufferCB func([]byte)
8687
level uint8
8788
}
8889

8990
type result struct {
9091
b []byte
92+
// return when writing
93+
ret []byte
9194
// Uncompressed start offset
9295
startOffset int64
9396
}
@@ -146,6 +149,10 @@ func (w *Writer) Reset(writer io.Writer) {
146149
for write := range toWrite {
147150
// Wait for the data to be available.
148151
input := <-write
152+
if input.ret != nil && w.bufferCB != nil {
153+
w.bufferCB(input.ret)
154+
input.ret = nil
155+
}
149156
in := input.b
150157
if len(in) > 0 {
151158
if w.err(nil) == nil {
@@ -341,7 +348,8 @@ func (w *Writer) AddSkippableBlock(id uint8, data []byte) (err error) {
341348
// but the input buffer cannot be written to by the caller
342349
// until Flush or Close has been called when concurrency != 1.
343350
//
344-
// If you cannot control that, use the regular Write function.
351+
// Use the WriterBufferDone to receive a callback when the buffer is done
352+
// Processing.
345353
//
346354
// Note that input is not buffered.
347355
// This means that each write will result in discrete blocks being created.
@@ -364,6 +372,9 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) {
364372
}
365373
if w.concurrency == 1 {
366374
_, err := w.writeSync(buf)
375+
if w.bufferCB != nil {
376+
w.bufferCB(buf)
377+
}
367378
return err
368379
}
369380

@@ -378,7 +389,7 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) {
378389
hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes}
379390
}
380391
}
381-
392+
orgBuf := buf
382393
for len(buf) > 0 {
383394
// Cut input.
384395
uncompressed := buf
@@ -397,6 +408,9 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) {
397408
startOffset: w.uncompWritten,
398409
}
399410
w.uncompWritten += int64(len(uncompressed))
411+
if len(buf) == 0 && w.bufferCB != nil {
412+
res.ret = orgBuf
413+
}
400414
go func() {
401415
race.ReadSlice(uncompressed)
402416

@@ -941,6 +955,17 @@ func WriterUncompressed() WriterOption {
941955
}
942956
}
943957

958+
// WriterBufferDone will perform a callback when EncodeBuffer has finished
959+
// writing a buffer to the output and the buffer can safely be reused.
960+
// If the buffer was split into several blocks, it will be sent after the last block.
961+
// Callbacks will not be done concurrently.
962+
func WriterBufferDone(fn func(b []byte)) WriterOption {
963+
return func(w *Writer) error {
964+
w.bufferCB = fn
965+
return nil
966+
}
967+
}
968+
944969
// WriterBlockSize allows to override the default block size.
945970
// Blocks will be this size or smaller.
946971
// Minimum size is 4KB and maximum size is 4MB.

s2/writer_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,49 @@ func TestBigEncodeBufferSync(t *testing.T) {
576576
t.Log(n)
577577
}
578578

579+
func TestWriterBufferDone(t *testing.T) {
580+
const blockSize = 1 << 20
581+
var buffers [][]byte
582+
for _, size := range []int{10, 100, 10000, blockSize, blockSize * 8} {
583+
buffers = append(buffers, make([]byte, size))
584+
}
585+
586+
dst := io.Discard
587+
wantNextBuf := 0
588+
var cbErr error
589+
enc := NewWriter(dst, WriterBlockSize(blockSize), WriterConcurrency(4), WriterBufferDone(func(b []byte) {
590+
if !bytes.Equal(b, buffers[wantNextBuf]) && cbErr == nil {
591+
cbErr = fmt.Errorf("wrong buffer returned, want %v got %v", buffers[wantNextBuf], b)
592+
}
593+
// Detect races.
594+
for i := range b[:] {
595+
b[i] = 255
596+
}
597+
wantNextBuf++
598+
}))
599+
for n, buf := range buffers {
600+
// Change the buffer to a new value.
601+
for i := range buf[:] {
602+
buf[i] = byte(n)
603+
}
604+
// Send the buffer
605+
err := enc.EncodeBuffer(buf)
606+
if err != nil {
607+
t.Fatal(err)
608+
}
609+
}
610+
err := enc.Close()
611+
if err != nil {
612+
t.Fatal(err)
613+
}
614+
if wantNextBuf != len(buffers) {
615+
t.Fatalf("want %d buffers, got %d ", len(buffers), wantNextBuf)
616+
}
617+
if cbErr != nil {
618+
t.Fatal(cbErr)
619+
}
620+
}
621+
579622
func BenchmarkWriterRandom(b *testing.B) {
580623
rng := rand.New(rand.NewSource(1))
581624
// Make max window so we never get matches.

0 commit comments

Comments
 (0)