content: remove Provider.Reader
After some analysis, it was found that Content.Reader was generally redudant to an io.ReaderAt. This change removes `Content.Reader` in favor of a `Content.ReaderAt`. In general, `ReaderAt` can perform better over interfaces with indeterminant latency because it avoids remote state for reads. Where a reader is required, a helper is provided to convert it into an `io.SectionReader`. Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
@@ -7,54 +7,17 @@ import (
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
type remoteReader struct {
|
||||
client contentapi.Content_ReadClient
|
||||
extra []byte
|
||||
}
|
||||
|
||||
func (rr *remoteReader) Read(p []byte) (n int, err error) {
|
||||
n += copy(p, rr.extra)
|
||||
if n >= len(p) {
|
||||
if n <= len(rr.extra) {
|
||||
rr.extra = rr.extra[n:]
|
||||
} else {
|
||||
rr.extra = rr.extra[:0]
|
||||
}
|
||||
return
|
||||
}
|
||||
rr.extra = rr.extra[:0]
|
||||
|
||||
p = p[n:]
|
||||
for len(p) > 0 {
|
||||
var resp *contentapi.ReadContentResponse
|
||||
// fill our buffer up until we can fill p.
|
||||
resp, err = rr.client.Recv()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
copied := copy(p, resp.Data)
|
||||
n += copied
|
||||
p = p[copied:]
|
||||
|
||||
if len(p) == 0 {
|
||||
rr.extra = append(rr.extra, resp.Data[copied:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rr *remoteReader) Close() error {
|
||||
return rr.client.CloseSend()
|
||||
}
|
||||
|
||||
type remoteReaderAt struct {
|
||||
ctx context.Context
|
||||
digest digest.Digest
|
||||
size int64
|
||||
client contentapi.ContentClient
|
||||
}
|
||||
|
||||
func (ra *remoteReaderAt) Size() int64 {
|
||||
return ra.size
|
||||
}
|
||||
|
||||
func (ra *remoteReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
rr := &contentapi.ReadContentRequest{
|
||||
Digest: ra.digest,
|
||||
@@ -80,3 +43,7 @@ func (ra *remoteReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (rr *remoteReaderAt) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -168,20 +168,11 @@ func (s *Service) Read(req *api.ReadContentRequest, session api.Content_ReadServ
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
|
||||
rc, err := s.store.Reader(session.Context(), req.Digest)
|
||||
ra, err := s.store.ReaderAt(session.Context(), req.Digest)
|
||||
if err != nil {
|
||||
return errdefs.ToGRPC(err)
|
||||
}
|
||||
defer rc.Close() // TODO(stevvooe): Cache these file descriptors for performance.
|
||||
|
||||
ra, ok := rc.(io.ReaderAt)
|
||||
if !ok {
|
||||
// TODO(stevvooe): Need to set this up to get correct behavior across
|
||||
// board. May change interface to store to just return ReaderAtCloser.
|
||||
// Possibly, we could just return io.ReaderAt and handle file
|
||||
// descriptors internally.
|
||||
return errors.New("content service only supports content stores that return ReaderAt")
|
||||
}
|
||||
defer ra.Close()
|
||||
|
||||
var (
|
||||
offset = req.Offset
|
||||
|
||||
@@ -70,21 +70,16 @@ func (rs *remoteStore) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *remoteStore) Reader(ctx context.Context, dgst digest.Digest) (io.ReadCloser, error) {
|
||||
client, err := rs.client.Read(ctx, &contentapi.ReadContentRequest{Digest: dgst})
|
||||
func (rs *remoteStore) ReaderAt(ctx context.Context, dgst digest.Digest) (content.ReaderAt, error) {
|
||||
i, err := rs.Info(ctx, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &remoteReader{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rs *remoteStore) ReaderAt(ctx context.Context, dgst digest.Digest) (io.ReaderAt, error) {
|
||||
return &remoteReaderAt{
|
||||
ctx: ctx,
|
||||
digest: dgst,
|
||||
size: i.Size,
|
||||
client: rs.client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -100,11 +100,11 @@ func (s *Service) Create(ctx context.Context, r *api.CreateTaskRequest) (*api.Cr
|
||||
if r.Checkpoint.MediaType != images.MediaTypeContainerd1Checkpoint {
|
||||
return nil, fmt.Errorf("unsupported checkpoint type %q", r.Checkpoint.MediaType)
|
||||
}
|
||||
reader, err := s.store.Reader(ctx, r.Checkpoint.Digest)
|
||||
reader, err := s.store.ReaderAt(ctx, r.Checkpoint.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = archive.Apply(ctx, checkpointPath, reader)
|
||||
_, err = archive.Apply(ctx, checkpointPath, content.NewReader(reader))
|
||||
reader.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user