| 
									
										
										
										
											2021-04-19 03:41:13 +08: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 12:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 07:23:42 +08:00
										 |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2019-05-15 04:49:10 +08:00
										 |  |  | 	"reflect" | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 	"sync" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Global lookup timeout.
 | 
					
						
							|  |  |  | const ( | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 	globalLookupTimeout    = time.Minute * 30 // 30minutes.
 | 
					
						
							|  |  |  | 	treeWalkEntryLimit     = 50 | 
					
						
							|  |  |  | 	treeWalkSameEntryLimit = 4 | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08: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-04 02:33:50 +08:00
										 |  |  | var errWalkAbort = errors.New("treeWalk abort") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | // treeWalk - represents the go routine that does the file tree walk.
 | 
					
						
							|  |  |  | type treeWalk struct { | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 	added      time.Time | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | 	resultCh   chan TreeWalkResult | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08: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 12:05:00 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | // TreeWalkPool - pool of treeWalk go routines.
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08: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-18 00:52:08 +08:00
										 |  |  | type TreeWalkPool struct { | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 	mu      sync.Mutex | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 	pool    map[listParams][]treeWalk | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 	timeOut time.Duration | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | // NewTreeWalkPool - initialize new tree walk pool.
 | 
					
						
							|  |  |  | func NewTreeWalkPool(timeout time.Duration) *TreeWalkPool { | 
					
						
							|  |  |  | 	tPool := &TreeWalkPool{ | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 		pool:    make(map[listParams][]treeWalk), | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 		timeOut: timeout, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return tPool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | // Release - selects a treeWalk from the pool based on the input
 | 
					
						
							| 
									
										
										
										
											2019-04-18 00:52:08 +08:00
										 |  |  | // listParams, removes it from the pool, and returns the TreeWalkResult
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | // channel.
 | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | // Returns nil if listParams does not have an associated treeWalk.
 | 
					
						
							| 
									
										
										
										
											2020-05-04 13:35:40 +08:00
										 |  |  | func (t *TreeWalkPool) Release(params listParams) (resultCh chan TreeWalkResult, endWalkCh chan struct{}) { | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 	t.mu.Lock() | 
					
						
							|  |  |  | 	defer t.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	walks, ok := t.pool[params] // Pick the valid walks.
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08:00
										 |  |  | 	if !ok || len(walks) == 0 { | 
					
						
							|  |  |  | 		// Release return nil if params not found.
 | 
					
						
							|  |  |  | 		return nil, nil | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08: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 12:05:00 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08: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-18 00:52:07 +08:00
										 |  |  | // 2) Release() signals the timer go-routine to end on endTimerCh.
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08: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 13:35:40 +08:00
										 |  |  | func (t *TreeWalkPool) Set(params listParams, resultCh chan TreeWalkResult, endWalkCh chan struct{}) { | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 	t.mu.Lock() | 
					
						
							|  |  |  | 	defer t.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08: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 22:02:18 +08:00
										 |  |  | 			e := v[0] | 
					
						
							|  |  |  | 			if e.added.Before(age) { | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 				oldest = k | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 				age = e.added | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Invalidate and delete oldest.
 | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 		if walks, ok := t.pool[oldest]; ok && len(walks) > 0 { | 
					
						
							|  |  |  | 			endCh := walks[0].endTimerCh | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08:00
										 |  |  | 			endWalkCh := walks[0].endWalkCh | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 			if len(walks) > 1 { | 
					
						
							|  |  |  | 				// Move walks forward
 | 
					
						
							|  |  |  | 				copy(walks, walks[1:]) | 
					
						
							|  |  |  | 				walks = walks[:len(walks)-1] | 
					
						
							|  |  |  | 				t.pool[oldest] = walks | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 				// Only entry, just delete.
 | 
					
						
							|  |  |  | 				delete(t.pool, oldest) | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 			select { | 
					
						
							|  |  |  | 			case endCh <- struct{}{}: | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08:00
										 |  |  | 				close(endWalkCh) | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 			default: | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// Shouldn't happen, but just in case.
 | 
					
						
							|  |  |  | 			delete(t.pool, oldest) | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	// Should be a buffered channel so that Release() never blocks.
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 	endTimerCh := make(chan struct{}, 1) | 
					
						
							|  |  |  | 	walkInfo := treeWalk{ | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 		added:      UTCNow(), | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 		resultCh:   resultCh, | 
					
						
							|  |  |  | 		endWalkCh:  endWalkCh, | 
					
						
							|  |  |  | 		endTimerCh: endTimerCh, | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 	// Append new walk info.
 | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08: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 22:02:18 +08:00
										 |  |  | 		select { | 
					
						
							|  |  |  | 		case walks[0].endTimerCh <- struct{}{}: | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08:00
										 |  |  | 			close(walks[0].endWalkCh) | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-06-18 00:52:07 +08:00
										 |  |  | 		copy(walks, walks[1:]) | 
					
						
							|  |  |  | 		walks[len(walks)-1] = walkInfo | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 	// Timer go-routine which times out after t.timeOut seconds.
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08:00
										 |  |  | 	go func(endTimerCh <-chan struct{}, walkInfo treeWalk) { | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 		select { | 
					
						
							|  |  |  | 		// Wait until timeOut
 | 
					
						
							|  |  |  | 		case <-time.After(t.timeOut): | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 			// Timeout has expired. Remove the treeWalk from treeWalkPool and
 | 
					
						
							|  |  |  | 			// end the treeWalk go-routine.
 | 
					
						
							| 
									
										
										
										
											2020-07-09 22:02:18 +08:00
										 |  |  | 			t.mu.Lock() | 
					
						
							|  |  |  | 			defer t.mu.Unlock() | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 			walks, ok := t.pool[params] | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 			if ok { | 
					
						
							| 
									
										
										
										
											2019-05-15 04:49:10 +08:00
										 |  |  | 				// Trick of filtering without allocating
 | 
					
						
							|  |  |  | 				// https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
 | 
					
						
							|  |  |  | 				nwalks := walks[:0] | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 				// Look for walkInfo, remove it from the walks list.
 | 
					
						
							| 
									
										
										
										
											2019-05-15 04:49:10 +08:00
										 |  |  | 				for _, walk := range walks { | 
					
						
							|  |  |  | 					if !reflect.DeepEqual(walk, walkInfo) { | 
					
						
							|  |  |  | 						nwalks = append(nwalks, walk) | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2019-05-15 04:49:10 +08:00
										 |  |  | 				if len(nwalks) == 0 { | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 					// No more treeWalk go-routines associated with listParams
 | 
					
						
							|  |  |  | 					// hence remove map entry.
 | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 					delete(t.pool, params) | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 				} else { | 
					
						
							|  |  |  | 					// There are more treeWalk go-routines associated with listParams
 | 
					
						
							|  |  |  | 					// hence save the list in the map.
 | 
					
						
							| 
									
										
										
										
											2019-05-15 04:49:10 +08:00
										 |  |  | 					t.pool[params] = nwalks | 
					
						
							| 
									
										
										
										
											2016-06-04 02:33:50 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-06-06 02:55:45 +08:00
										 |  |  | 			// Signal the treeWalk go-routine to die.
 | 
					
						
							|  |  |  | 			close(endWalkCh) | 
					
						
							|  |  |  | 		case <-endTimerCh: | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-07-21 08:28:26 +08:00
										 |  |  | 	}(endTimerCh, walkInfo) | 
					
						
							| 
									
										
										
										
											2016-05-30 12:05:00 +08:00
										 |  |  | } |