| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2019-04-10 02:39:42 +08:00
										 |  |  |  * MinIO Cloud Storage, (C) 2017 MinIO, Inc. | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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 ( | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	"math" | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 	"sync" | 
					
						
							|  |  |  | 	"sync/atomic" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	dynamicTimeoutIncreaseThresholdPct = 0.33 // Upper threshold for failures in order to increase timeout
 | 
					
						
							|  |  |  | 	dynamicTimeoutDecreaseThresholdPct = 0.10 // Lower threshold for failures in order to decrease timeout
 | 
					
						
							|  |  |  | 	dynamicTimeoutLogSize              = 16 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	maxDuration                        = math.MaxInt64 | 
					
						
							|  |  |  | 	maxDynamicTimeout                  = 24 * time.Hour // Never set timeout bigger than this.
 | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // timeouts that are dynamically adapted based on actual usage results
 | 
					
						
							|  |  |  | type dynamicTimeout struct { | 
					
						
							|  |  |  | 	timeout int64 | 
					
						
							|  |  |  | 	minimum int64 | 
					
						
							|  |  |  | 	entries int64 | 
					
						
							|  |  |  | 	log     [dynamicTimeoutLogSize]time.Duration | 
					
						
							|  |  |  | 	mutex   sync.Mutex | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newDynamicTimeout returns a new dynamic timeout initialized with timeout value
 | 
					
						
							|  |  |  | func newDynamicTimeout(timeout, minimum time.Duration) *dynamicTimeout { | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	if timeout <= 0 || minimum <= 0 { | 
					
						
							|  |  |  | 		panic("newDynamicTimeout: negative or zero timeout") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if minimum > timeout { | 
					
						
							|  |  |  | 		minimum = timeout | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 	return &dynamicTimeout{timeout: int64(timeout), minimum: int64(minimum)} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Timeout returns the current timeout value
 | 
					
						
							|  |  |  | func (dt *dynamicTimeout) Timeout() time.Duration { | 
					
						
							|  |  |  | 	return time.Duration(atomic.LoadInt64(&dt.timeout)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // LogSuccess logs the duration of a successful action that
 | 
					
						
							|  |  |  | // did not hit the timeout
 | 
					
						
							|  |  |  | func (dt *dynamicTimeout) LogSuccess(duration time.Duration) { | 
					
						
							|  |  |  | 	dt.logEntry(duration) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // LogFailure logs an action that hit the timeout
 | 
					
						
							|  |  |  | func (dt *dynamicTimeout) LogFailure() { | 
					
						
							|  |  |  | 	dt.logEntry(maxDuration) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // logEntry stores a log entry
 | 
					
						
							|  |  |  | func (dt *dynamicTimeout) logEntry(duration time.Duration) { | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	if duration < 0 { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 	entries := int(atomic.AddInt64(&dt.entries, 1)) | 
					
						
							|  |  |  | 	index := entries - 1 | 
					
						
							|  |  |  | 	if index < dynamicTimeoutLogSize { | 
					
						
							|  |  |  | 		dt.mutex.Lock() | 
					
						
							|  |  |  | 		dt.log[index] = duration | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 		// We leak entries while we copy
 | 
					
						
							|  |  |  | 		if entries == dynamicTimeoutLogSize { | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 			// Make copy on stack in order to call adjust()
 | 
					
						
							|  |  |  | 			logCopy := [dynamicTimeoutLogSize]time.Duration{} | 
					
						
							|  |  |  | 			copy(logCopy[:], dt.log[:]) | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 			// reset log entries
 | 
					
						
							|  |  |  | 			atomic.StoreInt64(&dt.entries, 0) | 
					
						
							|  |  |  | 			dt.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 			dt.adjust(logCopy) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		dt.mutex.Unlock() | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // adjust changes the value of the dynamic timeout based on the
 | 
					
						
							|  |  |  | // previous results
 | 
					
						
							|  |  |  | func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) { | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	failures, max := 0, time.Duration(0) | 
					
						
							|  |  |  | 	for _, dur := range entries[:] { | 
					
						
							|  |  |  | 		if dur == maxDuration { | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 			failures++ | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 		} else if dur > max { | 
					
						
							|  |  |  | 			max = dur | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	failPct := float64(failures) / float64(len(entries)) | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	if failPct > dynamicTimeoutIncreaseThresholdPct { | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 		// We are hitting the timeout too often, so increase the timeout by 25%
 | 
					
						
							|  |  |  | 		timeout := atomic.LoadInt64(&dt.timeout) * 125 / 100 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 		// Set upper cap.
 | 
					
						
							|  |  |  | 		if timeout > int64(maxDynamicTimeout) { | 
					
						
							|  |  |  | 			timeout = int64(maxDynamicTimeout) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Safety, shouldn't happen
 | 
					
						
							|  |  |  | 		if timeout < dt.minimum { | 
					
						
							|  |  |  | 			timeout = dt.minimum | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		atomic.StoreInt64(&dt.timeout, timeout) | 
					
						
							|  |  |  | 	} else if failPct < dynamicTimeoutDecreaseThresholdPct { | 
					
						
							|  |  |  | 		// We are hitting the timeout relatively few times,
 | 
					
						
							|  |  |  | 		// so decrease the timeout towards 25 % of maximum time spent.
 | 
					
						
							|  |  |  | 		max = max * 125 / 100 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		timeout := atomic.LoadInt64(&dt.timeout) | 
					
						
							|  |  |  | 		if max < time.Duration(timeout) { | 
					
						
							|  |  |  | 			// Move 50% toward the max.
 | 
					
						
							|  |  |  | 			timeout = (int64(max) + timeout) / 2 | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 		if timeout < dt.minimum { | 
					
						
							|  |  |  | 			timeout = dt.minimum | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		atomic.StoreInt64(&dt.timeout, timeout) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |