2021-04-18 15:41:13 -04:00
|
|
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
|
|
//
|
|
|
|
// This file is part of MinIO Object Storage stack
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2016-05-30 00:05:00 -04:00
|
|
|
|
2016-08-18 19:23:42 -04:00
|
|
|
package cmd
|
2016-05-30 00:05:00 -04:00
|
|
|
|
|
|
|
import (
|
2016-06-03 14:33:50 -04:00
|
|
|
"errors"
|
2019-05-14 16:49:10 -04:00
|
|
|
"reflect"
|
2016-05-30 00:05:00 -04:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Global lookup timeout.
|
|
|
|
const (
|
2020-06-17 12:52:07 -04:00
|
|
|
globalLookupTimeout = time.Minute * 30 // 30minutes.
|
|
|
|
treeWalkEntryLimit = 50
|
|
|
|
treeWalkSameEntryLimit = 4
|
2016-05-30 00:05:00 -04:00
|
|
|
)
|
|
|
|
|
2016-06-05 14:55:45 -04:00
|
|
|
// 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.
|
2016-06-03 14:33:50 -04:00
|
|
|
var errWalkAbort = errors.New("treeWalk abort")
|
|
|
|
|
2016-06-05 14:55:45 -04:00
|
|
|
// treeWalk - represents the go routine that does the file tree walk.
|
|
|
|
type treeWalk struct {
|
2020-06-17 12:52:07 -04:00
|
|
|
added time.Time
|
2019-04-17 12:52:08 -04:00
|
|
|
resultCh chan TreeWalkResult
|
2016-06-05 14:55:45 -04:00
|
|
|
endWalkCh chan struct{} // To signal when treeWalk go-routine should end.
|
|
|
|
endTimerCh chan<- struct{} // To signal when timer go-routine should end.
|
2016-05-30 00:05:00 -04:00
|
|
|
}
|
|
|
|
|
2019-04-17 12:52:08 -04:00
|
|
|
// TreeWalkPool - pool of treeWalk go routines.
|
2016-06-05 14:55:45 -04:00
|
|
|
// 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.
|
2019-04-17 12:52:08 -04:00
|
|
|
type TreeWalkPool struct {
|
2020-07-09 10:02:18 -04:00
|
|
|
mu sync.Mutex
|
2016-06-05 14:55:45 -04:00
|
|
|
pool map[listParams][]treeWalk
|
2016-05-30 00:05:00 -04:00
|
|
|
timeOut time.Duration
|
|
|
|
}
|
|
|
|
|
2019-04-17 12:52:08 -04:00
|
|
|
// NewTreeWalkPool - initialize new tree walk pool.
|
|
|
|
func NewTreeWalkPool(timeout time.Duration) *TreeWalkPool {
|
|
|
|
tPool := &TreeWalkPool{
|
2016-06-05 14:55:45 -04:00
|
|
|
pool: make(map[listParams][]treeWalk),
|
2016-05-30 00:05:00 -04:00
|
|
|
timeOut: timeout,
|
|
|
|
}
|
|
|
|
return tPool
|
|
|
|
}
|
|
|
|
|
2016-06-05 14:55:45 -04:00
|
|
|
// Release - selects a treeWalk from the pool based on the input
|
2019-04-17 12:52:08 -04:00
|
|
|
// listParams, removes it from the pool, and returns the TreeWalkResult
|
2016-06-05 14:55:45 -04:00
|
|
|
// channel.
|
2020-06-17 12:52:07 -04:00
|
|
|
// Returns nil if listParams does not have an associated treeWalk.
|
2020-05-04 01:35:40 -04:00
|
|
|
func (t *TreeWalkPool) Release(params listParams) (resultCh chan TreeWalkResult, endWalkCh chan struct{}) {
|
2020-07-09 10:02:18 -04:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2016-06-03 14:33:50 -04:00
|
|
|
walks, ok := t.pool[params] // Pick the valid walks.
|
2020-07-20 20:28:26 -04:00
|
|
|
if !ok || len(walks) == 0 {
|
|
|
|
// Release return nil if params not found.
|
|
|
|
return nil, nil
|
2016-05-30 00:05:00 -04:00
|
|
|
}
|
2020-07-20 20:28:26 -04:00
|
|
|
|
|
|
|
// 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
|
2016-05-30 00:05:00 -04:00
|
|
|
}
|
|
|
|
|
2016-06-05 14:55:45 -04:00
|
|
|
// 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.
|
2020-06-17 12:52:07 -04:00
|
|
|
// 2) Release() signals the timer go-routine to end on endTimerCh.
|
2016-06-05 14:55:45 -04:00
|
|
|
// During listing the timer should not timeout and end the treeWalk go-routine, hence the
|
|
|
|
// timer go-routine should be ended.
|
2020-05-04 01:35:40 -04:00
|
|
|
func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endWalkCh chan struct{}) {
|
2020-07-09 10:02:18 -04:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2020-06-17 12:52:07 -04:00
|
|
|
// 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.
|
2020-07-09 10:02:18 -04:00
|
|
|
e := v[0]
|
|
|
|
if e.added.Before(age) {
|
2020-06-17 12:52:07 -04:00
|
|
|
oldest = k
|
2020-07-09 10:02:18 -04:00
|
|
|
age = e.added
|
2020-06-17 12:52:07 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Invalidate and delete oldest.
|
2020-07-09 10:02:18 -04:00
|
|
|
if walks, ok := t.pool[oldest]; ok && len(walks) > 0 {
|
|
|
|
endCh := walks[0].endTimerCh
|
2020-07-20 20:28:26 -04:00
|
|
|
endWalkCh := walks[0].endWalkCh
|
2020-07-09 10:02:18 -04:00
|
|
|
if len(walks) > 1 {
|
|
|
|
// Move walks forward
|
|
|
|
copy(walks, walks[1:])
|
|
|
|
walks = walks[:len(walks)-1]
|
|
|
|
t.pool[oldest] = walks
|
2020-06-17 12:52:07 -04:00
|
|
|
} else {
|
2020-07-09 10:02:18 -04:00
|
|
|
// Only entry, just delete.
|
|
|
|
delete(t.pool, oldest)
|
2020-06-17 12:52:07 -04:00
|
|
|
}
|
2020-07-09 10:02:18 -04:00
|
|
|
select {
|
|
|
|
case endCh <- struct{}{}:
|
2020-07-20 20:28:26 -04:00
|
|
|
close(endWalkCh)
|
2020-07-09 10:02:18 -04:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Shouldn't happen, but just in case.
|
|
|
|
delete(t.pool, oldest)
|
2020-06-17 12:52:07 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-03 14:33:50 -04:00
|
|
|
// Should be a buffered channel so that Release() never blocks.
|
2016-06-05 14:55:45 -04:00
|
|
|
endTimerCh := make(chan struct{}, 1)
|
|
|
|
walkInfo := treeWalk{
|
2020-06-17 12:52:07 -04:00
|
|
|
added: UTCNow(),
|
2016-06-05 14:55:45 -04:00
|
|
|
resultCh: resultCh,
|
|
|
|
endWalkCh: endWalkCh,
|
|
|
|
endTimerCh: endTimerCh,
|
2016-06-03 14:33:50 -04:00
|
|
|
}
|
2020-07-20 20:28:26 -04:00
|
|
|
|
2016-06-03 14:33:50 -04:00
|
|
|
// Append new walk info.
|
2020-06-17 12:52:07 -04:00
|
|
|
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.
|
2020-07-09 10:02:18 -04:00
|
|
|
select {
|
|
|
|
case walks[0].endTimerCh <- struct{}{}:
|
2020-07-20 20:28:26 -04:00
|
|
|
close(walks[0].endWalkCh)
|
2020-07-09 10:02:18 -04:00
|
|
|
default:
|
|
|
|
}
|
2020-06-17 12:52:07 -04:00
|
|
|
copy(walks, walks[1:])
|
|
|
|
walks[len(walks)-1] = walkInfo
|
|
|
|
}
|
2016-05-30 00:05:00 -04:00
|
|
|
|
2016-06-05 14:55:45 -04:00
|
|
|
// Timer go-routine which times out after t.timeOut seconds.
|
2020-07-20 20:28:26 -04:00
|
|
|
go func(endTimerCh <-chan struct{}, walkInfo treeWalk) {
|
2016-05-30 00:05:00 -04:00
|
|
|
select {
|
|
|
|
// Wait until timeOut
|
|
|
|
case <-time.After(t.timeOut):
|
2016-06-05 14:55:45 -04:00
|
|
|
// Timeout has expired. Remove the treeWalk from treeWalkPool and
|
|
|
|
// end the treeWalk go-routine.
|
2020-07-09 10:02:18 -04:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
2016-06-05 14:55:45 -04:00
|
|
|
walks, ok := t.pool[params]
|
2016-06-03 14:33:50 -04:00
|
|
|
if ok {
|
2019-05-14 16:49:10 -04:00
|
|
|
// Trick of filtering without allocating
|
|
|
|
// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
|
|
|
|
nwalks := walks[:0]
|
2016-06-03 14:33:50 -04:00
|
|
|
// Look for walkInfo, remove it from the walks list.
|
2019-05-14 16:49:10 -04:00
|
|
|
for _, walk := range walks {
|
|
|
|
if !reflect.DeepEqual(walk, walkInfo) {
|
|
|
|
nwalks = append(nwalks, walk)
|
2016-06-03 14:33:50 -04:00
|
|
|
}
|
|
|
|
}
|
2019-05-14 16:49:10 -04:00
|
|
|
if len(nwalks) == 0 {
|
2016-06-05 14:55:45 -04:00
|
|
|
// No more treeWalk go-routines associated with listParams
|
|
|
|
// hence remove map entry.
|
2016-06-03 14:33:50 -04:00
|
|
|
delete(t.pool, params)
|
2016-06-05 14:55:45 -04:00
|
|
|
} else {
|
|
|
|
// There are more treeWalk go-routines associated with listParams
|
|
|
|
// hence save the list in the map.
|
2019-05-14 16:49:10 -04:00
|
|
|
t.pool[params] = nwalks
|
2016-06-03 14:33:50 -04:00
|
|
|
}
|
2016-05-30 00:05:00 -04:00
|
|
|
}
|
2016-06-05 14:55:45 -04:00
|
|
|
// Signal the treeWalk go-routine to die.
|
|
|
|
close(endWalkCh)
|
|
|
|
case <-endTimerCh:
|
2016-05-30 00:05:00 -04:00
|
|
|
return
|
|
|
|
}
|
2020-07-20 20:28:26 -04:00
|
|
|
}(endTimerCh, walkInfo)
|
2016-05-30 00:05:00 -04:00
|
|
|
}
|