storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/dynamic-timeouts.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "math" 21 "sync" 22 "sync/atomic" 23 "time" 24 ) 25 26 const ( 27 dynamicTimeoutIncreaseThresholdPct = 0.33 // Upper threshold for failures in order to increase timeout 28 dynamicTimeoutDecreaseThresholdPct = 0.10 // Lower threshold for failures in order to decrease timeout 29 dynamicTimeoutLogSize = 16 30 maxDuration = math.MaxInt64 31 maxDynamicTimeout = 24 * time.Hour // Never set timeout bigger than this. 32 ) 33 34 // timeouts that are dynamically adapted based on actual usage results 35 type dynamicTimeout struct { 36 timeout int64 37 minimum int64 38 entries int64 39 log [dynamicTimeoutLogSize]time.Duration 40 mutex sync.Mutex 41 } 42 43 // newDynamicTimeout returns a new dynamic timeout initialized with timeout value 44 func newDynamicTimeout(timeout, minimum time.Duration) *dynamicTimeout { 45 if timeout <= 0 || minimum <= 0 { 46 panic("newDynamicTimeout: negative or zero timeout") 47 } 48 if minimum > timeout { 49 minimum = timeout 50 } 51 return &dynamicTimeout{timeout: int64(timeout), minimum: int64(minimum)} 52 } 53 54 // Timeout returns the current timeout value 55 func (dt *dynamicTimeout) Timeout() time.Duration { 56 return time.Duration(atomic.LoadInt64(&dt.timeout)) 57 } 58 59 // LogSuccess logs the duration of a successful action that 60 // did not hit the timeout 61 func (dt *dynamicTimeout) LogSuccess(duration time.Duration) { 62 dt.logEntry(duration) 63 } 64 65 // LogFailure logs an action that hit the timeout 66 func (dt *dynamicTimeout) LogFailure() { 67 dt.logEntry(maxDuration) 68 } 69 70 // logEntry stores a log entry 71 func (dt *dynamicTimeout) logEntry(duration time.Duration) { 72 if duration < 0 { 73 return 74 } 75 entries := int(atomic.AddInt64(&dt.entries, 1)) 76 index := entries - 1 77 if index < dynamicTimeoutLogSize { 78 dt.mutex.Lock() 79 dt.log[index] = duration 80 81 // We leak entries while we copy 82 if entries == dynamicTimeoutLogSize { 83 84 // Make copy on stack in order to call adjust() 85 logCopy := [dynamicTimeoutLogSize]time.Duration{} 86 copy(logCopy[:], dt.log[:]) 87 88 // reset log entries 89 atomic.StoreInt64(&dt.entries, 0) 90 dt.mutex.Unlock() 91 92 dt.adjust(logCopy) 93 return 94 } 95 dt.mutex.Unlock() 96 } 97 } 98 99 // adjust changes the value of the dynamic timeout based on the 100 // previous results 101 func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) { 102 failures, max := 0, time.Duration(0) 103 for _, dur := range entries[:] { 104 if dur == maxDuration { 105 failures++ 106 } else if dur > max { 107 max = dur 108 } 109 } 110 111 failPct := float64(failures) / float64(len(entries)) 112 113 if failPct > dynamicTimeoutIncreaseThresholdPct { 114 // We are hitting the timeout too often, so increase the timeout by 25% 115 timeout := atomic.LoadInt64(&dt.timeout) * 125 / 100 116 117 // Set upper cap. 118 if timeout > int64(maxDynamicTimeout) { 119 timeout = int64(maxDynamicTimeout) 120 } 121 // Safety, shouldn't happen 122 if timeout < dt.minimum { 123 timeout = dt.minimum 124 } 125 atomic.StoreInt64(&dt.timeout, timeout) 126 } else if failPct < dynamicTimeoutDecreaseThresholdPct { 127 // We are hitting the timeout relatively few times, 128 // so decrease the timeout towards 25 % of maximum time spent. 129 max = max * 125 / 100 130 131 timeout := atomic.LoadInt64(&dt.timeout) 132 if max < time.Duration(timeout) { 133 // Move 50% toward the max. 134 timeout = (int64(max) + timeout) / 2 135 } 136 if timeout < dt.minimum { 137 timeout = dt.minimum 138 } 139 atomic.StoreInt64(&dt.timeout, timeout) 140 } 141 }