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