Skip to content

Commit 1242a7b

Browse files
Cleanup the timers and intervals in the http-client when the process is killed
1 parent 6e280c4 commit 1242a7b

File tree

1 file changed

+44
-27
lines changed

1 file changed

+44
-27
lines changed

lib/common/http-client.ts

+44-27
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,26 @@ import { HttpStatusCodes } from "./constants";
77
import * as request from "request";
88

99
export class HttpClient implements Server.IHttpClient {
10-
private defaultUserAgent: string;
1110
private static STATUS_CODE_REGEX = /statuscode=(\d+)/i;
1211
private static STUCK_REQUEST_ERROR_MESSAGE = "The request can't receive any response.";
1312
private static STUCK_RESPONSE_ERROR_MESSAGE = "Can't receive all parts of the response.";
1413
private static STUCK_REQUEST_TIMEOUT = 60000;
1514
// We receive multiple response packets every ms but we don't need to be very aggressive here.
1615
private static STUCK_RESPONSE_CHECK_INTERVAL = 10000;
1716

17+
private defaultUserAgent: string;
18+
private cleanupData: ICleanupRequestData[];
19+
1820
constructor(private $config: Config.IConfig,
1921
private $logger: ILogger,
22+
private $processService: IProcessService,
2023
private $proxyService: IProxyService,
21-
private $staticConfig: Config.IStaticConfig) { }
24+
private $staticConfig: Config.IStaticConfig) {
25+
this.cleanupData = [];
26+
this.$processService.attachToProcessExitSignals(this, () => {
27+
this.cleanupData.forEach(d => this.cleanupAfterRequest(d));
28+
});
29+
}
2230

2331
public async httpRequest(options: any, proxySettings?: IProxySettings): Promise<Server.IResponse> {
2432
try {
@@ -97,9 +105,9 @@ export class HttpClient implements Server.IHttpClient {
97105
const result = new Promise<Server.IResponse>((resolve, reject) => {
98106
let timerId: number;
99107
let stuckRequestTimerId: number;
100-
let stuckResponseIntervalId: NodeJS.Timer;
101108
let hasResponse = false;
102-
const timers: number[] = [];
109+
const cleanupRequestData: ICleanupRequestData = { timers: [], stuckResponseIntervalId: null };
110+
this.cleanupData.push(cleanupRequestData);
103111

104112
const promiseActions: IPromiseActions<Server.IResponse> = {
105113
resolve,
@@ -109,9 +117,9 @@ export class HttpClient implements Server.IHttpClient {
109117

110118
if (options.timeout) {
111119
timerId = setTimeout(() => {
112-
this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(`Request to ${unmodifiedOptions.url} timed out.`) });
120+
this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(`Request to ${unmodifiedOptions.url} timed out.`) });
113121
}, options.timeout);
114-
timers.push(timerId);
122+
cleanupRequestData.timers.push(timerId);
115123

116124
delete options.timeout;
117125
}
@@ -128,10 +136,10 @@ export class HttpClient implements Server.IHttpClient {
128136
stuckRequestTimerId = null;
129137
if (!hasResponse) {
130138
requestObj.abort();
131-
this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(HttpClient.STUCK_REQUEST_ERROR_MESSAGE) });
139+
this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(HttpClient.STUCK_REQUEST_ERROR_MESSAGE) });
132140
}
133141
}, options.timeout || HttpClient.STUCK_REQUEST_TIMEOUT);
134-
timers.push(stuckRequestTimerId);
142+
cleanupRequestData.timers.push(stuckRequestTimerId);
135143

136144
requestObj
137145
.on("error", (err: IHttpRequestError) => {
@@ -145,18 +153,18 @@ export class HttpClient implements Server.IHttpClient {
145153
const errorMessage = this.getErrorMessage(errorMessageStatusCode, null);
146154
err.proxyAuthenticationRequired = errorMessageStatusCode === HttpStatusCodes.PROXY_AUTHENTICATION_REQUIRED;
147155
err.message = errorMessage || err.message;
148-
this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err });
156+
this.setResponseResult(promiseActions, cleanupRequestData, { err });
149157
})
150158
.on("response", (response: Server.IRequestResponseData) => {
151159
hasResponse = true;
152160
let lastChunkTimestamp = Date.now();
153-
stuckResponseIntervalId = setInterval(() => {
161+
cleanupRequestData.stuckResponseIntervalId = setInterval(() => {
154162
if (Date.now() - lastChunkTimestamp > HttpClient.STUCK_RESPONSE_CHECK_INTERVAL) {
155163
if ((<any>response).destroy) {
156164
(<any>response).destroy();
157165
}
158166

159-
this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err: new Error(HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) });
167+
this.setResponseResult(promiseActions, cleanupRequestData, { err: new Error(HttpClient.STUCK_RESPONSE_ERROR_MESSAGE) });
160168
}
161169
}, HttpClient.STUCK_RESPONSE_CHECK_INTERVAL);
162170
const successful = helpers.isRequestSuccessful(response);
@@ -180,7 +188,7 @@ export class HttpClient implements Server.IHttpClient {
180188
if (pipeTo) {
181189
pipeTo.on("finish", () => {
182190
this.$logger.trace("httpRequest: Piping done. code = %d", response.statusCode.toString());
183-
this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { response });
191+
this.setResponseResult(promiseActions, cleanupRequestData, { response });
184192
});
185193

186194
responseStream.pipe(pipeTo);
@@ -196,13 +204,13 @@ export class HttpClient implements Server.IHttpClient {
196204
const responseBody = data.join("");
197205

198206
if (successful) {
199-
this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { body: responseBody, response });
207+
this.setResponseResult(promiseActions, cleanupRequestData, { body: responseBody, response });
200208
} else {
201209
const errorMessage = this.getErrorMessage(response.statusCode, responseBody);
202210
const err: any = new Error(errorMessage);
203211
err.response = response;
204212
err.body = responseBody;
205-
this.setResponseResult(promiseActions, timers, stuckResponseIntervalId, { err });
213+
this.setResponseResult(promiseActions, cleanupRequestData, { err });
206214
}
207215
});
208216
}
@@ -233,19 +241,8 @@ export class HttpClient implements Server.IHttpClient {
233241
return response;
234242
}
235243

236-
private setResponseResult(result: IPromiseActions<Server.IResponse>, timers: number[], stuckResponseIntervalId: NodeJS.Timer, resultData: { response?: Server.IRequestResponseData, body?: string, err?: Error }): void {
237-
timers.forEach(t => {
238-
if (t) {
239-
clearTimeout(t);
240-
t = null;
241-
}
242-
});
243-
244-
if (stuckResponseIntervalId) {
245-
clearInterval(stuckResponseIntervalId);
246-
stuckResponseIntervalId = null;
247-
}
248-
244+
private setResponseResult(result: IPromiseActions<Server.IResponse>, cleanupRequestData: ICleanupRequestData, resultData: { response?: Server.IRequestResponseData, body?: string, err?: Error }): void {
245+
this.cleanupAfterRequest(cleanupRequestData);
249246
if (!result.isResolved()) {
250247
result.isResolved = () => true;
251248
if (resultData.err) {
@@ -317,5 +314,25 @@ export class HttpClient implements Server.IHttpClient {
317314
this.$logger.trace("Using proxy: %s", options.proxy);
318315
}
319316
}
317+
318+
private cleanupAfterRequest(data: ICleanupRequestData): void {
319+
data.timers.forEach(t => {
320+
if (t) {
321+
clearTimeout(t);
322+
t = null;
323+
}
324+
});
325+
326+
if (data.stuckResponseIntervalId) {
327+
clearInterval(data.stuckResponseIntervalId);
328+
data.stuckResponseIntervalId = null;
329+
}
330+
}
320331
}
332+
333+
interface ICleanupRequestData {
334+
timers: number[];
335+
stuckResponseIntervalId: NodeJS.Timer;
336+
}
337+
321338
$injector.register("httpClient", HttpClient);

0 commit comments

Comments
 (0)