Skip to content

Commit dbbc6ef

Browse files
authored
fix: constrain maximum timeout value (#3163)
fix: constrain maxiumum timeout value Add a setting to allow constraining the timeout value to a maximum.
1 parent 2a7425c commit dbbc6ef

File tree

2 files changed

+57
-8
lines changed

2 files changed

+57
-8
lines changed

packages/utils/src/adaptive-timeout.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import type { ClearableSignal } from 'any-signal'
66

77
export const DEFAULT_TIMEOUT_MULTIPLIER = 1.2
88
export const DEFAULT_FAILURE_MULTIPLIER = 2
9-
export const DEFAULT_MIN_TIMEOUT = 5000
9+
export const DEFAULT_MIN_TIMEOUT = 5_000
10+
export const DEFAULT_MAX_TIMEOUT = 60_000
11+
export const DEFAULT_INTERVAL = 5_000
1012

1113
export interface AdaptiveTimeoutSignal extends ClearableSignal {
1214
start: number
@@ -20,6 +22,7 @@ export interface AdaptiveTimeoutInit {
2022
timeoutMultiplier?: number
2123
failureMultiplier?: number
2224
minTimeout?: number
25+
maxTimeout?: number
2326
}
2427

2528
export interface GetTimeoutSignalOptions {
@@ -35,14 +38,17 @@ export class AdaptiveTimeout {
3538
private readonly timeoutMultiplier: number
3639
private readonly failureMultiplier: number
3740
private readonly minTimeout: number
41+
private readonly maxTimeout: number
3842

3943
constructor (init: AdaptiveTimeoutInit = {}) {
40-
this.success = new MovingAverage(init.interval ?? 5000)
41-
this.failure = new MovingAverage(init.interval ?? 5000)
42-
this.next = new MovingAverage(init.interval ?? 5000)
44+
const interval = init.interval ?? DEFAULT_INTERVAL
45+
this.success = new MovingAverage(interval)
46+
this.failure = new MovingAverage(interval)
47+
this.next = new MovingAverage(interval)
4348
this.failureMultiplier = init.failureMultiplier ?? DEFAULT_FAILURE_MULTIPLIER
4449
this.timeoutMultiplier = init.timeoutMultiplier ?? DEFAULT_TIMEOUT_MULTIPLIER
4550
this.minTimeout = init.minTimeout ?? DEFAULT_MIN_TIMEOUT
51+
this.maxTimeout = init.maxTimeout ?? DEFAULT_MAX_TIMEOUT
4652

4753
if (init.metricName != null) {
4854
this.metric = init.metrics?.registerMetricGroup(init.metricName)
@@ -52,10 +58,16 @@ export class AdaptiveTimeout {
5258
getTimeoutSignal (options: GetTimeoutSignalOptions = {}): AdaptiveTimeoutSignal {
5359
// calculate timeout for individual peers based on moving average of
5460
// previous successful requests
55-
const timeout = Math.max(
56-
Math.round(this.next.movingAverage * (options.timeoutFactor ?? this.timeoutMultiplier)),
57-
this.minTimeout
58-
)
61+
let timeout = Math.round(this.next.movingAverage * (options.timeoutFactor ?? this.timeoutMultiplier))
62+
63+
if (timeout < this.minTimeout) {
64+
timeout = this.minTimeout
65+
}
66+
67+
if (timeout > this.maxTimeout) {
68+
timeout = this.maxTimeout
69+
}
70+
5971
const sendTimeout = AbortSignal.timeout(timeout)
6072
const timeoutSignal = anySignal([options.signal, sendTimeout]) as AdaptiveTimeoutSignal
6173
setMaxListeners(Infinity, timeoutSignal, sendTimeout)

packages/utils/test/adaptive-timeout.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,41 @@ describe('adaptive-timeout', () => {
107107

108108
adaptiveTimeout.cleanUp(signal)
109109
})
110+
111+
it('should have a minimum timeout', () => {
112+
const adaptiveTimeout = new AdaptiveTimeout({
113+
minTimeout: 10_000
114+
})
115+
116+
const signal1 = adaptiveTimeout.getTimeoutSignal()
117+
adaptiveTimeout.cleanUp(signal1)
118+
119+
const signal2 = adaptiveTimeout.getTimeoutSignal()
120+
adaptiveTimeout.cleanUp(signal2)
121+
122+
const signal3 = adaptiveTimeout.getTimeoutSignal()
123+
adaptiveTimeout.cleanUp(signal3)
124+
125+
expect(signal3).to.have.property('timeout', 10_000)
126+
})
127+
128+
it('should have a maximum timeout', () => {
129+
const adaptiveTimeout = new AdaptiveTimeout({
130+
maxTimeout: 10_000
131+
})
132+
133+
const signal1 = adaptiveTimeout.getTimeoutSignal()
134+
clock.tick(20_000)
135+
adaptiveTimeout.cleanUp(signal1)
136+
137+
const signal2 = adaptiveTimeout.getTimeoutSignal()
138+
clock.tick(20_000)
139+
adaptiveTimeout.cleanUp(signal2)
140+
141+
const signal3 = adaptiveTimeout.getTimeoutSignal()
142+
clock.tick(20_000)
143+
adaptiveTimeout.cleanUp(signal3)
144+
145+
expect(signal3).to.have.property('timeout', 10_000)
146+
})
110147
})

0 commit comments

Comments
 (0)