github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/dynamic-timeouts_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"math/rand"
    22  	"runtime"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  )
    27  
    28  func TestDynamicTimeoutSingleIncrease(t *testing.T) {
    29  	timeout := newDynamicTimeout(time.Minute, time.Second)
    30  
    31  	initial := timeout.Timeout()
    32  
    33  	for i := 0; i < dynamicTimeoutLogSize; i++ {
    34  		timeout.LogFailure()
    35  	}
    36  
    37  	adjusted := timeout.Timeout()
    38  
    39  	if initial >= adjusted {
    40  		t.Errorf("Failure to increase timeout, expected %v to be more than %v", adjusted, initial)
    41  	}
    42  }
    43  
    44  func TestDynamicTimeoutDualIncrease(t *testing.T) {
    45  	timeout := newDynamicTimeout(time.Minute, time.Second)
    46  
    47  	initial := timeout.Timeout()
    48  
    49  	for i := 0; i < dynamicTimeoutLogSize; i++ {
    50  		timeout.LogFailure()
    51  	}
    52  
    53  	adjusted := timeout.Timeout()
    54  
    55  	for i := 0; i < dynamicTimeoutLogSize; i++ {
    56  		timeout.LogFailure()
    57  	}
    58  
    59  	adjustedAgain := timeout.Timeout()
    60  
    61  	if initial >= adjusted || adjusted >= adjustedAgain {
    62  		t.Errorf("Failure to increase timeout multiple times")
    63  	}
    64  }
    65  
    66  func TestDynamicTimeoutSingleDecrease(t *testing.T) {
    67  	timeout := newDynamicTimeout(time.Minute, time.Second)
    68  
    69  	initial := timeout.Timeout()
    70  
    71  	for i := 0; i < dynamicTimeoutLogSize; i++ {
    72  		timeout.LogSuccess(20 * time.Second)
    73  	}
    74  
    75  	adjusted := timeout.Timeout()
    76  
    77  	if initial <= adjusted {
    78  		t.Errorf("Failure to decrease timeout, expected %v to be less than %v", adjusted, initial)
    79  	}
    80  }
    81  
    82  func TestDynamicTimeoutDualDecrease(t *testing.T) {
    83  	timeout := newDynamicTimeout(time.Minute, time.Second)
    84  
    85  	initial := timeout.Timeout()
    86  
    87  	for i := 0; i < dynamicTimeoutLogSize; i++ {
    88  		timeout.LogSuccess(20 * time.Second)
    89  	}
    90  
    91  	adjusted := timeout.Timeout()
    92  
    93  	for i := 0; i < dynamicTimeoutLogSize; i++ {
    94  		timeout.LogSuccess(20 * time.Second)
    95  	}
    96  
    97  	adjustedAgain := timeout.Timeout()
    98  
    99  	if initial <= adjusted || adjusted <= adjustedAgain {
   100  		t.Errorf("Failure to decrease timeout multiple times, initial: %v, adjusted: %v, again: %v", initial, adjusted, adjustedAgain)
   101  	}
   102  }
   103  
   104  func TestDynamicTimeoutManyDecreases(t *testing.T) {
   105  	timeout := newDynamicTimeout(time.Minute, time.Second)
   106  
   107  	initial := timeout.Timeout()
   108  
   109  	const successTimeout = 20 * time.Second
   110  	for l := 0; l < 100; l++ {
   111  		for i := 0; i < dynamicTimeoutLogSize; i++ {
   112  			timeout.LogSuccess(successTimeout)
   113  		}
   114  	}
   115  
   116  	adjusted := timeout.Timeout()
   117  	// Check whether eventual timeout is between initial value and success timeout
   118  	if initial <= adjusted || adjusted <= successTimeout {
   119  		t.Errorf("Failure to decrease timeout appropriately")
   120  	}
   121  }
   122  
   123  func TestDynamicTimeoutConcurrent(t *testing.T) {
   124  	// Race test.
   125  	timeout := newDynamicTimeout(time.Second, time.Millisecond)
   126  	var wg sync.WaitGroup
   127  	for i := 0; i < runtime.GOMAXPROCS(0); i++ {
   128  		wg.Add(1)
   129  		rng := rand.New(rand.NewSource(int64(i)))
   130  		go func() {
   131  			defer wg.Done()
   132  			for i := 0; i < 100; i++ {
   133  				for j := 0; j < 100; j++ {
   134  					timeout.LogSuccess(time.Duration(float64(time.Second) * rng.Float64()))
   135  				}
   136  				to := timeout.Timeout()
   137  				if to < time.Millisecond || to > time.Second {
   138  					panic(to)
   139  				}
   140  			}
   141  		}()
   142  	}
   143  	wg.Wait()
   144  }
   145  
   146  func TestDynamicTimeoutHitMinimum(t *testing.T) {
   147  	const minimum = 30 * time.Second
   148  	timeout := newDynamicTimeout(time.Minute, minimum)
   149  
   150  	initial := timeout.Timeout()
   151  
   152  	const successTimeout = 20 * time.Second
   153  	for l := 0; l < 100; l++ {
   154  		for i := 0; i < dynamicTimeoutLogSize; i++ {
   155  			timeout.LogSuccess(successTimeout)
   156  		}
   157  	}
   158  
   159  	adjusted := timeout.Timeout()
   160  	// Check whether eventual timeout has hit the minimum value
   161  	if initial <= adjusted || adjusted != minimum {
   162  		t.Errorf("Failure to decrease timeout appropriately")
   163  	}
   164  }
   165  
   166  func testDynamicTimeoutAdjust(t *testing.T, timeout *dynamicTimeout, f func() float64) {
   167  	const successTimeout = 20 * time.Second
   168  
   169  	for i := 0; i < dynamicTimeoutLogSize; i++ {
   170  
   171  		rnd := f()
   172  		duration := time.Duration(float64(successTimeout) * rnd)
   173  
   174  		if duration < 100*time.Millisecond {
   175  			duration = 100 * time.Millisecond
   176  		}
   177  		if duration >= time.Minute {
   178  			timeout.LogFailure()
   179  		} else {
   180  			timeout.LogSuccess(duration)
   181  		}
   182  	}
   183  }
   184  
   185  func TestDynamicTimeoutAdjustExponential(t *testing.T) {
   186  	timeout := newDynamicTimeout(time.Minute, time.Second)
   187  
   188  	rand.Seed(0)
   189  
   190  	initial := timeout.Timeout()
   191  
   192  	for try := 0; try < 10; try++ {
   193  		testDynamicTimeoutAdjust(t, timeout, rand.ExpFloat64)
   194  	}
   195  
   196  	adjusted := timeout.Timeout()
   197  	if initial <= adjusted {
   198  		t.Errorf("Failure to decrease timeout, expected %v to be less than %v", adjusted, initial)
   199  	}
   200  }
   201  
   202  func TestDynamicTimeoutAdjustNormalized(t *testing.T) {
   203  	timeout := newDynamicTimeout(time.Minute, time.Second)
   204  
   205  	rand.Seed(0)
   206  
   207  	initial := timeout.Timeout()
   208  
   209  	for try := 0; try < 10; try++ {
   210  		testDynamicTimeoutAdjust(t, timeout, func() float64 {
   211  			return 1.0 + rand.NormFloat64()
   212  		})
   213  	}
   214  
   215  	adjusted := timeout.Timeout()
   216  	if initial <= adjusted {
   217  		t.Errorf("Failure to decrease timeout, expected %v to be less than %v", adjusted, initial)
   218  	}
   219  }