Skip to content

Commit 51cb201

Browse files
Add memory logs and destroy stream on successful case (#78)
## Description <!-- A brief description of what the PR does/changes. Use active voice and present tense, e.g., This PR fixes ... --> This PR adds few more safety http stream destroys and adds memory logging every 10 seconds in main thread. ## Connected Issues - https://app.devrev.ai/devrev/works/ISS-214557 - https://app.devrev.ai/devrev/works/ISS-214092 ## Checklist - [x] Tests added/updated and ran with `npm run test` OR no tests needed. - [x] Ran backwards compatibility tests with `npm run test:backwards-compatibility`. - [x] Tested airdrop-template linked to this PR. - [x] Documentation updated and provided a link to PR / new docs OR `no-docs` written: <!-- Add this once we have eslint prepared: - [ ] Code formatted and checked with `npm run lint`. -->
1 parent e572380 commit 51cb201

File tree

5 files changed

+80
-6
lines changed

5 files changed

+80
-6
lines changed

src/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@ export const LIBRARY_VERSION = getLibraryVersion();
6969

7070
export const DEFAULT_LAMBDA_TIMEOUT = 10 * 60 * 1000; // 10 minutes
7171
export const HARD_TIMEOUT_MULTIPLIER = 1.3;
72+
export const MEMORY_LOG_INTERVAL = 10 * 1000; // 10 seconds
7273

7374
export const DEFAULT_SLEEP_DELAY_MS = 3 * 60 * 1000; // 3 minutes

src/common/helpers.ts

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import * as path from 'path';
2+
import { readFileSync } from 'fs';
3+
import * as v8 from 'v8';
4+
15
import {
26
AirdropEvent,
37
EventType,
@@ -10,9 +14,10 @@ import {
1014
LoaderReport,
1115
StatsFileObject,
1216
} from '../types/loading';
13-
import { readFileSync } from 'fs';
14-
import * as path from 'path';
15-
import { MAX_DEVREV_FILENAME_EXTENSION_LENGTH, MAX_DEVREV_FILENAME_LENGTH } from './constants';
17+
import {
18+
MAX_DEVREV_FILENAME_EXTENSION_LENGTH,
19+
MAX_DEVREV_FILENAME_LENGTH,
20+
} from './constants';
1621

1722
export function getTimeoutErrorEventType(eventType: EventType): {
1823
eventType: ExtractorEventType | LoaderEventType;
@@ -210,13 +215,66 @@ export function truncateFilename(filename: string): string {
210215
console.warn(
211216
`Filename length exceeds the maximum limit of ${MAX_DEVREV_FILENAME_LENGTH} characters. Truncating filename.`
212217
);
213-
218+
214219
let extension = filename.slice(-MAX_DEVREV_FILENAME_EXTENSION_LENGTH);
215220
// Calculate how many characters are available for the name part after accounting for the extension and "..."
216-
const availableNameLength = MAX_DEVREV_FILENAME_LENGTH - MAX_DEVREV_FILENAME_EXTENSION_LENGTH - 3; // -3 for "..."
221+
const availableNameLength =
222+
MAX_DEVREV_FILENAME_LENGTH - MAX_DEVREV_FILENAME_EXTENSION_LENGTH - 3; // -3 for "..."
217223

218224
// Truncate the name part and add an ellipsis
219225
const truncatedFilename = filename.slice(0, availableNameLength);
220226

221227
return `${truncatedFilename}...${extension}`;
222228
}
229+
230+
export interface MemoryInfo {
231+
rssUsedMB: string;
232+
rssUsedPercent: string; // Critical for OOM detection
233+
heapUsedPercent: string; // GC pressure indicator
234+
externalMB: string; // C++ objects and buffers (HTTP streams, etc.)
235+
arrayBuffersMB: string; // Buffer data (unclosed streams show here)
236+
formattedMessage: string;
237+
}
238+
239+
export function getMemoryUsage(): MemoryInfo | null {
240+
try {
241+
const memUsage = process.memoryUsage();
242+
const heapStats = v8.getHeapStatistics();
243+
244+
const rssUsedMB = memUsage.rss / 1024 / 1024;
245+
const heapLimitMB = heapStats.heap_size_limit / 1024 / 1024;
246+
247+
const effectiveMemoryLimitMB = heapLimitMB;
248+
249+
// Calculate heap values for consistent format
250+
const heapUsedMB = heapStats.used_heap_size / 1024 / 1024;
251+
const heapTotalMB = heapStats.heap_size_limit / 1024 / 1024;
252+
253+
// Calculate external and buffer values (critical for detecting stream leaks)
254+
const externalMB = memUsage.external / 1024 / 1024;
255+
const arrayBuffersMB = memUsage.arrayBuffers / 1024 / 1024;
256+
257+
// Critical percentages for OOM detection
258+
const rssUsedPercent =
259+
((rssUsedMB / effectiveMemoryLimitMB) * 100).toFixed(2) + '%';
260+
const heapUsedPercent =
261+
((heapStats.used_heap_size / heapStats.heap_size_limit) * 100).toFixed(
262+
2
263+
) + '%';
264+
265+
// Detailed message showing RSS breakdown for leak detection
266+
const formattedMessage = `Memory: RSS ${rssUsedMB.toFixed(2)}/${effectiveMemoryLimitMB.toFixed(2)}MB (${rssUsedPercent}) [Heap ${heapUsedMB.toFixed(2)}/${heapTotalMB.toFixed(2)}MB (${heapUsedPercent}) + External ${externalMB.toFixed(2)}MB + Buffers ${arrayBuffersMB.toFixed(2)}MB].`;
267+
268+
return {
269+
rssUsedMB: rssUsedMB.toFixed(2),
270+
rssUsedPercent,
271+
heapUsedPercent,
272+
externalMB: externalMB.toFixed(2),
273+
arrayBuffersMB: arrayBuffersMB.toFixed(2),
274+
formattedMessage,
275+
};
276+
} catch (err) {
277+
console.error('Error retrieving memory usage:', (err as Error).message);
278+
return null;
279+
}
280+
}

src/uploader/uploader.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ export class Uploader {
184184
maxRedirects: 0, // Prevents buffering
185185
validateStatus: () => true, // Prevents errors on redirects
186186
});
187+
this.destroyStream(fileStream);
187188
return response;
188189
} catch (error) {
189190
console.error('Error while streaming artifact.', serializeError(error));

src/workers/spawn.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ExtractorEventType,
99
} from '../types/extraction';
1010
import { emit } from '../common/control-protocol';
11-
import { getTimeoutErrorEventType } from '../common/helpers';
11+
import { getTimeoutErrorEventType, getMemoryUsage } from '../common/helpers';
1212
import { Logger, serializeError } from '../logger/logger';
1313
import {
1414
GetWorkerPathInterface,
@@ -23,6 +23,7 @@ import { LogLevel } from '../logger/logger.interfaces';
2323
import {
2424
DEFAULT_LAMBDA_TIMEOUT,
2525
HARD_TIMEOUT_MULTIPLIER,
26+
MEMORY_LOG_INTERVAL,
2627
} from '../common/constants';
2728

2829
function getWorkerPath({
@@ -166,6 +167,7 @@ export class Spawn {
166167
private lambdaTimeout: number;
167168
private softTimeoutTimer: ReturnType<typeof setTimeout> | undefined;
168169
private hardTimeoutTimer: ReturnType<typeof setTimeout> | undefined;
170+
private memoryMonitoringInterval: ReturnType<typeof setInterval> | undefined;
169171
private logger: Logger;
170172
private resolve: (value: void | PromiseLike<void>) => void;
171173

@@ -228,6 +230,14 @@ export class Spawn {
228230
this.alreadyEmitted = true;
229231
}
230232
});
233+
234+
// Log memory usage every 10 seconds
235+
this.memoryMonitoringInterval = setInterval(() => {
236+
const memoryInfo = getMemoryUsage();
237+
if (memoryInfo) {
238+
this.logger.info(memoryInfo.formattedMessage);
239+
}
240+
}, MEMORY_LOG_INTERVAL);
231241
}
232242

233243
private clearTimeouts(): void {
@@ -237,6 +247,9 @@ export class Spawn {
237247
if (this.hardTimeoutTimer) {
238248
clearTimeout(this.hardTimeoutTimer);
239249
}
250+
if (this.memoryMonitoringInterval) {
251+
clearInterval(this.memoryMonitoringInterval);
252+
}
240253
}
241254

242255
private async exitFromMainThread(): Promise<void> {

src/workers/worker-adapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ export class WorkerAdapter<ConnectorState> {
727727
console.warn(
728728
`Error while streaming to artifact for attachment ID ${attachment.id}. Skipping attachment.`
729729
);
730+
this.destroyHttpStream(httpStream);
730731
return;
731732
}
732733

0 commit comments

Comments
 (0)