Skip to content

Commit

Permalink
ReadAt should behave appropriately at io.EOF (#1138)
Browse files Browse the repository at this point in the history
Fixes #1137
  • Loading branch information
kimmelserj authored and harshavardhana committed Jul 9, 2019
1 parent 6898080 commit 26dec1a
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 6 deletions.
14 changes: 8 additions & 6 deletions api-get-object.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ func (o *Object) Read(b []byte) (n int, err error) {
if o.prevErr != nil || o.isClosed {
return 0, o.prevErr
}

// Create a new request.
readReq := getRequest{
isReadOp: true,
Expand Down Expand Up @@ -403,10 +404,13 @@ func (o *Object) ReadAt(b []byte, offset int64) (n int, err error) {
defer o.mutex.Unlock()

// prevErr is error which was saved in previous operation.
if o.prevErr != nil || o.isClosed {
if o.prevErr != nil && o.prevErr != io.EOF || o.isClosed {
return 0, o.prevErr
}

// Set the current offset to ReadAt offset, because the current offset will be shifted at the end of this method.
o.currOffset = offset

// Can only compare offsets to size when size has been set.
if o.objectInfoSet {
// If offset is negative than we return io.EOF.
Expand Down Expand Up @@ -476,11 +480,9 @@ func (o *Object) Seek(offset int64, whence int) (n int64, err error) {
o.mutex.Lock()
defer o.mutex.Unlock()

if o.prevErr != nil {
// At EOF seeking is legal allow only io.EOF, for any other errors we return.
if o.prevErr != io.EOF {
return 0, o.prevErr
}
// At EOF seeking is legal allow only io.EOF, for any other errors we return.
if o.prevErr != nil && o.prevErr != io.EOF {
return 0, o.prevErr
}

// Negative offset is valid for whence of '2'.
Expand Down
128 changes: 128 additions & 0 deletions functional_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -2226,6 +2226,133 @@ func testGetObjectReadAtFunctional() {
successLogger(testName, function, args, startTime).Info()
}

// Reproduces issue https://github.com/minio/minio-go/issues/1137
func testGetObjectReadAtWhenEOFWasReached() {
// initialize logging params
startTime := time.Now()
testName := getFuncName()
function := "GetObject(bucketName, objectName)"
args := map[string]interface{}{}

// Seed random based on current time.
rand.Seed(time.Now().Unix())

// Instantiate new minio client object.
c, err := minio.New(
os.Getenv(serverEndpoint),
os.Getenv(accessKey),
os.Getenv(secretKey),
mustParseBool(os.Getenv(enableHTTPS)),
)
if err != nil {
logError(testName, function, args, startTime, "", "MinIO client object creation failed", err)
return
}

// Enable tracing, write to stderr.
// c.TraceOn(os.Stderr)

// Set user agent.
c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0")

// Generate a new random bucket name.
bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
args["bucketName"] = bucketName

// Make a new bucket.
err = c.MakeBucket(bucketName, "us-east-1")
if err != nil {
logError(testName, function, args, startTime, "", "MakeBucket failed", err)
return
}

// Generate 33K of data.
bufSize := dataFileMap["datafile-33-kB"]
var reader = getDataReader("datafile-33-kB")
defer reader.Close()

objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
args["objectName"] = objectName

buf, err := ioutil.ReadAll(reader)
if err != nil {
logError(testName, function, args, startTime, "", "ReadAll failed", err)
return
}

// Save the data
n, err := c.PutObject(bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"})
if err != nil {
logError(testName, function, args, startTime, "", "PutObject failed", err)
return
}

if n != int64(bufSize) {
logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(int64(bufSize))+", got "+string(n), err)
return
}

// read the data back
r, err := c.GetObject(bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
logError(testName, function, args, startTime, "", "PutObject failed", err)
return
}

// read directly
buf1 := make([]byte, n)
buf2 := make([]byte, 512)

m, err := r.Read(buf1)
if err != nil {
if err != io.EOF {
logError(testName, function, args, startTime, "", "Read failed", err)
return
}
}
if m != len(buf1) {
logError(testName, function, args, startTime, "", "Read read shorter bytes before reaching EOF, expected "+string(len(buf1))+", got "+string(m), err)
return
}
if !bytes.Equal(buf1, buf) {
logError(testName, function, args, startTime, "", "Incorrect count of Read data", err)
return
}

st, err := r.Stat()
if err != nil {
logError(testName, function, args, startTime, "", "Stat failed", err)
return
}

if st.Size != int64(bufSize) {
logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err)
return
}

m, err = r.ReadAt(buf2, 512)
if err != nil {
logError(testName, function, args, startTime, "", "ReadAt failed", err)
return
}
if m != len(buf2) {
logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf2))+", got "+string(m), err)
return
}
if !bytes.Equal(buf2, buf[512:1024]) {
logError(testName, function, args, startTime, "", "Incorrect count of ReadAt data", err)
return
}

// Delete all objects and buckets
if err = cleanupBucket(bucketName, c); err != nil {
logError(testName, function, args, startTime, "", "Cleanup failed", err)
return
}

successLogger(testName, function, args, startTime).Info()
}

// Test Presigned Post Policy
func testPresignedPostPolicy() {
// initialize logging params
Expand Down Expand Up @@ -10004,6 +10131,7 @@ func main() {
testFPutObject()
testGetObjectReadSeekFunctional()
testGetObjectReadAtFunctional()
testGetObjectReadAtWhenEOFWasReached()
testPresignedPostPolicy()
testCopyObject()
testComposeObjectErrorCases()
Expand Down

0 comments on commit 26dec1a

Please sign in to comment.