[#149] client: Fill buffer completely when reading stream data

In previous implementation all `Read` methods read single response
per-call, so buffer could be incompletely filled w/o an error. In order
to follow `io.Reader` docs we need to continue filling the buffer while
it is possible.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-02-24 14:33:43 +03:00 committed by LeL
parent 8e99e8a818
commit 2adbe29f7f
2 changed files with 83 additions and 60 deletions

View file

@ -166,14 +166,20 @@ func (x *ObjectReader) readChunk(buf []byte) (int, bool) {
return read, true return read, true
} }
var ok bool
var part v2object.GetObjectPart
var chunk []byte
var lastRead int
for {
// receive next message // receive next message
ok := x.ctxCall.readResponse() ok = x.ctxCall.readResponse()
if !ok { if !ok {
return read, false return read, false
} }
// get chunk part message // get chunk part message
part := x.bodyResp.GetObjectPart() part = x.bodyResp.GetObjectPart()
var partChunk *v2object.GetObjectPartChunk var partChunk *v2object.GetObjectPartChunk
@ -184,17 +190,24 @@ func (x *ObjectReader) readChunk(buf []byte) (int, bool) {
} }
// read new chunk // read new chunk
chunk := partChunk.GetChunk() chunk = partChunk.GetChunk()
if len(chunk) == 0 {
// just skip empty chunks since they are not prohibited by protocol
continue
}
tailOffset := copy(buf[read:], chunk) lastRead = copy(buf[read:], chunk)
read += tailOffset read += lastRead
if read == len(buf) {
// save the tail // save the tail
x.tailPayload = append(x.tailPayload, chunk[tailOffset:]...) x.tailPayload = append(x.tailPayload, chunk[lastRead:]...)
return read, true return read, true
} }
}
}
// ReadChunk reads another chunk of the object payload. Works similar to // ReadChunk reads another chunk of the object payload. Works similar to
// io.Reader.Read but returns success flag instead of error. // io.Reader.Read but returns success flag instead of error.
@ -593,15 +606,19 @@ func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) {
return read, true return read, true
} }
var ok bool
var partChunk *v2object.GetRangePartChunk
var chunk []byte
var lastRead int
for {
// receive next message // receive next message
ok := x.ctxCall.readResponse() ok = x.ctxCall.readResponse()
if !ok { if !ok {
return read, false return read, false
} }
// get chunk message // get chunk message
var partChunk *v2object.GetRangePartChunk
switch v := x.bodyResp.GetRangePart().(type) { switch v := x.bodyResp.GetRangePart().(type) {
default: default:
x.ctxCall.err = fmt.Errorf("unexpected message received: %T", v) x.ctxCall.err = fmt.Errorf("unexpected message received: %T", v)
@ -613,18 +630,24 @@ func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) {
partChunk = v partChunk = v
} }
// read new chunk chunk = partChunk.GetChunk()
chunk := partChunk.GetChunk() if len(chunk) == 0 {
// just skip empty chunks since they are not prohibited by protocol
continue
}
tailOffset := copy(buf[read:], chunk) lastRead = copy(buf[read:], chunk)
read += tailOffset read += lastRead
if read == len(buf) {
// save the tail // save the tail
x.tailPayload = append(x.tailPayload, chunk[tailOffset:]...) x.tailPayload = append(x.tailPayload, chunk[lastRead:]...)
return read, true return read, true
} }
}
}
// ReadChunk reads another chunk of the object payload range. // ReadChunk reads another chunk of the object payload range.
// Works similar to io.Reader.Read but returns success flag instead of error. // Works similar to io.Reader.Read but returns success flag instead of error.

View file

@ -166,14 +166,14 @@ func (x *ObjectListReader) Read(buf []oid.ID) (int, bool) {
read += ln read += ln
// save the tail and break if read == len(buf) {
// save the tail
x.tail = append(x.tail, ids[ln:]...) x.tail = append(x.tail, ids[ln:]...)
break
}
return read, true return read, true
} }
}
}
// Close ends reading list of the matched objects and returns the result of the operation // Close ends reading list of the matched objects and returns the result of the operation
// along with the final results. Must be called after using the ObjectListReader. // along with the final results. Must be called after using the ObjectListReader.