Offload listing to posix layer (#7611)
This PR adds one API WalkCh which sorts and sends list over the network Each disk walks independently in a sorted manner.master
parent
a343d14f19
commit
b3f22eac56
@ -0,0 +1,143 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package cmd |
||||
|
||||
import ( |
||||
"reflect" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
globalMergeLookupTimeout = time.Minute * 1 // 1 minutes.
|
||||
) |
||||
|
||||
// mergeWalk - represents the go routine that does the merge walk.
|
||||
type mergeWalk struct { |
||||
entryChs []FileInfoCh |
||||
endWalkCh chan struct{} // To signal when mergeWalk go-routine should end.
|
||||
endTimerCh chan<- struct{} // To signal when timer go-routine should end.
|
||||
} |
||||
|
||||
// MergeWalkPool - pool of mergeWalk go routines.
|
||||
// A mergeWalk is added to the pool by Set() and removed either by
|
||||
// doing a Release() or if the concerned timer goes off.
|
||||
// mergeWalkPool's purpose is to maintain active mergeWalk go-routines in a map so that
|
||||
// it can be looked up across related list calls.
|
||||
type MergeWalkPool struct { |
||||
pool map[listParams][]mergeWalk |
||||
timeOut time.Duration |
||||
lock *sync.Mutex |
||||
} |
||||
|
||||
// NewMergeWalkPool - initialize new tree walk pool.
|
||||
func NewMergeWalkPool(timeout time.Duration) *MergeWalkPool { |
||||
tPool := &MergeWalkPool{ |
||||
pool: make(map[listParams][]mergeWalk), |
||||
timeOut: timeout, |
||||
lock: &sync.Mutex{}, |
||||
} |
||||
return tPool |
||||
} |
||||
|
||||
// Release - selects a mergeWalk from the pool based on the input
|
||||
// listParams, removes it from the pool, and returns the MergeWalkResult
|
||||
// channel.
|
||||
// Returns nil if listParams does not have an asccociated mergeWalk.
|
||||
func (t MergeWalkPool) Release(params listParams) ([]FileInfoCh, chan struct{}) { |
||||
t.lock.Lock() |
||||
defer t.lock.Unlock() |
||||
walks, ok := t.pool[params] // Pick the valid walks.
|
||||
if ok { |
||||
if len(walks) > 0 { |
||||
// Pop out the first valid walk entry.
|
||||
walk := walks[0] |
||||
walks = walks[1:] |
||||
if len(walks) > 0 { |
||||
t.pool[params] = walks |
||||
} else { |
||||
delete(t.pool, params) |
||||
} |
||||
walk.endTimerCh <- struct{}{} |
||||
return walk.entryChs, walk.endWalkCh |
||||
} |
||||
} |
||||
// Release return nil if params not found.
|
||||
return nil, nil |
||||
} |
||||
|
||||
// Set - adds a mergeWalk to the mergeWalkPool.
|
||||
// Also starts a timer go-routine that ends when:
|
||||
// 1) time.After() expires after t.timeOut seconds.
|
||||
// The expiration is needed so that the mergeWalk go-routine resources are freed after a timeout
|
||||
// if the S3 client does only partial listing of objects.
|
||||
// 2) Relase() signals the timer go-routine to end on endTimerCh.
|
||||
// During listing the timer should not timeout and end the mergeWalk go-routine, hence the
|
||||
// timer go-routine should be ended.
|
||||
func (t MergeWalkPool) Set(params listParams, resultChs []FileInfoCh, endWalkCh chan struct{}) { |
||||
t.lock.Lock() |
||||
defer t.lock.Unlock() |
||||
|
||||
// Should be a buffered channel so that Release() never blocks.
|
||||
endTimerCh := make(chan struct{}, 1) |
||||
|
||||
walkInfo := mergeWalk{ |
||||
entryChs: resultChs, |
||||
endWalkCh: endWalkCh, |
||||
endTimerCh: endTimerCh, |
||||
} |
||||
|
||||
// Append new walk info.
|
||||
t.pool[params] = append(t.pool[params], walkInfo) |
||||
|
||||
// Timer go-routine which times out after t.timeOut seconds.
|
||||
go func(endTimerCh <-chan struct{}, walkInfo mergeWalk) { |
||||
select { |
||||
// Wait until timeOut
|
||||
case <-time.After(t.timeOut): |
||||
// Timeout has expired. Remove the mergeWalk from mergeWalkPool and
|
||||
// end the mergeWalk go-routine.
|
||||
t.lock.Lock() |
||||
walks, ok := t.pool[params] |
||||
if ok { |
||||
// Trick of filtering without allocating
|
||||
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
|
||||
nwalks := walks[:0] |
||||
// Look for walkInfo, remove it from the walks list.
|
||||
for _, walk := range walks { |
||||
if !reflect.DeepEqual(walk, walkInfo) { |
||||
nwalks = append(nwalks, walk) |
||||
} |
||||
} |
||||
if len(nwalks) == 0 { |
||||
// No more mergeWalk go-routines associated with listParams
|
||||
// hence remove map entry.
|
||||
delete(t.pool, params) |
||||
} else { |
||||
// There are more mergeWalk go-routines associated with listParams
|
||||
// hence save the list in the map.
|
||||
t.pool[params] = nwalks |
||||
} |
||||
} |
||||
// Signal the mergeWalk go-routine to die.
|
||||
close(endWalkCh) |
||||
t.lock.Unlock() |
||||
case <-endTimerCh: |
||||
return |
||||
} |
||||
}(endTimerCh, walkInfo) |
||||
} |
@ -0,0 +1,103 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2019 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package cmd |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
// Test if tree walker go-routine is removed from the pool after timeout
|
||||
// and that is available in the pool before the timeout.
|
||||
func TestMergeWalkPoolBasic(t *testing.T) { |
||||
// Create a treeWalkPool
|
||||
tw := NewMergeWalkPool(1 * time.Second) |
||||
|
||||
// Create sample params
|
||||
params := listParams{ |
||||
bucket: "test-bucket", |
||||
} |
||||
|
||||
endWalkCh := make(chan struct{}) |
||||
// Add a treeWalk to the pool
|
||||
tw.Set(params, []FileInfoCh{}, endWalkCh) |
||||
|
||||
// Wait for treeWalkPool timeout to happen
|
||||
<-time.After(2 * time.Second) |
||||
if c1, _ := tw.Release(params); c1 != nil { |
||||
t.Error("treeWalk go-routine must have been freed") |
||||
} |
||||
|
||||
// Add the treeWalk back to the pool
|
||||
endWalkCh = make(chan struct{}) |
||||
tw.Set(params, []FileInfoCh{}, endWalkCh) |
||||
|
||||
// Release the treeWalk before timeout
|
||||
select { |
||||
case <-time.After(1 * time.Second): |
||||
break |
||||
default: |
||||
if c1, _ := tw.Release(params); c1 == nil { |
||||
t.Error("treeWalk go-routine got freed before timeout") |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Test if multiple merge walkers for the same listParams are managed as expected by the pool.
|
||||
func TestManyMergeWalksSameParam(t *testing.T) { |
||||
// Create a treeWalkPool.
|
||||
tw := NewMergeWalkPool(5 * time.Second) |
||||
|
||||
// Create sample params.
|
||||
params := listParams{ |
||||
bucket: "test-bucket", |
||||
} |
||||
|
||||
select { |
||||
// This timeout is an upper-bound. This is started
|
||||
// before the first treeWalk go-routine's timeout period starts.
|
||||
case <-time.After(5 * time.Second): |
||||
break |
||||
default: |
||||
// Create many treeWalk go-routines for the same params.
|
||||
for i := 0; i < 10; i++ { |
||||
endWalkCh := make(chan struct{}) |
||||
walkChs := make([]FileInfoCh, 0) |
||||
tw.Set(params, walkChs, endWalkCh) |
||||
} |
||||
|
||||
tw.lock.Lock() |
||||
if walks, ok := tw.pool[params]; ok { |
||||
if len(walks) != 10 { |
||||
t.Error("There aren't as many walks as were Set") |
||||
} |
||||
} |
||||
tw.lock.Unlock() |
||||
for i := 0; i < 10; i++ { |
||||
tw.lock.Lock() |
||||
if walks, ok := tw.pool[params]; ok { |
||||
// Before ith Release we should have 10-i treeWalk go-routines.
|
||||
if 10-i != len(walks) { |
||||
t.Error("There aren't as many walks as were Set") |
||||
} |
||||
} |
||||
tw.lock.Unlock() |
||||
tw.Release(params) |
||||
} |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue