mirror of
https://github.com/minio/minio.git
synced 2024-12-25 06:35:56 -05:00
1a1f00fa15
Similar to #10775 for fewer memory allocations, since we use getOnlineDisks() extensively for listing we should optimize it further. Additionally, remove all unused walkers from the storage layer
212 lines
6.1 KiB
Go
212 lines
6.1 KiB
Go
/*
|
|
* MinIO Cloud Storage, (C) 2016-2020 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 (
|
|
"errors"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Global lookup timeout.
|
|
const (
|
|
globalLookupTimeout = time.Minute * 30 // 30minutes.
|
|
treeWalkEntryLimit = 50
|
|
treeWalkSameEntryLimit = 4
|
|
)
|
|
|
|
// listParams - list object params used for list object map
|
|
type listParams struct {
|
|
bucket string
|
|
recursive bool
|
|
marker string
|
|
prefix string
|
|
}
|
|
|
|
// errWalkAbort - returned by doTreeWalk() if it returns prematurely.
|
|
// doTreeWalk() can return prematurely if
|
|
// 1) treeWalk is timed out by the timer go-routine.
|
|
// 2) there is an error during tree walk.
|
|
var errWalkAbort = errors.New("treeWalk abort")
|
|
|
|
// treeWalk - represents the go routine that does the file tree walk.
|
|
type treeWalk struct {
|
|
added time.Time
|
|
resultCh chan TreeWalkResult
|
|
endWalkCh chan struct{} // To signal when treeWalk go-routine should end.
|
|
endTimerCh chan<- struct{} // To signal when timer go-routine should end.
|
|
}
|
|
|
|
// TreeWalkPool - pool of treeWalk go routines.
|
|
// A treeWalk is added to the pool by Set() and removed either by
|
|
// doing a Release() or if the concerned timer goes off.
|
|
// treeWalkPool's purpose is to maintain active treeWalk go-routines in a map so that
|
|
// it can be looked up across related list calls.
|
|
type TreeWalkPool struct {
|
|
mu sync.Mutex
|
|
pool map[listParams][]treeWalk
|
|
timeOut time.Duration
|
|
}
|
|
|
|
// NewTreeWalkPool - initialize new tree walk pool.
|
|
func NewTreeWalkPool(timeout time.Duration) *TreeWalkPool {
|
|
tPool := &TreeWalkPool{
|
|
pool: make(map[listParams][]treeWalk),
|
|
timeOut: timeout,
|
|
}
|
|
return tPool
|
|
}
|
|
|
|
// Release - selects a treeWalk from the pool based on the input
|
|
// listParams, removes it from the pool, and returns the TreeWalkResult
|
|
// channel.
|
|
// Returns nil if listParams does not have an associated treeWalk.
|
|
func (t *TreeWalkPool) Release(params listParams) (resultCh chan TreeWalkResult, endWalkCh chan struct{}) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
walks, ok := t.pool[params] // Pick the valid walks.
|
|
if !ok || len(walks) == 0 {
|
|
// Release return nil if params not found.
|
|
return nil, nil
|
|
}
|
|
|
|
// 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.resultCh, walk.endWalkCh
|
|
}
|
|
|
|
// Set - adds a treeWalk to the treeWalkPool.
|
|
// Also starts a timer go-routine that ends when:
|
|
// 1) time.After() expires after t.timeOut seconds.
|
|
// The expiration is needed so that the treeWalk go-routine resources are freed after a timeout
|
|
// if the S3 client does only partial listing of objects.
|
|
// 2) Release() signals the timer go-routine to end on endTimerCh.
|
|
// During listing the timer should not timeout and end the treeWalk go-routine, hence the
|
|
// timer go-routine should be ended.
|
|
func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endWalkCh chan struct{}) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
// If we are above the limit delete at least one entry from the pool.
|
|
if len(t.pool) > treeWalkEntryLimit {
|
|
age := time.Now()
|
|
var oldest listParams
|
|
for k, v := range t.pool {
|
|
if len(v) == 0 {
|
|
delete(t.pool, k)
|
|
continue
|
|
}
|
|
// The first element is the oldest, so we only check that.
|
|
e := v[0]
|
|
if e.added.Before(age) {
|
|
oldest = k
|
|
age = e.added
|
|
}
|
|
}
|
|
// Invalidate and delete oldest.
|
|
if walks, ok := t.pool[oldest]; ok && len(walks) > 0 {
|
|
endCh := walks[0].endTimerCh
|
|
endWalkCh := walks[0].endWalkCh
|
|
if len(walks) > 1 {
|
|
// Move walks forward
|
|
copy(walks, walks[1:])
|
|
walks = walks[:len(walks)-1]
|
|
t.pool[oldest] = walks
|
|
} else {
|
|
// Only entry, just delete.
|
|
delete(t.pool, oldest)
|
|
}
|
|
select {
|
|
case endCh <- struct{}{}:
|
|
close(endWalkCh)
|
|
default:
|
|
}
|
|
} else {
|
|
// Shouldn't happen, but just in case.
|
|
delete(t.pool, oldest)
|
|
}
|
|
}
|
|
|
|
// Should be a buffered channel so that Release() never blocks.
|
|
endTimerCh := make(chan struct{}, 1)
|
|
walkInfo := treeWalk{
|
|
added: UTCNow(),
|
|
resultCh: resultCh,
|
|
endWalkCh: endWalkCh,
|
|
endTimerCh: endTimerCh,
|
|
}
|
|
|
|
// Append new walk info.
|
|
walks := t.pool[params]
|
|
if len(walks) < treeWalkSameEntryLimit {
|
|
t.pool[params] = append(walks, walkInfo)
|
|
} else {
|
|
// We are at limit, invalidate oldest, move list down and add new as last.
|
|
select {
|
|
case walks[0].endTimerCh <- struct{}{}:
|
|
close(walks[0].endWalkCh)
|
|
default:
|
|
}
|
|
copy(walks, walks[1:])
|
|
walks[len(walks)-1] = walkInfo
|
|
}
|
|
|
|
// Timer go-routine which times out after t.timeOut seconds.
|
|
go func(endTimerCh <-chan struct{}, walkInfo treeWalk) {
|
|
select {
|
|
// Wait until timeOut
|
|
case <-time.After(t.timeOut):
|
|
// Timeout has expired. Remove the treeWalk from treeWalkPool and
|
|
// end the treeWalk go-routine.
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
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 treeWalk go-routines associated with listParams
|
|
// hence remove map entry.
|
|
delete(t.pool, params)
|
|
} else {
|
|
// There are more treeWalk go-routines associated with listParams
|
|
// hence save the list in the map.
|
|
t.pool[params] = nwalks
|
|
}
|
|
}
|
|
// Signal the treeWalk go-routine to die.
|
|
close(endWalkCh)
|
|
case <-endTimerCh:
|
|
return
|
|
}
|
|
}(endTimerCh, walkInfo)
|
|
}
|