| 
									
										
										
										
											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/>.
 | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	"runtime" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestDynamicTimeoutSingleIncrease(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 		timeout.LogFailure() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if initial >= adjusted { | 
					
						
							|  |  |  | 		t.Errorf("Failure to increase timeout, expected %v to be more than %v", adjusted, initial) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestDynamicTimeoutDualIncrease(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 		timeout.LogFailure() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 		timeout.LogFailure() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjustedAgain := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if initial >= adjusted || adjusted >= adjustedAgain { | 
					
						
							|  |  |  | 		t.Errorf("Failure to increase timeout multiple times") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestDynamicTimeoutSingleDecrease(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 		timeout.LogSuccess(20 * time.Second) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if initial <= adjusted { | 
					
						
							|  |  |  | 		t.Errorf("Failure to decrease timeout, expected %v to be less than %v", adjusted, initial) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestDynamicTimeoutDualDecrease(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 		timeout.LogSuccess(20 * time.Second) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 		timeout.LogSuccess(20 * time.Second) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjustedAgain := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if initial <= adjusted || adjusted <= adjustedAgain { | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 		t.Errorf("Failure to decrease timeout multiple times, initial: %v, adjusted: %v, again: %v", initial, adjusted, adjustedAgain) | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestDynamicTimeoutManyDecreases(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const successTimeout = 20 * time.Second | 
					
						
							|  |  |  | 	for l := 0; l < 100; l++ { | 
					
						
							|  |  |  | 		for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 			timeout.LogSuccess(successTimeout) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 	// Check whether eventual timeout is between initial value and success timeout
 | 
					
						
							|  |  |  | 	if initial <= adjusted || adjusted <= successTimeout { | 
					
						
							|  |  |  | 		t.Errorf("Failure to decrease timeout appropriately") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | func TestDynamicTimeoutConcurrent(t *testing.T) { | 
					
						
							|  |  |  | 	// Race test.
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Second, time.Millisecond) | 
					
						
							|  |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 	for i := 0; i < runtime.GOMAXPROCS(0); i++ { | 
					
						
							|  |  |  | 		wg.Add(1) | 
					
						
							|  |  |  | 		rng := rand.New(rand.NewSource(int64(i))) | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							|  |  |  | 			for i := 0; i < 100; i++ { | 
					
						
							|  |  |  | 				timeout.LogFailure() | 
					
						
							|  |  |  | 				for j := 0; j < 100; j++ { | 
					
						
							|  |  |  | 					timeout.LogSuccess(time.Duration(float64(time.Second) * rng.Float64())) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				to := timeout.Timeout() | 
					
						
							|  |  |  | 				if to < time.Millisecond || to > time.Second { | 
					
						
							|  |  |  | 					panic(to) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	wg.Wait() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | func TestDynamicTimeoutHitMinimum(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const minimum = 30 * time.Second | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, minimum) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const successTimeout = 20 * time.Second | 
					
						
							|  |  |  | 	for l := 0; l < 100; l++ { | 
					
						
							|  |  |  | 		for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 			timeout.LogSuccess(successTimeout) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 	// Check whether eventual timeout has hit the minimum value
 | 
					
						
							|  |  |  | 	if initial <= adjusted || adjusted != minimum { | 
					
						
							|  |  |  | 		t.Errorf("Failure to decrease timeout appropriately") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func testDynamicTimeoutAdjust(t *testing.T, timeout *dynamicTimeout, f func() float64) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const successTimeout = 20 * time.Second | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i := 0; i < dynamicTimeoutLogSize; i++ { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rnd := f() | 
					
						
							|  |  |  | 		duration := time.Duration(float64(successTimeout) * rnd) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if duration < 100*time.Millisecond { | 
					
						
							|  |  |  | 			duration = 100 * time.Millisecond | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if duration >= time.Minute { | 
					
						
							|  |  |  | 			timeout.LogFailure() | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			timeout.LogSuccess(duration) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestDynamicTimeoutAdjustExponential(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	rand.Seed(0) | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for try := 0; try < 10; try++ { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		testDynamicTimeoutAdjust(t, timeout, rand.ExpFloat64) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 	if initial <= adjusted { | 
					
						
							|  |  |  | 		t.Errorf("Failure to decrease timeout, expected %v to be less than %v", adjusted, initial) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestDynamicTimeoutAdjustNormalized(t *testing.T) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout := newDynamicTimeout(time.Minute, time.Second) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 00:18:18 +08:00
										 |  |  | 	rand.Seed(0) | 
					
						
							| 
									
										
										
										
											2017-09-01 02:29:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	initial := timeout.Timeout() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for try := 0; try < 10; try++ { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		testDynamicTimeoutAdjust(t, timeout, func() float64 { | 
					
						
							|  |  |  | 			return 1.0 + rand.NormFloat64() | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adjusted := timeout.Timeout() | 
					
						
							|  |  |  | 	if initial <= adjusted { | 
					
						
							|  |  |  | 		t.Errorf("Failure to decrease timeout, expected %v to be less than %v", adjusted, initial) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |