Skip to content

Commit 8d3ac08

Browse files
hexadecyharshavardhana
authored andcommitted
#593 fix GetObject seek to support http.ServeContent (#599)
* #593 fix GetObject seek to support http.ServeContent * Enhance unit test for object Seeker
1 parent 0660977 commit 8d3ac08

File tree

2 files changed

+99
-87
lines changed

2 files changed

+99
-87
lines changed

api-get-object.go

+14-23
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,12 @@ type Object struct {
212212
reqCh chan<- getRequest
213213
resCh <-chan getResponse
214214
doneCh chan<- struct{}
215-
prevOffset int64
216215
currOffset int64
217216
objectInfo ObjectInfo
218217

218+
// Ask lower level to initiate data fetching based on currOffset
219+
seekData bool
220+
219221
// Keeps track of closed call.
220222
isClosed bool
221223

@@ -258,6 +260,10 @@ func (o *Object) doGetRequest(request getRequest) (getResponse, error) {
258260
if response.Error != nil {
259261
return response, response.Error
260262
}
263+
264+
// Data are ready on the wire, no need to reinitiate connection in lower level
265+
o.seekData = false
266+
261267
return response, nil
262268
}
263269

@@ -266,8 +272,6 @@ func (o *Object) doGetRequest(request getRequest) (getResponse, error) {
266272
func (o *Object) setOffset(bytesRead int64) error {
267273
// Update the currentOffset.
268274
o.currOffset += bytesRead
269-
// Save the current offset as previous offset.
270-
o.prevOffset = o.currOffset
271275

272276
if o.currOffset >= o.objectInfo.Size {
273277
return io.EOF
@@ -303,22 +307,9 @@ func (o *Object) Read(b []byte) (n int, err error) {
303307
readReq.isFirstReq = true
304308
}
305309

306-
// Verify if offset has changed and currOffset is greater than
307-
// previous offset. Perhaps due to Seek().
308-
offsetChange := o.prevOffset - o.currOffset
309-
if offsetChange < 0 {
310-
offsetChange = -offsetChange
311-
}
312-
if offsetChange > 0 {
313-
// Fetch the new reader at the current offset again.
314-
readReq.Offset = o.currOffset
315-
readReq.DidOffsetChange = true
316-
} else {
317-
// No offset changes no need to fetch new reader, continue
318-
// reading.
319-
readReq.DidOffsetChange = false
320-
readReq.Offset = 0
321-
}
310+
// Ask to establish a new data fetch routine based on seekData flag
311+
readReq.DidOffsetChange = o.seekData
312+
readReq.Offset = o.currOffset
322313

323314
// Send and receive from the first request.
324315
response, err := o.doGetRequest(readReq)
@@ -430,8 +421,6 @@ func (o *Object) ReadAt(b []byte, offset int64) (n int, err error) {
430421
if !o.objectInfoSet {
431422
// Update the currentOffset.
432423
o.currOffset += bytesRead
433-
// Save the current offset as previous offset.
434-
o.prevOffset = o.currOffset
435424
} else {
436425
// If this was not the first request update
437426
// the offsets and compare against objectInfo
@@ -492,8 +481,6 @@ func (o *Object) Seek(offset int64, whence int) (n int64, err error) {
492481
return 0, err
493482
}
494483
}
495-
// Save current offset as previous offset.
496-
o.prevOffset = o.currOffset
497484

498485
// Switch through whence.
499486
switch whence {
@@ -527,6 +514,10 @@ func (o *Object) Seek(offset int64, whence int) (n int64, err error) {
527514
if o.prevErr == io.EOF {
528515
o.prevErr = nil
529516
}
517+
518+
// Ask lower level to fetch again from source
519+
o.seekData = true
520+
530521
// Return the effective offset.
531522
return o.currOffset, nil
532523
}

api_functional_v4_test.go

+85-64
Original file line numberDiff line numberDiff line change
@@ -1255,6 +1255,7 @@ func TestGetObjectReadSeekFunctional(t *testing.T) {
12551255

12561256
// Generate data more than 32K
12571257
buf := bytes.Repeat([]byte("2"), rand.Intn(1<<20)+32*1024)
1258+
bufSize := len(buf)
12581259

12591260
// Save the data
12601261
objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "")
@@ -1263,10 +1264,21 @@ func TestGetObjectReadSeekFunctional(t *testing.T) {
12631264
t.Fatal("Error:", err, bucketName, objectName)
12641265
}
12651266

1266-
if n != int64(len(buf)) {
1267+
if n != int64(bufSize) {
12671268
t.Fatalf("Error: number of bytes does not match, want %v, got %v\n", len(buf), n)
12681269
}
12691270

1271+
defer func() {
1272+
err = c.RemoveObject(bucketName, objectName)
1273+
if err != nil {
1274+
t.Fatal("Error: ", err)
1275+
}
1276+
err = c.RemoveBucket(bucketName)
1277+
if err != nil {
1278+
t.Fatal("Error:", err)
1279+
}
1280+
}()
1281+
12701282
// Read the data back
12711283
r, err := c.GetObject(bucketName, objectName)
12721284
if err != nil {
@@ -1277,77 +1289,86 @@ func TestGetObjectReadSeekFunctional(t *testing.T) {
12771289
if err != nil {
12781290
t.Fatal("Error:", err, bucketName, objectName)
12791291
}
1280-
if st.Size != int64(len(buf)) {
1292+
if st.Size != int64(bufSize) {
12811293
t.Fatalf("Error: number of bytes in stat does not match, want %v, got %v\n",
12821294
len(buf), st.Size)
12831295
}
12841296

1285-
offset := int64(2048)
1286-
n, err = r.Seek(offset, 0)
1287-
if err != nil {
1288-
t.Fatal("Error:", err, offset)
1289-
}
1290-
if n != offset {
1291-
t.Fatalf("Error: number of bytes seeked does not match, want %v, got %v\n",
1292-
offset, n)
1293-
}
1294-
n, err = r.Seek(0, 1)
1295-
if err != nil {
1296-
t.Fatal("Error:", err)
1297-
}
1298-
if n != offset {
1299-
t.Fatalf("Error: number of current seek does not match, want %v, got %v\n",
1300-
offset, n)
1301-
}
1302-
_, err = r.Seek(offset, 2)
1303-
if err == nil {
1304-
t.Fatal("Error: seek on positive offset for whence '2' should error out")
1305-
}
1306-
n, err = r.Seek(-offset, 2)
1307-
if err != nil {
1308-
t.Fatal("Error:", err)
1309-
}
1310-
if n != st.Size-offset {
1311-
t.Fatalf("Error: number of bytes seeked back does not match, want %d, got %v\n", st.Size-offset, n)
1312-
}
1313-
1314-
var buffer1 bytes.Buffer
1315-
if _, err = io.CopyN(&buffer1, r, st.Size); err != nil {
1316-
if err != io.EOF {
1317-
t.Fatal("Error:", err)
1297+
// This following function helps us to compare data from the reader after seek
1298+
// with the data from the original buffer
1299+
cmpData := func(r io.Reader, start, end int) {
1300+
if end-start == 0 {
1301+
return
13181302
}
1319-
}
1320-
if !bytes.Equal(buf[len(buf)-int(offset):], buffer1.Bytes()) {
1321-
t.Fatal("Error: Incorrect read bytes v/s original buffer.")
1322-
}
1323-
1324-
// Seek again and read again.
1325-
n, err = r.Seek(offset-1, 0)
1326-
if err != nil {
1327-
t.Fatal("Error:", err)
1328-
}
1329-
if n != (offset - 1) {
1330-
t.Fatalf("Error: number of bytes seeked back does not match, want %v, got %v\n", offset-1, n)
1331-
}
1332-
1333-
var buffer2 bytes.Buffer
1334-
if _, err = io.CopyN(&buffer2, r, st.Size); err != nil {
1335-
if err != io.EOF {
1336-
t.Fatal("Error:", err)
1303+
buffer := bytes.NewBuffer([]byte{})
1304+
if _, err := io.CopyN(buffer, r, int64(bufSize)); err != nil {
1305+
if err != io.EOF {
1306+
t.Fatal("Error:", err)
1307+
}
1308+
}
1309+
if !bytes.Equal(buf[start:end], buffer.Bytes()) {
1310+
t.Fatal("Error: Incorrect read bytes v/s original buffer.")
13371311
}
1338-
}
1339-
// Verify now lesser bytes.
1340-
if !bytes.Equal(buf[2047:], buffer2.Bytes()) {
1341-
t.Fatal("Error: Incorrect read bytes v/s original buffer.")
13421312
}
13431313

1344-
err = c.RemoveObject(bucketName, objectName)
1345-
if err != nil {
1346-
t.Fatal("Error: ", err)
1347-
}
1348-
err = c.RemoveBucket(bucketName)
1349-
if err != nil {
1350-
t.Fatal("Error:", err)
1314+
// Generic seek error for errors other than io.EOF
1315+
seekErr := errors.New("seek error")
1316+
1317+
testCases := []struct {
1318+
offset int64
1319+
whence int
1320+
pos int64
1321+
err error
1322+
shouldCmp bool
1323+
start int
1324+
end int
1325+
}{
1326+
// Start from offset 0, fetch data and compare
1327+
{0, 0, 0, nil, true, 0, 0},
1328+
// Start from offset 2048, fetch data and compare
1329+
{2048, 0, 2048, nil, true, 2048, bufSize},
1330+
// Start from offset larger than possible
1331+
{int64(bufSize) + 1024, 0, 0, seekErr, false, 0, 0},
1332+
// Move to offset 0 without comparing
1333+
{0, 0, 0, nil, false, 0, 0},
1334+
// Move one step forward and compare
1335+
{1, 1, 1, nil, true, 1, bufSize},
1336+
// Move larger than possible
1337+
{int64(bufSize), 1, 0, seekErr, false, 0, 0},
1338+
// Provide negative offset with CUR_SEEK
1339+
{int64(-1), 1, 0, seekErr, false, 0, 0},
1340+
// Test with whence SEEK_END and with positive offset
1341+
{1024, 2, int64(bufSize) - 1024, io.EOF, true, 0, 0},
1342+
// Test with whence SEEK_END and with negative offset
1343+
{-1024, 2, int64(bufSize) - 1024, nil, true, bufSize - 1024, bufSize},
1344+
// Test with whence SEEK_END and with large negative offset
1345+
{-int64(bufSize) * 2, 2, 0, seekErr, true, 0, 0},
1346+
}
1347+
1348+
for i, testCase := range testCases {
1349+
// Perform seek operation
1350+
n, err := r.Seek(testCase.offset, testCase.whence)
1351+
// We expect an error
1352+
if testCase.err == seekErr && err == nil {
1353+
t.Fatalf("Test %d, unexpected err value: expected: %v, found: %v", i+1, testCase.err, err)
1354+
}
1355+
// We expect a specific error
1356+
if testCase.err != seekErr && testCase.err != err {
1357+
t.Fatalf("Test %d, unexpected err value: expected: %v, found: %v", i+1, testCase.err, err)
1358+
}
1359+
// If we expect an error go to the next loop
1360+
if testCase.err != nil {
1361+
continue
1362+
}
1363+
// Check the returned seek pos
1364+
if n != testCase.pos {
1365+
t.Fatalf("Test %d, error: number of bytes seeked does not match, want %v, got %v\n", i+1,
1366+
testCase.pos, n)
1367+
}
1368+
// Compare only if shouldCmp is activated
1369+
if testCase.shouldCmp {
1370+
cmpData(r, testCase.start, testCase.end)
1371+
}
13511372
}
13521373
}
13531374

0 commit comments

Comments
 (0)