Skip to content

Commit b2135ec

Browse files
committed
test: add unit tests for RpcTimeoutError and configure timeout backoff (PR #1275)
1 parent 91cb0dc commit b2135ec

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

src/managers/common/nativePythonFinder.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ const MAX_RESTART_ATTEMPTS = 3;
2525
const RESTART_BACKOFF_BASE_MS = 1_000; // 1 second base, exponential: 1s, 2s, 4s
2626
const MAX_CONFIGURE_TIMEOUTS_BEFORE_KILL = 2; // Kill on the 2nd consecutive timeout
2727

28+
/**
29+
* Computes the configure timeout with exponential backoff.
30+
* @param retryCount Number of consecutive configure timeouts so far
31+
* @returns Timeout in milliseconds: 30s, 60s, 120s, ... capped at REFRESH_TIMEOUT_MS
32+
*/
33+
export function getConfigureTimeoutMs(retryCount: number): number {
34+
return Math.min(CONFIGURE_TIMEOUT_MS * Math.pow(2, retryCount), REFRESH_TIMEOUT_MS);
35+
}
36+
2837
export async function getNativePythonToolsPath(): Promise<string> {
2938
const envsExt = getExtension(ENVS_EXTENSION_ID);
3039
if (envsExt) {
@@ -123,7 +132,7 @@ interface RefreshOptions {
123132
/**
124133
* Error thrown when a JSON-RPC request times out.
125134
*/
126-
class RpcTimeoutError extends Error {
135+
export class RpcTimeoutError extends Error {
127136
constructor(
128137
public readonly method: string,
129138
timeoutMs: number,
@@ -602,7 +611,7 @@ class NativePythonFinderImpl implements NativePythonFinder {
602611
}
603612
this.outputChannel.info('[pet] configure: Sending configuration update:', JSON.stringify(options));
604613
// Exponential backoff: 30s, 60s on retry. Capped at REFRESH_TIMEOUT_MS.
605-
const timeoutMs = Math.min(CONFIGURE_TIMEOUT_MS * Math.pow(2, this.configureTimeoutCount), REFRESH_TIMEOUT_MS);
614+
const timeoutMs = getConfigureTimeoutMs(this.configureTimeoutCount);
606615
if (this.configureTimeoutCount > 0) {
607616
this.outputChannel.info(
608617
`[pet] configure: Using extended timeout of ${timeoutMs}ms (retry ${this.configureTimeoutCount})`,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import assert from 'node:assert';
2+
import { RpcTimeoutError, getConfigureTimeoutMs } from '../../../managers/common/nativePythonFinder';
3+
4+
suite('RpcTimeoutError', () => {
5+
test('has correct name property', () => {
6+
const error = new RpcTimeoutError('configure', 30000);
7+
assert.strictEqual(error.name, 'RpcTimeoutError');
8+
});
9+
10+
test('has correct method property', () => {
11+
const error = new RpcTimeoutError('configure', 30000);
12+
assert.strictEqual(error.method, 'configure');
13+
});
14+
15+
test('message includes method name and timeout', () => {
16+
const error = new RpcTimeoutError('resolve', 5000);
17+
assert.strictEqual(error.message, "Request 'resolve' timed out after 5000ms");
18+
});
19+
20+
test('is instanceof Error', () => {
21+
const error = new RpcTimeoutError('configure', 30000);
22+
assert.ok(error instanceof Error);
23+
assert.ok(error instanceof RpcTimeoutError);
24+
});
25+
});
26+
27+
suite('getConfigureTimeoutMs', () => {
28+
test('returns base timeout (30s) on first attempt', () => {
29+
assert.strictEqual(getConfigureTimeoutMs(0), 30_000);
30+
});
31+
32+
test('doubles timeout on first retry (60s)', () => {
33+
assert.strictEqual(getConfigureTimeoutMs(1), 60_000);
34+
});
35+
36+
test('doubles again on second retry (120s)', () => {
37+
assert.strictEqual(getConfigureTimeoutMs(2), 120_000);
38+
});
39+
40+
test('caps at REFRESH_TIMEOUT_MS (120s) for higher retries', () => {
41+
// 30_000 * 2^3 = 240_000, but capped at 120_000
42+
assert.strictEqual(getConfigureTimeoutMs(3), 120_000);
43+
assert.strictEqual(getConfigureTimeoutMs(10), 120_000);
44+
});
45+
});

0 commit comments

Comments
 (0)