forked from TrueCloudLab/frostfs-s3-gw
[#346] api: Do not use io.Pipe
in CompleteMultipartUpload
Replace `layer.objectWritePayload` method with `initObjectPayloadReader` which returns `io.Reader` of the object payload. Copy payload data to the parameterized `io.Writer` in `layer.GetObject`. Remove `io.Pipe` from `CompleteMultipartUpload` implementation and build analogue of `io.MultiReader` for the part list. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
eac4c4d849
commit
8fb3835250
3 changed files with 79 additions and 63 deletions
|
@ -567,7 +567,6 @@ func (n *layer) ListBuckets(ctx context.Context) ([]*data.BucketInfo, error) {
|
||||||
func (n *layer) GetObject(ctx context.Context, p *GetObjectParams) error {
|
func (n *layer) GetObject(ctx context.Context, p *GetObjectParams) error {
|
||||||
var params getParams
|
var params getParams
|
||||||
|
|
||||||
params.w = p.Writer
|
|
||||||
params.oid = p.ObjectInfo.ID
|
params.oid = p.ObjectInfo.ID
|
||||||
params.cid = p.ObjectInfo.CID
|
params.cid = p.ObjectInfo.CID
|
||||||
|
|
||||||
|
@ -580,10 +579,22 @@ func (n *layer) GetObject(ctx context.Context, p *GetObjectParams) error {
|
||||||
params.ln = p.Range.End - p.Range.Start + 1
|
params.ln = p.Range.End - p.Range.Start + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
err := n.objectWritePayload(ctx, params)
|
payload, err := n.initObjectPayloadReader(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.objCache.Delete(p.ObjectInfo.Address())
|
return fmt.Errorf("init object payload reader: %w", err)
|
||||||
return fmt.Errorf("couldn't get object, cid: %s : %w", p.ObjectInfo.CID, err)
|
}
|
||||||
|
|
||||||
|
if params.ln == 0 {
|
||||||
|
params.ln = 4096 // configure?
|
||||||
|
}
|
||||||
|
|
||||||
|
// alloc buffer for copying
|
||||||
|
buf := make([]byte, params.ln) // sync-pool it?
|
||||||
|
|
||||||
|
// copy full payload
|
||||||
|
_, err = io.CopyBuffer(p.Writer, payload, buf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copy object payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -2,6 +2,8 @@ package layer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
stderrors "errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -169,6 +171,45 @@ func (n *layer) UploadPartCopy(ctx context.Context, p *UploadCopyParams) (*data.
|
||||||
return n.CopyObject(ctx, c)
|
return n.CopyObject(ctx, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implements io.Reader of payloads of the object list stored in the NeoFS network.
|
||||||
|
type multiObjectReader struct {
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
layer *layer
|
||||||
|
|
||||||
|
prm getParams
|
||||||
|
|
||||||
|
curReader io.Reader
|
||||||
|
|
||||||
|
parts []*data.ObjectInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiObjectReader) Read(p []byte) (n int, err error) {
|
||||||
|
if x.curReader != nil {
|
||||||
|
n, err = x.curReader.Read(p)
|
||||||
|
if !stderrors.Is(err, io.EOF) {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(x.parts) == 0 {
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
x.prm.oid = x.parts[0].ID
|
||||||
|
|
||||||
|
x.curReader, err = x.layer.initObjectPayloadReader(x.ctx, x.prm)
|
||||||
|
if err != nil {
|
||||||
|
return n, fmt.Errorf("init payload reader for the next part: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.parts = x.parts[1:]
|
||||||
|
|
||||||
|
next, err := x.Read(p[n:])
|
||||||
|
|
||||||
|
return n + next, err
|
||||||
|
}
|
||||||
|
|
||||||
func (n *layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*data.ObjectInfo, error) {
|
func (n *layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*data.ObjectInfo, error) {
|
||||||
var obj *data.ObjectInfo
|
var obj *data.ObjectInfo
|
||||||
|
|
||||||
|
@ -231,10 +272,6 @@ func (n *layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
||||||
initMetadata[api.ContentType] = objects[0].ContentType
|
initMetadata[api.ContentType] = objects[0].ContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
pr, pw := io.Pipe()
|
|
||||||
done := make(chan bool)
|
|
||||||
uploadCompleted := false
|
|
||||||
|
|
||||||
/* We will keep "S3-Upload-Id" attribute in completed object to determine is it "common" object or completed object.
|
/* We will keep "S3-Upload-Id" attribute in completed object to determine is it "common" object or completed object.
|
||||||
We will need to differ these objects if something goes wrong during completing multipart upload.
|
We will need to differ these objects if something goes wrong during completing multipart upload.
|
||||||
I.e. we had completed the object but didn't put tagging/acl for some reason */
|
I.e. we had completed the object but didn't put tagging/acl for some reason */
|
||||||
|
@ -242,46 +279,26 @@ func (n *layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar
|
||||||
delete(initMetadata, UploadKeyAttributeName)
|
delete(initMetadata, UploadKeyAttributeName)
|
||||||
delete(initMetadata, attrVersionsIgnore)
|
delete(initMetadata, attrVersionsIgnore)
|
||||||
|
|
||||||
go func(done chan bool) {
|
r := &multiObjectReader{
|
||||||
obj, err = n.objectPut(ctx, p.Info.Bkt, &PutObjectParams{
|
ctx: ctx,
|
||||||
Bucket: p.Info.Bkt.Name,
|
layer: n,
|
||||||
Object: p.Info.Key,
|
parts: parts,
|
||||||
Reader: pr,
|
|
||||||
Header: initMetadata,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
n.log.Error("could not put a completed object (multipart upload)",
|
|
||||||
zap.String("uploadID", p.Info.UploadID),
|
|
||||||
zap.String("uploadKey", p.Info.Key),
|
|
||||||
zap.Error(err))
|
|
||||||
done <- true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uploadCompleted = true
|
|
||||||
done <- true
|
|
||||||
}(done)
|
|
||||||
|
|
||||||
var prmGet getParams
|
|
||||||
prmGet.w = pw
|
|
||||||
prmGet.cid = p.Info.Bkt.CID
|
|
||||||
|
|
||||||
for _, part := range parts {
|
|
||||||
prmGet.oid = part.ID
|
|
||||||
|
|
||||||
err = n.objectWritePayload(ctx, prmGet)
|
|
||||||
if err != nil {
|
|
||||||
_ = pw.Close()
|
|
||||||
n.log.Error("could not download a part of multipart upload",
|
|
||||||
zap.String("uploadID", p.Info.UploadID),
|
|
||||||
zap.String("part number", part.Headers[UploadPartNumberAttributeName]),
|
|
||||||
zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ = pw.Close()
|
|
||||||
<-done
|
|
||||||
|
|
||||||
if !uploadCompleted {
|
r.prm.cid = p.Info.Bkt.CID
|
||||||
|
|
||||||
|
obj, err = n.objectPut(ctx, p.Info.Bkt, &PutObjectParams{
|
||||||
|
Bucket: p.Info.Bkt.Name,
|
||||||
|
Object: p.Info.Key,
|
||||||
|
Reader: r,
|
||||||
|
Header: initMetadata,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
n.log.Error("could not put a completed object (multipart upload)",
|
||||||
|
zap.String("uploadID", p.Info.UploadID),
|
||||||
|
zap.String("uploadKey", p.Info.Key),
|
||||||
|
zap.Error(err))
|
||||||
|
|
||||||
return nil, errors.GetAPIError(errors.ErrInternalError)
|
return nil, errors.GetAPIError(errors.ErrInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
getParams struct {
|
getParams struct {
|
||||||
w io.Writer
|
|
||||||
|
|
||||||
// payload range
|
// payload range
|
||||||
off, ln uint64
|
off, ln uint64
|
||||||
|
|
||||||
|
@ -114,9 +112,9 @@ func (n *layer) objectHead(ctx context.Context, idCnr *cid.ID, idObj *oid.ID) (*
|
||||||
return res.Head, nil
|
return res.Head, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writes payload part of the NeoFS object to the provided io.Writer.
|
// initializes payload reader of the NeoFS object.
|
||||||
// Zero range corresponds to full payload (panics if only offset is set).
|
// Zero range corresponds to full payload (panics if only offset is set).
|
||||||
func (n *layer) objectWritePayload(ctx context.Context, p getParams) error {
|
func (n *layer) initObjectPayloadReader(ctx context.Context, p getParams) (io.Reader, error) {
|
||||||
prm := PrmObjectRead{
|
prm := PrmObjectRead{
|
||||||
Container: *p.cid,
|
Container: *p.cid,
|
||||||
Object: *p.oid,
|
Object: *p.oid,
|
||||||
|
@ -127,21 +125,11 @@ func (n *layer) objectWritePayload(ctx context.Context, p getParams) error {
|
||||||
n.prepareAuthParameters(ctx, &prm.PrmAuth)
|
n.prepareAuthParameters(ctx, &prm.PrmAuth)
|
||||||
|
|
||||||
res, err := n.neoFS.ReadObject(ctx, prm)
|
res, err := n.neoFS.ReadObject(ctx, prm)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
defer res.Payload.Close()
|
return nil, n.transformNeofsError(ctx, err)
|
||||||
|
|
||||||
if p.ln == 0 {
|
|
||||||
p.ln = 4096 // configure?
|
|
||||||
}
|
|
||||||
|
|
||||||
// alloc buffer for copying
|
|
||||||
buf := make([]byte, p.ln) // sync-pool it?
|
|
||||||
|
|
||||||
// copy full payload
|
|
||||||
_, err = io.CopyBuffer(p.w, res.Payload, buf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.transformNeofsError(ctx, err)
|
return res.Payload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectGet returns an object with payload in the object.
|
// objectGet returns an object with payload in the object.
|
||||||
|
|
Loading…
Reference in a new issue