mirror of
https://github.com/minio/minio.git
synced 2025-11-24 19:46:16 -05:00
Put an upper limit on walk pool sizes (#9848)
Fixes potentially infinite allocations, especially in FS mode, since lookups live up to 30 minutes. Limit walk pool sizes to 50 max parameter entries and 4 concurrent operations with the same parameters. Fixes #9835
This commit is contained in:
@@ -25,7 +25,9 @@ import (
|
||||
|
||||
// Global lookup timeout.
|
||||
const (
|
||||
globalLookupTimeout = time.Minute * 30 // 30minutes.
|
||||
globalLookupTimeout = time.Minute * 30 // 30minutes.
|
||||
treeWalkEntryLimit = 50
|
||||
treeWalkSameEntryLimit = 4
|
||||
)
|
||||
|
||||
// listParams - list object params used for list object map
|
||||
@@ -44,6 +46,7 @@ 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.
|
||||
@@ -72,7 +75,7 @@ func NewTreeWalkPool(timeout time.Duration) *TreeWalkPool {
|
||||
// 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 asccociated treeWalk.
|
||||
// Returns nil if listParams does not have an associated treeWalk.
|
||||
func (t *TreeWalkPool) Release(params listParams) (resultCh chan TreeWalkResult, endWalkCh chan struct{}) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
@@ -81,6 +84,7 @@ func (t *TreeWalkPool) Release(params listParams) (resultCh chan TreeWalkResult,
|
||||
if len(walks) > 0 {
|
||||
// Pop out the first valid walk entry.
|
||||
walk := walks[0]
|
||||
walks[0] = treeWalk{} // clear references.
|
||||
walks = walks[1:]
|
||||
if len(walks) > 0 {
|
||||
t.pool[params] = walks
|
||||
@@ -100,22 +104,59 @@ func (t *TreeWalkPool) Release(params listParams) (resultCh chan TreeWalkResult,
|
||||
// 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) Relase() signals the timer go-routine to end on endTimerCh.
|
||||
// 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.Lock()
|
||||
defer t.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.
|
||||
if v[0].added.Before(age) {
|
||||
oldest = k
|
||||
}
|
||||
}
|
||||
// Invalidate and delete oldest.
|
||||
if walks, ok := t.pool[oldest]; ok {
|
||||
walk := walks[0]
|
||||
walks[0] = treeWalk{} // clear references.
|
||||
walks = walks[1:]
|
||||
if len(walks) > 0 {
|
||||
t.pool[params] = walks
|
||||
} else {
|
||||
delete(t.pool, params)
|
||||
}
|
||||
walk.endTimerCh <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
t.pool[params] = append(t.pool[params], walkInfo)
|
||||
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.
|
||||
walks[0].endTimerCh <- struct{}{}
|
||||
copy(walks, walks[1:])
|
||||
walks[len(walks)-1] = walkInfo
|
||||
}
|
||||
|
||||
// Timer go-routine which times out after t.timeOut seconds.
|
||||
go func(endTimerCh <-chan struct{}) {
|
||||
|
||||
Reference in New Issue
Block a user