Skip to content

Commit 45e7aa5

Browse files
committed
Update copy to discard over truncate
Prevents the copy method from calling discard on the writer when the reader is not seekable. Instead, the copy method will discard up to the offset. Truncate is a more expensive operation since any bytes that are truncated already have their hash calculated and are stored on disk in the backend. Re-writing bytes which were truncated requires transfering the data over GRPC again and re-computing the hash up to the point of truncation. Signed-off-by: Derek McGowan <[email protected]>
1 parent d7a0e70 commit 45e7aa5

3 files changed

Lines changed: 20 additions & 20 deletions

File tree

content/helpers.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package content
33
import (
44
"context"
55
"io"
6+
"io/ioutil"
67
"sync"
78

89
"github.com/containerd/containerd/errdefs"
@@ -76,14 +77,7 @@ func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected dige
7677
if ws.Offset > 0 {
7778
r, err = seekReader(r, ws.Offset, size)
7879
if err != nil {
79-
if !isUnseekable(err) {
80-
return errors.Wrapf(err, "unable to resume write to %v", ws.Ref)
81-
}
82-
83-
// reader is unseekable, try to move the writer back to the start.
84-
if err := cw.Truncate(0); err != nil {
85-
return errors.Wrapf(err, "content writer truncate failed")
86-
}
80+
return errors.Wrapf(err, "unable to resume write to %v", ws.Ref)
8781
}
8882
}
8983

@@ -103,14 +97,9 @@ func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected dige
10397
return nil
10498
}
10599

106-
var errUnseekable = errors.New("seek not supported")
107-
108-
func isUnseekable(err error) bool {
109-
return errors.Cause(err) == errUnseekable
110-
}
111-
112100
// seekReader attempts to seek the reader to the given offset, either by
113-
// resolving `io.Seeker` or by detecting `io.ReaderAt`.
101+
// resolving `io.Seeker`, by detecting `io.ReaderAt`, or discarding
102+
// up to the given offset.
114103
func seekReader(r io.Reader, offset, size int64) (io.Reader, error) {
115104
// attempt to resolve r as a seeker and setup the offset.
116105
seeker, ok := r.(io.Seeker)
@@ -134,5 +123,17 @@ func seekReader(r io.Reader, offset, size int64) (io.Reader, error) {
134123
return sr, nil
135124
}
136125

137-
return r, errors.Wrapf(errUnseekable, "seek to offset %v failed", offset)
126+
// well then, let's just discard up to the offset
127+
buf := bufPool.Get().(*[]byte)
128+
defer bufPool.Put(buf)
129+
130+
n, err := io.CopyBuffer(ioutil.Discard, io.LimitReader(r, offset), *buf)
131+
if err != nil {
132+
return nil, errors.Wrap(err, "failed to discard to offset")
133+
}
134+
if n != offset {
135+
return nil, errors.Errorf("unable to discard to offset")
136+
}
137+
138+
return r, nil
138139
}

content/helpers_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ func TestCopy(t *testing.T) {
4242
},
4343
{
4444
name: "copy with offset from unseekable source",
45-
source: copySource{reader: bytes.NewBufferString("foo"), size: 3},
46-
writer: fakeWriter{status: Status{Offset: 8}},
47-
expected: "foo",
45+
source: copySource{reader: bytes.NewBufferString("foobar"), size: 6},
46+
writer: fakeWriter{status: Status{Offset: 3}},
47+
expected: "bar",
4848
},
4949
{
5050
name: "commit already exists",

content/testsuite/testsuite.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,6 @@ func checkResume(rf func(context.Context, content.Writer, []byte, int64, int64,
403403
if err := rf(ctx, w, b, limit, size, d); err != nil {
404404
t.Fatalf("Resume failed: %+v", err)
405405
}
406-
407406
postCommit := time.Now()
408407

409408
if err := w.Close(); err != nil {

0 commit comments

Comments
 (0)