Browse Source
working delayed buffered writes
working delayed buffered writes
but no obvious perf improvementsvolume_buffered_writes
Chris Lu
4 years ago
2 changed files with 205 additions and 2 deletions
@ -0,0 +1,167 @@ |
|||
package buffered_writer |
|||
|
|||
import ( |
|||
"io" |
|||
"sync" |
|||
"time" |
|||
) |
|||
|
|||
type TimedWriteBuffer struct { |
|||
maxLatencyWriterAt *maxLatencyWriterAt |
|||
bufWriterAt *bufferedWriterAt |
|||
} |
|||
|
|||
func (t *TimedWriteBuffer) ReadAt(p []byte, off int64) (n int, err error) { |
|||
bufStart := t.bufWriterAt.nextOffset - int64(t.bufWriterAt.dataSize) |
|||
start := max(bufStart, off) |
|||
stop := min(t.bufWriterAt.nextOffset, off+int64(len(p))) |
|||
if start <= stop { |
|||
n = copy(p, t.bufWriterAt.data[start-bufStart:stop-bufStart]) |
|||
} |
|||
return |
|||
} |
|||
func (t *TimedWriteBuffer) WriteAt(p []byte, offset int64) (n int, err error) { |
|||
return t.maxLatencyWriterAt.WriteAt(p, offset) |
|||
} |
|||
func (t *TimedWriteBuffer) Flush() { |
|||
t.maxLatencyWriterAt.Flush() |
|||
} |
|||
func (t *TimedWriteBuffer) Close() { |
|||
t.maxLatencyWriterAt.Close() |
|||
} |
|||
|
|||
func NewTimedWriteBuffer(writerAt io.WriterAt, size int, latency time.Duration, currentOffset int64) *TimedWriteBuffer { |
|||
bufWriterAt := newBufferedWriterAt(writerAt, size, currentOffset) |
|||
maxLatencyWriterAt := newMaxLatencyWriterAt(bufWriterAt, latency) |
|||
return &TimedWriteBuffer{ |
|||
bufWriterAt: bufWriterAt, |
|||
maxLatencyWriterAt: maxLatencyWriterAt, |
|||
} |
|||
} |
|||
|
|||
type bufferedWriterAt struct { |
|||
data []byte |
|||
dataSize int |
|||
nextOffset int64 |
|||
writerAt io.WriterAt |
|||
counter int |
|||
} |
|||
|
|||
func newBufferedWriterAt(writerAt io.WriterAt, bufferSize int, currentOffset int64) *bufferedWriterAt { |
|||
return &bufferedWriterAt{ |
|||
data: make([]byte, bufferSize), |
|||
nextOffset: currentOffset, |
|||
dataSize: 0, |
|||
writerAt: writerAt, |
|||
} |
|||
} |
|||
|
|||
func (b *bufferedWriterAt) WriteAt(p []byte, offset int64) (n int, err error) { |
|||
if b.nextOffset != offset { |
|||
println("nextOffset", b.nextOffset, "bufSize", b.dataSize, "offset", offset, "data", len(p)) |
|||
} |
|||
if b.nextOffset != offset || len(p)+b.dataSize > len(b.data) { |
|||
if err = b.Flush(); err != nil { |
|||
return 0, err |
|||
} |
|||
} |
|||
if len(p)+b.dataSize > len(b.data) { |
|||
n, err = b.writerAt.WriteAt(p, offset) |
|||
if err == nil { |
|||
b.nextOffset = offset + int64(n) |
|||
} |
|||
} else { |
|||
n = copy(b.data[b.dataSize:len(p)+b.dataSize], p) |
|||
b.dataSize += n |
|||
b.nextOffset += int64(n) |
|||
b.counter++ |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (b *bufferedWriterAt) Flush() (err error) { |
|||
if b.dataSize == 0 { |
|||
return nil |
|||
} |
|||
// println("flush", b.counter)
|
|||
b.counter = 0 |
|||
_, err = b.writerAt.WriteAt(b.data[0:b.dataSize], b.nextOffset-int64(b.dataSize)) |
|||
if err == nil { |
|||
b.dataSize = 0 |
|||
} |
|||
return |
|||
} |
|||
|
|||
// adapted from https://golang.org/src/net/http/httputil/reverseproxy.go
|
|||
|
|||
type writeFlusher interface { |
|||
io.WriterAt |
|||
Flush() error |
|||
} |
|||
|
|||
type maxLatencyWriterAt struct { |
|||
dst writeFlusher |
|||
latency time.Duration // non-zero; negative means to flush immediately
|
|||
mu sync.Mutex // protects t, flushPending, and dst.Flush
|
|||
t *time.Timer |
|||
flushPending bool |
|||
} |
|||
|
|||
func newMaxLatencyWriterAt(dst writeFlusher, latency time.Duration) *maxLatencyWriterAt { |
|||
return &maxLatencyWriterAt{ |
|||
dst: dst, |
|||
latency: latency, |
|||
} |
|||
} |
|||
|
|||
func (m *maxLatencyWriterAt) WriteAt(p []byte, offset int64) (n int, err error) { |
|||
m.mu.Lock() |
|||
defer m.mu.Unlock() |
|||
n, err = m.dst.WriteAt(p, offset) |
|||
if m.latency < 0 { |
|||
m.dst.Flush() |
|||
return |
|||
} |
|||
if m.flushPending { |
|||
return |
|||
} |
|||
if m.t == nil { |
|||
m.t = time.AfterFunc(m.latency, m.Flush) |
|||
} else { |
|||
m.t.Reset(m.latency) |
|||
} |
|||
m.flushPending = true |
|||
return |
|||
} |
|||
|
|||
func (m *maxLatencyWriterAt) Flush() { |
|||
m.mu.Lock() |
|||
defer m.mu.Unlock() |
|||
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
|
|||
return |
|||
} |
|||
m.dst.Flush() |
|||
m.flushPending = false |
|||
} |
|||
|
|||
func (m *maxLatencyWriterAt) Close() { |
|||
m.mu.Lock() |
|||
defer m.mu.Unlock() |
|||
m.flushPending = false |
|||
if m.t != nil { |
|||
m.t.Stop() |
|||
} |
|||
} |
|||
|
|||
func min(x, y int64) int64 { |
|||
if x <= y { |
|||
return x |
|||
} |
|||
return y |
|||
} |
|||
func max(x, y int64) int64 { |
|||
if x <= y { |
|||
return y |
|||
} |
|||
return x |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue