Browse Source
			
			
			working delayed buffered writes
			
				
		working delayed buffered writes
	
		
	
			
				but no obvious perf improvementsvolume_buffered_writes
				 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