Skip to content

Commit 6595d04

Browse files
authored
fix(cli): long connection timeout slows the CLI down (#19187)
Setting a timeout of 3 seconds for both the connection and the response, making the CLI give up fast in case of network issues or high latency. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 7300f2e commit 6595d04

File tree

2 files changed

+74
-30
lines changed

2 files changed

+74
-30
lines changed

packages/aws-cdk/lib/notices.ts

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -105,37 +105,46 @@ export interface NoticeDataSource {
105105

106106
export class WebsiteNoticeDataSource implements NoticeDataSource {
107107
fetch(): Promise<Notice[]> {
108+
const timeout = 3000;
109+
108110
return new Promise((resolve) => {
109111
try {
110-
const req = https.get('https://cli.cdk.dev-tools.aws.dev/notices.json', res => {
111-
if (res.statusCode === 200) {
112-
res.setEncoding('utf8');
113-
let rawData = '';
114-
res.on('data', (chunk) => {
115-
rawData += chunk;
116-
});
117-
res.on('end', () => {
118-
try {
119-
const data = JSON.parse(rawData).notices as Notice[];
120-
resolve(data ?? []);
121-
} catch (e) {
122-
debug(`Failed to parse notices: ${e}`);
112+
const req = https.get('https://cli.cdk.dev-tools.aws.dev/notices.json',
113+
{ timeout },
114+
res => {
115+
const startTime = Date.now();
116+
if (res.statusCode === 200) {
117+
res.setEncoding('utf8');
118+
let rawData = '';
119+
res.on('data', (chunk) => {
120+
if (Date.now() - startTime > timeout) {
121+
resolve([]);
122+
}
123+
rawData += chunk;
124+
});
125+
res.on('end', () => {
126+
try {
127+
const data = JSON.parse(rawData).notices as Notice[];
128+
resolve(data ?? []);
129+
} catch (e) {
130+
debug(`Failed to parse notices: ${e}`);
131+
resolve([]);
132+
}
133+
});
134+
res.on('error', e => {
135+
debug(`Failed to fetch notices: ${e}`);
123136
resolve([]);
124-
}
125-
});
126-
res.on('error', e => {
127-
debug(`Failed to fetch notices: ${e}`);
137+
});
138+
} else {
139+
debug(`Failed to fetch notices. Status code: ${res.statusCode}`);
128140
resolve([]);
129-
});
130-
} else {
131-
debug(`Failed to fetch notices. Status code: ${res.statusCode}`);
132-
resolve([]);
133-
}
134-
});
141+
}
142+
});
135143
req.on('error', e => {
136144
debug(`Error on request: ${e}`);
137145
resolve([]);
138146
});
147+
req.on('timeout', _ => resolve([]));
139148
} catch (e) {
140149
debug(`HTTPS 'get' call threw an error: ${e}`);
141150
resolve([]);
@@ -176,14 +185,18 @@ export class CachedDataSource implements NoticeDataSource {
176185
}
177186

178187
private async load(): Promise<CachedNotices> {
188+
const defaultValue = {
189+
expiration: 0,
190+
notices: [],
191+
};
192+
179193
try {
180-
return await fs.readJSON(this.fileName) as CachedNotices;
194+
return fs.existsSync(this.fileName)
195+
? await fs.readJSON(this.fileName) as CachedNotices
196+
: defaultValue;
181197
} catch (e) {
182198
debug(`Failed to load notices from cache: ${e}`);
183-
return {
184-
expiration: 0,
185-
notices: [],
186-
};
199+
return defaultValue;
187200
}
188201
}
189202

packages/aws-cdk/test/notices.test.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as os from 'os';
33
import * as path from 'path';
44
import * as fs from 'fs-extra';
55
import * as nock from 'nock';
6+
import * as logging from '../lib/logging';
67
import {
78
CachedDataSource,
89
filterNotices,
@@ -233,6 +234,32 @@ describe('cli notices', () => {
233234
expect(result).toEqual([]);
234235
});
235236

237+
test('returns empty array when the connection stays idle for too long', async () => {
238+
nock('https://cli.cdk.dev-tools.aws.dev')
239+
.get('/notices.json')
240+
.delayConnection(3500)
241+
.reply(200, {
242+
notices: [BASIC_NOTICE],
243+
});
244+
245+
const result = await dataSource.fetch();
246+
247+
expect(result).toEqual([]);
248+
});
249+
250+
test('returns empty array when the request takes too long to finish', async () => {
251+
nock('https://cli.cdk.dev-tools.aws.dev')
252+
.get('/notices.json')
253+
.delayBody(3500)
254+
.reply(200, {
255+
notices: [BASIC_NOTICE],
256+
});
257+
258+
const result = await dataSource.fetch();
259+
260+
expect(result).toEqual([]);
261+
});
262+
236263
function mockCall(statusCode: number, body: any): Promise<Notice[]> {
237264
nock('https://cli.cdk.dev-tools.aws.dev')
238265
.get('/notices.json')
@@ -284,12 +311,16 @@ describe('cli notices', () => {
284311
});
285312

286313
test('retrieves data from the delegate when the file cannot be read', async () => {
287-
const nonExistingFile = path.join(os.tmpdir(), 'cache.json');
288-
const dataSource = dataSourceWithDelegateReturning(freshData, nonExistingFile);
314+
const debugSpy = jest.spyOn(logging, 'debug');
315+
316+
const dataSource = dataSourceWithDelegateReturning(freshData, 'does-not-exist.json');
289317

290318
const notices = await dataSource.fetch();
291319

292320
expect(notices).toEqual(freshData);
321+
expect(debugSpy).not.toHaveBeenCalled();
322+
323+
debugSpy.mockRestore();
293324
});
294325

295326
test('retrieved data from the delegate when it is configured to ignore the cache', async () => {

0 commit comments

Comments
 (0)