@devrev/ts-adaas 1.12.3-beta.6 → 1.12.3-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/constants.d.ts +1 -0
- package/dist/common/constants.d.ts.map +1 -1
- package/dist/common/constants.js +2 -1
- package/dist/common/worker-memory.d.ts +59 -0
- package/dist/common/worker-memory.d.ts.map +1 -0
- package/dist/common/worker-memory.js +177 -0
- package/dist/tests/oom-handling/extraction.d.ts +11 -0
- package/dist/tests/oom-handling/extraction.d.ts.map +1 -0
- package/dist/tests/oom-handling/extraction.js +29 -0
- package/dist/tests/oom-handling/jest.setup.d.ts +7 -0
- package/dist/tests/oom-handling/jest.setup.d.ts.map +1 -0
- package/dist/tests/oom-handling/jest.setup.js +26 -0
- package/dist/tests/oom-handling/oom-after-emit-worker.d.ts +2 -0
- package/dist/tests/oom-handling/oom-after-emit-worker.d.ts.map +1 -0
- package/dist/tests/oom-handling/oom-after-emit-worker.js +35 -0
- package/dist/tests/oom-handling/oom-attachments-worker.d.ts +2 -0
- package/dist/tests/oom-handling/oom-attachments-worker.d.ts.map +1 -0
- package/dist/tests/oom-handling/oom-attachments-worker.js +29 -0
- package/dist/tests/oom-handling/oom-external-sync-units-worker.d.ts +2 -0
- package/dist/tests/oom-handling/oom-external-sync-units-worker.d.ts.map +1 -0
- package/dist/tests/oom-handling/oom-external-sync-units-worker.js +29 -0
- package/dist/tests/oom-handling/oom-gradual-worker.d.ts +2 -0
- package/dist/tests/oom-handling/oom-gradual-worker.d.ts.map +1 -0
- package/dist/tests/oom-handling/oom-gradual-worker.js +47 -0
- package/dist/tests/oom-handling/oom-handling.test.d.ts +2 -0
- package/dist/tests/oom-handling/oom-handling.test.d.ts.map +1 -0
- package/dist/tests/oom-handling/oom-handling.test.js +407 -0
- package/dist/tests/oom-handling/oom-metadata-worker.d.ts +2 -0
- package/dist/tests/oom-handling/oom-metadata-worker.d.ts.map +1 -0
- package/dist/tests/oom-handling/oom-metadata-worker.js +29 -0
- package/dist/tests/oom-handling/oom-worker.d.ts +2 -0
- package/dist/tests/oom-handling/oom-worker.d.ts.map +1 -0
- package/dist/tests/oom-handling/oom-worker.js +55 -0
- package/dist/tests/oom-handling/worker-memory.test.d.ts +2 -0
- package/dist/tests/oom-handling/worker-memory.test.d.ts.map +1 -0
- package/dist/tests/oom-handling/worker-memory.test.js +306 -0
- package/dist/types/common.d.ts +3 -0
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/workers.d.ts +58 -0
- package/dist/types/workers.d.ts.map +1 -1
- package/dist/uploader/uploader.d.ts.map +1 -1
- package/dist/uploader/uploader.js +13 -5
- package/dist/workers/create-worker.d.ts +20 -2
- package/dist/workers/create-worker.d.ts.map +1 -1
- package/dist/workers/create-worker.js +38 -3
- package/dist/workers/create-worker.test.js +113 -14
- package/dist/workers/spawn.d.ts +10 -1
- package/dist/workers/spawn.d.ts.map +1 -1
- package/dist/workers/spawn.js +77 -3
- package/package.json +2 -1
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const extraction_1 = require("../../types/extraction");
|
|
7
|
+
const mock_server_1 = require("../mock-server");
|
|
8
|
+
const test_helpers_1 = require("../test-helpers");
|
|
9
|
+
const extraction_2 = __importDefault(require("./extraction"));
|
|
10
|
+
describe('OOM handling', () => {
|
|
11
|
+
let mockServer;
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
mockServer = new mock_server_1.MockServer(3010);
|
|
14
|
+
await mockServer.start();
|
|
15
|
+
});
|
|
16
|
+
afterAll(async () => {
|
|
17
|
+
if (mockServer) {
|
|
18
|
+
await mockServer.stop();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
mockServer.clearRequests();
|
|
24
|
+
});
|
|
25
|
+
describe('basic OOM detection', () => {
|
|
26
|
+
it('should emit error event with OOM details when worker runs out of memory', async () => {
|
|
27
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
28
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
29
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
30
|
+
eventContextOverrides: {
|
|
31
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
32
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
33
|
+
},
|
|
34
|
+
executionMetadataOverrides: {
|
|
35
|
+
devrev_endpoint: `${baseUrl}`,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// Run the OOM worker - this should trigger OOM and emit an error event
|
|
39
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker');
|
|
40
|
+
const requests = mockServer.getRequests();
|
|
41
|
+
const lastRequest = requests[requests.length - 1];
|
|
42
|
+
// Expect last request to be emission of error event
|
|
43
|
+
expect(lastRequest.url).toContain('airdrop.external-extractor.message');
|
|
44
|
+
expect(lastRequest.method).toBe('POST');
|
|
45
|
+
// The event type should be an error event
|
|
46
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.DataExtractionError);
|
|
47
|
+
// The error should contain OOM information
|
|
48
|
+
expect(lastRequest.body.event_data).toBeDefined();
|
|
49
|
+
expect(lastRequest.body.event_data.error).toBeDefined();
|
|
50
|
+
expect(lastRequest.body.event_data.error.message).toContain('out of memory');
|
|
51
|
+
// OOM error info should be present
|
|
52
|
+
const oomErrorInfo = lastRequest.body.event_data.error.oom_error_info;
|
|
53
|
+
expect(oomErrorInfo).toBeDefined();
|
|
54
|
+
expect(oomErrorInfo.type).toBe('OOM_ERROR');
|
|
55
|
+
expect(oomErrorInfo.memoryLimitMb).toBeGreaterThan(0);
|
|
56
|
+
expect(oomErrorInfo.eventType).toBe(extraction_1.EventType.StartExtractingData);
|
|
57
|
+
}, 120000);
|
|
58
|
+
it('should keep parent thread stable when worker dies from OOM', async () => {
|
|
59
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
60
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
61
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
62
|
+
eventContextOverrides: {
|
|
63
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
64
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
65
|
+
},
|
|
66
|
+
executionMetadataOverrides: {
|
|
67
|
+
devrev_endpoint: `${baseUrl}`,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// Run the OOM worker
|
|
71
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker');
|
|
72
|
+
// If we get here, the parent thread survived
|
|
73
|
+
expect(true).toBe(true);
|
|
74
|
+
// Verify we can still make requests (parent is functional)
|
|
75
|
+
const requests = mockServer.getRequests();
|
|
76
|
+
expect(requests.length).toBeGreaterThan(0);
|
|
77
|
+
}, 120000);
|
|
78
|
+
});
|
|
79
|
+
describe('OOM with alreadyEmitted', () => {
|
|
80
|
+
it('should handle OOM gracefully when worker has done some work before crashing', async () => {
|
|
81
|
+
var _a, _b, _c, _d;
|
|
82
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
83
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
84
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
85
|
+
eventContextOverrides: {
|
|
86
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
87
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
88
|
+
},
|
|
89
|
+
executionMetadataOverrides: {
|
|
90
|
+
devrev_endpoint: `${baseUrl}`,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
// Run the worker that attempts to emit before causing OOM
|
|
94
|
+
// Use a larger memory limit (256MB) to give the worker time to initialize and emit
|
|
95
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-after-emit-worker', {
|
|
96
|
+
testMemoryLimitMb: 256,
|
|
97
|
+
});
|
|
98
|
+
const requests = mockServer.getRequests();
|
|
99
|
+
// Filter for callback requests (event emissions)
|
|
100
|
+
const callbackRequests = requests.filter((r) => r.url.includes('airdrop.external-extractor.message') &&
|
|
101
|
+
r.method === 'POST');
|
|
102
|
+
// Should have at least one event (either progress or error)
|
|
103
|
+
// The key is that the system handles this gracefully without crashing
|
|
104
|
+
expect(callbackRequests.length).toBeGreaterThanOrEqual(1);
|
|
105
|
+
// Check if we got a progress event (worker emitted before OOM)
|
|
106
|
+
const progressRequest = callbackRequests.find((r) => { var _a; return ((_a = r.body) === null || _a === void 0 ? void 0 : _a.event_type) === extraction_1.ExtractorEventType.DataExtractionProgress; });
|
|
107
|
+
// Check if we got an error event (OOM error)
|
|
108
|
+
const errorRequest = callbackRequests.find((r) => { var _a; return ((_a = r.body) === null || _a === void 0 ? void 0 : _a.event_type) === extraction_1.ExtractorEventType.DataExtractionError; });
|
|
109
|
+
// At least one of these should be present
|
|
110
|
+
expect(progressRequest || errorRequest).toBeTruthy();
|
|
111
|
+
// If progress was emitted, verify it's valid
|
|
112
|
+
if (progressRequest) {
|
|
113
|
+
expect((_a = progressRequest.body) === null || _a === void 0 ? void 0 : _a.event_context).toBeDefined();
|
|
114
|
+
}
|
|
115
|
+
// If error was emitted, verify it contains OOM info
|
|
116
|
+
if (errorRequest) {
|
|
117
|
+
expect((_d = (_c = (_b = errorRequest.body) === null || _b === void 0 ? void 0 : _b.event_data) === null || _c === void 0 ? void 0 : _c.error) === null || _d === void 0 ? void 0 : _d.oom_error_info).toBeDefined();
|
|
118
|
+
}
|
|
119
|
+
}, 120000);
|
|
120
|
+
});
|
|
121
|
+
describe('gradual memory leak OOM', () => {
|
|
122
|
+
it('should detect OOM from gradual memory consumption', async () => {
|
|
123
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
124
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
125
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
126
|
+
eventContextOverrides: {
|
|
127
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
128
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
129
|
+
},
|
|
130
|
+
executionMetadataOverrides: {
|
|
131
|
+
devrev_endpoint: `${baseUrl}`,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-gradual-worker');
|
|
135
|
+
const requests = mockServer.getRequests();
|
|
136
|
+
const lastRequest = requests[requests.length - 1];
|
|
137
|
+
expect(lastRequest.url).toContain('airdrop.external-extractor.message');
|
|
138
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.DataExtractionError);
|
|
139
|
+
expect(lastRequest.body.event_data.error.oom_error_info).toBeDefined();
|
|
140
|
+
}, 120000);
|
|
141
|
+
});
|
|
142
|
+
describe('OOM with disabled memory limits', () => {
|
|
143
|
+
it('should still function when memory limits are disabled', async () => {
|
|
144
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
145
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
146
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
147
|
+
eventContextOverrides: {
|
|
148
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
149
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
150
|
+
},
|
|
151
|
+
executionMetadataOverrides: {
|
|
152
|
+
devrev_endpoint: `${baseUrl}`,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
// Run with memory limits disabled - worker won't hit OOM limit
|
|
156
|
+
// but will eventually run out of system memory or hit timeout
|
|
157
|
+
// This test verifies the system doesn't crash when limits are disabled
|
|
158
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker', {
|
|
159
|
+
enableMemoryLimits: false,
|
|
160
|
+
testMemoryLimitMb: undefined,
|
|
161
|
+
});
|
|
162
|
+
// Parent should still be functional after worker exits
|
|
163
|
+
const requests = mockServer.getRequests();
|
|
164
|
+
expect(requests.length).toBeGreaterThan(0);
|
|
165
|
+
}, 120000);
|
|
166
|
+
});
|
|
167
|
+
describe('OOM for different event types', () => {
|
|
168
|
+
it('should handle OOM during metadata extraction', async () => {
|
|
169
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
170
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
171
|
+
eventType: extraction_1.EventType.StartExtractingMetadata,
|
|
172
|
+
eventContextOverrides: {
|
|
173
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
174
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
175
|
+
},
|
|
176
|
+
executionMetadataOverrides: {
|
|
177
|
+
devrev_endpoint: `${baseUrl}`,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-metadata-worker');
|
|
181
|
+
const requests = mockServer.getRequests();
|
|
182
|
+
const lastRequest = requests[requests.length - 1];
|
|
183
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.MetadataExtractionError);
|
|
184
|
+
expect(lastRequest.body.event_data.error.oom_error_info).toBeDefined();
|
|
185
|
+
expect(lastRequest.body.event_data.error.oom_error_info.eventType).toBe(extraction_1.EventType.StartExtractingMetadata);
|
|
186
|
+
}, 120000);
|
|
187
|
+
it('should handle OOM during attachments extraction', async () => {
|
|
188
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
189
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
190
|
+
eventType: extraction_1.EventType.StartExtractingAttachments,
|
|
191
|
+
eventContextOverrides: {
|
|
192
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
193
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
194
|
+
},
|
|
195
|
+
executionMetadataOverrides: {
|
|
196
|
+
devrev_endpoint: `${baseUrl}`,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-attachments-worker');
|
|
200
|
+
const requests = mockServer.getRequests();
|
|
201
|
+
const lastRequest = requests[requests.length - 1];
|
|
202
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.AttachmentExtractionError);
|
|
203
|
+
expect(lastRequest.body.event_data.error.oom_error_info).toBeDefined();
|
|
204
|
+
expect(lastRequest.body.event_data.error.oom_error_info.eventType).toBe(extraction_1.EventType.StartExtractingAttachments);
|
|
205
|
+
}, 120000);
|
|
206
|
+
it('should handle OOM during external sync units extraction', async () => {
|
|
207
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
208
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
209
|
+
eventType: extraction_1.EventType.StartExtractingExternalSyncUnits,
|
|
210
|
+
eventContextOverrides: {
|
|
211
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
212
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
213
|
+
},
|
|
214
|
+
executionMetadataOverrides: {
|
|
215
|
+
devrev_endpoint: `${baseUrl}`,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-external-sync-units-worker');
|
|
219
|
+
const requests = mockServer.getRequests();
|
|
220
|
+
const lastRequest = requests[requests.length - 1];
|
|
221
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.ExternalSyncUnitExtractionError);
|
|
222
|
+
expect(lastRequest.body.event_data.error.oom_error_info).toBeDefined();
|
|
223
|
+
expect(lastRequest.body.event_data.error.oom_error_info.eventType).toBe(extraction_1.EventType.StartExtractingExternalSyncUnits);
|
|
224
|
+
}, 120000);
|
|
225
|
+
});
|
|
226
|
+
describe('OOM error info completeness', () => {
|
|
227
|
+
it('should include all required fields in OOM error info', async () => {
|
|
228
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
229
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
230
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
231
|
+
eventContextOverrides: {
|
|
232
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
233
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
234
|
+
},
|
|
235
|
+
executionMetadataOverrides: {
|
|
236
|
+
devrev_endpoint: `${baseUrl}`,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker');
|
|
240
|
+
const requests = mockServer.getRequests();
|
|
241
|
+
const lastRequest = requests[requests.length - 1];
|
|
242
|
+
const oomErrorInfo = lastRequest.body.event_data.error.oom_error_info;
|
|
243
|
+
// Verify all required fields are present
|
|
244
|
+
expect(oomErrorInfo).toHaveProperty('type', 'OOM_ERROR');
|
|
245
|
+
expect(oomErrorInfo).toHaveProperty('message');
|
|
246
|
+
expect(oomErrorInfo).toHaveProperty('memoryLimitMb');
|
|
247
|
+
expect(oomErrorInfo).toHaveProperty('totalAvailableMemoryMb');
|
|
248
|
+
expect(oomErrorInfo).toHaveProperty('isLambda');
|
|
249
|
+
expect(oomErrorInfo).toHaveProperty('isLocalDevelopment');
|
|
250
|
+
expect(oomErrorInfo).toHaveProperty('exitCode');
|
|
251
|
+
expect(oomErrorInfo).toHaveProperty('eventType');
|
|
252
|
+
// Verify types
|
|
253
|
+
expect(typeof oomErrorInfo.message).toBe('string');
|
|
254
|
+
expect(typeof oomErrorInfo.memoryLimitMb).toBe('number');
|
|
255
|
+
expect(typeof oomErrorInfo.totalAvailableMemoryMb).toBe('number');
|
|
256
|
+
expect(typeof oomErrorInfo.isLambda).toBe('boolean');
|
|
257
|
+
expect(typeof oomErrorInfo.isLocalDevelopment).toBe('boolean');
|
|
258
|
+
expect(typeof oomErrorInfo.exitCode).toBe('number');
|
|
259
|
+
}, 120000);
|
|
260
|
+
it('should correctly identify local development environment in OOM info', async () => {
|
|
261
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
262
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
263
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
264
|
+
eventContextOverrides: {
|
|
265
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
266
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
267
|
+
},
|
|
268
|
+
executionMetadataOverrides: {
|
|
269
|
+
devrev_endpoint: `${baseUrl}`,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker');
|
|
273
|
+
const requests = mockServer.getRequests();
|
|
274
|
+
const lastRequest = requests[requests.length - 1];
|
|
275
|
+
const oomErrorInfo = lastRequest.body.event_data.error.oom_error_info;
|
|
276
|
+
// Test runs in local development mode
|
|
277
|
+
expect(oomErrorInfo.isLocalDevelopment).toBe(true);
|
|
278
|
+
expect(oomErrorInfo.isLambda).toBe(false);
|
|
279
|
+
}, 120000);
|
|
280
|
+
});
|
|
281
|
+
describe('memory limit edge cases', () => {
|
|
282
|
+
it('should handle very small memory limit (32MB)', async () => {
|
|
283
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
284
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
285
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
286
|
+
eventContextOverrides: {
|
|
287
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
288
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
289
|
+
},
|
|
290
|
+
executionMetadataOverrides: {
|
|
291
|
+
devrev_endpoint: `${baseUrl}`,
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
// Use a very small memory limit
|
|
295
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker', {
|
|
296
|
+
testMemoryLimitMb: 32,
|
|
297
|
+
});
|
|
298
|
+
const requests = mockServer.getRequests();
|
|
299
|
+
const lastRequest = requests[requests.length - 1];
|
|
300
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.DataExtractionError);
|
|
301
|
+
expect(lastRequest.body.event_data.error.oom_error_info.memoryLimitMb).toBe(32);
|
|
302
|
+
}, 120000);
|
|
303
|
+
it('should handle moderate memory limit (128MB)', async () => {
|
|
304
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
305
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
306
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
307
|
+
eventContextOverrides: {
|
|
308
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
309
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
310
|
+
},
|
|
311
|
+
executionMetadataOverrides: {
|
|
312
|
+
devrev_endpoint: `${baseUrl}`,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker', {
|
|
316
|
+
testMemoryLimitMb: 128,
|
|
317
|
+
});
|
|
318
|
+
const requests = mockServer.getRequests();
|
|
319
|
+
const lastRequest = requests[requests.length - 1];
|
|
320
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.DataExtractionError);
|
|
321
|
+
expect(lastRequest.body.event_data.error.oom_error_info.memoryLimitMb).toBe(128);
|
|
322
|
+
}, 120000);
|
|
323
|
+
});
|
|
324
|
+
describe('memory monitoring during OOM', () => {
|
|
325
|
+
it('should not cause issues when memory monitoring runs during OOM buildup', async () => {
|
|
326
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
327
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
328
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
329
|
+
eventContextOverrides: {
|
|
330
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
331
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
332
|
+
},
|
|
333
|
+
executionMetadataOverrides: {
|
|
334
|
+
devrev_endpoint: `${baseUrl}`,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
// Gradual worker gives time for memory monitoring to run
|
|
338
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-gradual-worker');
|
|
339
|
+
// Parent thread should still be functional
|
|
340
|
+
const requests = mockServer.getRequests();
|
|
341
|
+
expect(requests.length).toBeGreaterThan(0);
|
|
342
|
+
// Should still receive OOM error
|
|
343
|
+
const lastRequest = requests[requests.length - 1];
|
|
344
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.DataExtractionError);
|
|
345
|
+
}, 120000);
|
|
346
|
+
it('should properly clean up after OOM and allow subsequent operations', async () => {
|
|
347
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
348
|
+
// First OOM event
|
|
349
|
+
const event1 = (0, test_helpers_1.createEvent)({
|
|
350
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
351
|
+
eventContextOverrides: {
|
|
352
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
353
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
354
|
+
},
|
|
355
|
+
executionMetadataOverrides: {
|
|
356
|
+
devrev_endpoint: `${baseUrl}`,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
await (0, extraction_2.default)([event1], __dirname + '/oom-worker');
|
|
360
|
+
const requestsAfterFirst = mockServer.getRequests().length;
|
|
361
|
+
// Clear and run second OOM event
|
|
362
|
+
mockServer.clearRequests();
|
|
363
|
+
const event2 = (0, test_helpers_1.createEvent)({
|
|
364
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
365
|
+
eventContextOverrides: {
|
|
366
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
367
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
368
|
+
},
|
|
369
|
+
executionMetadataOverrides: {
|
|
370
|
+
devrev_endpoint: `${baseUrl}`,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
await (0, extraction_2.default)([event2], __dirname + '/oom-worker');
|
|
374
|
+
const requestsAfterSecond = mockServer.getRequests().length;
|
|
375
|
+
// Both runs should have completed and made requests
|
|
376
|
+
expect(requestsAfterFirst).toBeGreaterThan(0);
|
|
377
|
+
expect(requestsAfterSecond).toBeGreaterThan(0);
|
|
378
|
+
// Both should have emitted OOM errors
|
|
379
|
+
const lastRequest = mockServer.getRequests()[mockServer.getRequests().length - 1];
|
|
380
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.DataExtractionError);
|
|
381
|
+
}, 240000); // 4 minute timeout for two OOM tests
|
|
382
|
+
});
|
|
383
|
+
describe('race condition handling', () => {
|
|
384
|
+
it('should handle rapid OOM (error before any logging)', async () => {
|
|
385
|
+
const baseUrl = mockServer.getBaseUrl();
|
|
386
|
+
const event = (0, test_helpers_1.createEvent)({
|
|
387
|
+
eventType: extraction_1.EventType.StartExtractingData,
|
|
388
|
+
eventContextOverrides: {
|
|
389
|
+
callback_url: `${baseUrl}/internal/airdrop.external-extractor.message`,
|
|
390
|
+
worker_data_url: `${baseUrl}/internal/airdrop.external-worker`,
|
|
391
|
+
},
|
|
392
|
+
executionMetadataOverrides: {
|
|
393
|
+
devrev_endpoint: `${baseUrl}`,
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
// Very small limit causes near-instant OOM
|
|
397
|
+
await (0, extraction_2.default)([event], __dirname + '/oom-worker', {
|
|
398
|
+
testMemoryLimitMb: 16,
|
|
399
|
+
});
|
|
400
|
+
const requests = mockServer.getRequests();
|
|
401
|
+
const lastRequest = requests[requests.length - 1];
|
|
402
|
+
// Should still properly detect and report OOM
|
|
403
|
+
expect(lastRequest.body.event_type).toBe(extraction_1.ExtractorEventType.DataExtractionError);
|
|
404
|
+
expect(lastRequest.body.event_data.error.oom_error_info).toBeDefined();
|
|
405
|
+
}, 120000);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oom-metadata-worker.d.ts","sourceRoot":"","sources":["../../../src/tests/oom-handling/oom-metadata-worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("../../index");
|
|
4
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
6
|
+
/**
|
|
7
|
+
* Worker that causes OOM during metadata extraction.
|
|
8
|
+
* Tests OOM handling for EventType.StartExtractingMetadata.
|
|
9
|
+
*/
|
|
10
|
+
(0, index_1.processTask)({
|
|
11
|
+
task: async ({ adapter }) => {
|
|
12
|
+
console.log('OOM metadata worker starting');
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
const memoryHog = [];
|
|
15
|
+
while (true) {
|
|
16
|
+
const chunk = [];
|
|
17
|
+
for (let i = 0; i < 10000; i++) {
|
|
18
|
+
chunk.push({
|
|
19
|
+
data: 'metadata'.repeat(12),
|
|
20
|
+
index: i,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
memoryHog.push(chunk);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
onTimeout: async ({ adapter }) => {
|
|
27
|
+
await adapter.emit(index_1.ExtractorEventType.MetadataExtractionDone);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oom-worker.d.ts","sourceRoot":"","sources":["../../../src/tests/oom-handling/oom-worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("../../index");
|
|
4
|
+
/**
|
|
5
|
+
* Worker that intentionally causes an OOM error by allocating large arrays.
|
|
6
|
+
* This is used to test OOM detection and handling in the parent thread.
|
|
7
|
+
*
|
|
8
|
+
* Note: We use JavaScript arrays/objects to allocate V8 heap memory,
|
|
9
|
+
* not Buffers (which use external memory and aren't limited by resourceLimits).
|
|
10
|
+
*/
|
|
11
|
+
(0, index_1.processTask)({
|
|
12
|
+
task: async ({ adapter }) => {
|
|
13
|
+
console.log('OOM worker starting - will intentionally cause OOM');
|
|
14
|
+
// Array to hold references to prevent garbage collection
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const memoryHog = [];
|
|
17
|
+
try {
|
|
18
|
+
// Allocate memory in chunks until we run out
|
|
19
|
+
// Each chunk is approximately 1MB of heap memory (array of objects)
|
|
20
|
+
let totalAllocated = 0;
|
|
21
|
+
while (true) {
|
|
22
|
+
// Create an array of objects to consume V8 heap memory
|
|
23
|
+
// Each object with strings consumes heap memory
|
|
24
|
+
const chunk = [];
|
|
25
|
+
for (let i = 0; i < 10000; i++) {
|
|
26
|
+
chunk.push({
|
|
27
|
+
data: 'x'.repeat(100), // 100 bytes per string
|
|
28
|
+
index: i,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
memoryHog.push(chunk);
|
|
32
|
+
totalAllocated += 1; // Approximately 1MB per chunk
|
|
33
|
+
if (totalAllocated % 10 === 0) {
|
|
34
|
+
console.log(`Allocated approximately ${totalAllocated}MB of heap memory`);
|
|
35
|
+
}
|
|
36
|
+
// Small delay to allow logging (but not too long to avoid timeout)
|
|
37
|
+
if (totalAllocated % 50 === 0) {
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// This catch block may not be reached if OOM kills the process
|
|
44
|
+
console.error('Error during memory allocation:', error);
|
|
45
|
+
await adapter.emit(index_1.ExtractorEventType.DataExtractionError, {
|
|
46
|
+
error: { message: 'Memory allocation failed' },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
onTimeout: async ({ adapter }) => {
|
|
51
|
+
// This should not be called in OOM scenario
|
|
52
|
+
console.log('OOM worker timeout handler called');
|
|
53
|
+
await adapter.emit(index_1.ExtractorEventType.DataExtractionProgress);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-memory.test.d.ts","sourceRoot":"","sources":["../../../src/tests/oom-handling/worker-memory.test.ts"],"names":[],"mappings":""}
|