-
Notifications
You must be signed in to change notification settings - Fork 57
feat: Extend error handling to allow conflict resolution #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
54d6359
119b026
01730bc
02bc94e
b7f2705
fbc6808
508ceab
91ec78b
1eb526e
180d281
fa82b21
23fb5f0
c7ace6a
32c4c30
9081df9
0001cb1
6606fbf
ebdd117
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// Package seekablebuffers provides a seekable wrapper around net.Buffers | ||
// that enables retry functionality for database copy operations. | ||
package buffer | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
) | ||
|
||
// Buffers contains zero or more runs of bytes to write. | ||
// | ||
// On certain machines, for certain types of connections, this is | ||
// optimized into an OS-specific batch write operation (such as | ||
// "writev"). | ||
type Seekable struct { | ||
buf [][]byte | ||
position int64 | ||
} | ||
|
||
var ( | ||
_ io.WriterTo = (*Seekable)(nil) | ||
_ io.Reader = (*Seekable)(nil) | ||
_ io.Writer = (*Seekable)(nil) | ||
_ io.Seeker = (*Seekable)(nil) | ||
) | ||
|
||
func NewSeekable(buf [][]byte) *Seekable { | ||
return &Seekable{ | ||
buf: buf, | ||
position: 0, | ||
} | ||
} | ||
|
||
func (v *Seekable) HasData() bool { | ||
return v.position < v.TotalSize() | ||
} | ||
|
||
func (v *Seekable) TotalSize() int64 { | ||
var size int64 | ||
for _, b := range v.buf { | ||
size += int64(len(b)) | ||
} | ||
return size | ||
} | ||
|
||
// WriteTo writes contents of the buffers to w. | ||
// | ||
// WriteTo implements [io.WriterTo] for [Buffers]. | ||
// | ||
// WriteTo modifies the slice v as well as v[i] for 0 <= i < len(v), | ||
// but does not modify v[i][j] for any i, j. | ||
func (v *Seekable) WriteTo(w io.Writer) (n int64, err error) { | ||
if v.position >= v.TotalSize() { | ||
return 0, nil | ||
} | ||
|
||
currentPos := v.position | ||
|
||
for _, buf := range v.buf { | ||
bufLen := int64(len(buf)) | ||
if currentPos >= bufLen { | ||
currentPos -= bufLen | ||
continue | ||
} | ||
|
||
startInBuf := currentPos | ||
bytesToWrite := buf[startInBuf:] | ||
|
||
nb, err := w.Write(bytesToWrite) | ||
n += int64(nb) | ||
if err != nil { | ||
v.position += n | ||
return n, err | ||
} | ||
currentPos = 0 | ||
} | ||
|
||
v.position += n | ||
return n, nil | ||
} | ||
|
||
// Read from the buffers. | ||
// | ||
// Read implements [io.Reader] for [Buffers]. | ||
// | ||
// Read modifies the slice v as well as v[i] for 0 <= i < len(v), | ||
// but does not modify v[i][j] for any i, j. | ||
func (v *Seekable) Read(p []byte) (n int, err error) { | ||
if v.position >= v.TotalSize() { | ||
return 0, io.EOF | ||
} | ||
|
||
remaining := len(p) | ||
currentPos := v.position | ||
|
||
for i, buf := range v.buf { | ||
if remaining == 0 { | ||
break | ||
} | ||
|
||
bufLen := int64(len(buf)) | ||
if currentPos >= bufLen { | ||
currentPos -= bufLen | ||
continue | ||
} | ||
|
||
startInBuf := currentPos | ||
bytesToRead := bufLen - startInBuf | ||
if int64(remaining) < bytesToRead { | ||
bytesToRead = int64(remaining) | ||
} | ||
|
||
copied := copy(p[n:], buf[startInBuf:startInBuf+bytesToRead]) | ||
n += copied | ||
remaining -= copied | ||
currentPos = 0 | ||
|
||
if i == len(v.buf)-1 && n < len(p) { | ||
err = io.EOF | ||
} | ||
} | ||
|
||
v.position += int64(n) | ||
return n, err | ||
} | ||
|
||
// Write appends data to the buffer | ||
func (v *Seekable) Write(p []byte) (n int, err error) { | ||
if len(p) == 0 { | ||
return 0, nil | ||
} | ||
|
||
// Create a copy of the input data to avoid issues with caller reusing the slice | ||
data := make([]byte, len(p)) | ||
copy(data, p) | ||
|
||
v.buf = append(v.buf, data) | ||
return len(p), nil | ||
} | ||
|
||
// WriteString appends a string to the buffer | ||
func (v *Seekable) WriteString(s string) (n int, err error) { | ||
return v.Write([]byte(s)) | ||
} | ||
|
||
// Seek sets the position for next Read or Write operation | ||
func (v *Seekable) Seek(offset int64, whence int) (int64, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a use-case for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. net buffer discards data on read. here is the implementation. on Read it consumes the bytes by resizing the slice. That is why this implementation uses the moving indexes instead. It allows to just reset it to come back to the start.
|
||
totalSize := v.TotalSize() | ||
|
||
var newPos int64 | ||
switch whence { | ||
case io.SeekStart: | ||
newPos = offset | ||
case io.SeekCurrent: | ||
newPos = v.position + offset | ||
case io.SeekEnd: | ||
newPos = totalSize + offset | ||
default: | ||
return v.position, fmt.Errorf("invalid whence value: %d", whence) | ||
} | ||
|
||
if newPos < 0 { | ||
return v.position, fmt.Errorf("seek position cannot be negative: %d", newPos) | ||
} | ||
if newPos > totalSize { | ||
return v.position, fmt.Errorf("seek position beyond buffer size: %d > %d", newPos, totalSize) | ||
} | ||
|
||
v.position = newPos | ||
return v.position, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no copy involved with net.Buffers., should we retain that property to avoid excessive garbage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a copy, but done in scan.go function
here exactly, I hope the link works
https://github.com/timescale/timescaledb-parallel-copy/pull/124/files#diff-50cd4d562d776a354f7d300bd42b001d9736e761941da821c9e14344d51a78e8L200