node-opcua-leak-detector 2.51.0

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014-2021 Etienne Rossignon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ module.exports = {
3
+ describeWithLeakDetector: require("./src/resource_leak_detector").describeWithLeakDetector
4
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "node-opcua-leak-detector",
3
+ "version": "2.51.0",
4
+ "description": "pure nodejs OPCUA SDK - module -leak-detector",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "lint": "tslint source/**/*.ts",
8
+ "clean": "node -e \"require('rimraf').sync('dist');\"",
9
+ "test": "echo no test"
10
+ },
11
+ "author": "Etienne Rossignon",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "chalk": "4.1.2",
15
+ "node-opcua-assert": "2.51.0",
16
+ "node-opcua-debug": "2.51.0",
17
+ "node-opcua-object-registry": "2.51.0"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git://github.com/node-opcua/node-opcua.git"
22
+ },
23
+ "keywords": [
24
+ "OPCUA",
25
+ "opcua",
26
+ "m2m",
27
+ "iot",
28
+ "opc ua",
29
+ "internet of things"
30
+ ],
31
+ "homepage": "http://node-opcua.github.io/",
32
+ "gitHead": "75feb111daf7ec65fa0111e4fa5beb8987fd4945"
33
+ }
@@ -0,0 +1,459 @@
1
+ "use strict";
2
+ Error.stackTraceLimit = Infinity;
3
+
4
+ const chalk = require("chalk");
5
+ const { assert } = require("node-opcua-assert");
6
+
7
+ const { ObjectRegistry } = require("node-opcua-object-registry");
8
+ const trace = false;
9
+
10
+
11
+ function get_stack() {
12
+ const stack = (new Error("Stack Trace recording")).stack.split("\n");
13
+ return stack.slice(2, 7).join("\n");
14
+ }
15
+
16
+ const monitor_intervals = false;
17
+
18
+
19
+ function ResourceLeakDetector() {
20
+ const self = this;
21
+
22
+ self.setIntervalCallCount = 0;
23
+ self.clearIntervalCallCount = 0;
24
+
25
+ self.setTimeoutCallCount = 0;
26
+ self.clearTimeoutCallCount = 0;
27
+ self.honoredTimeoutFuncCallCount = 0;
28
+ self.setTimeoutCallPendingCount = 0;
29
+
30
+ self.interval_map = {};
31
+ self.timeout_map = {};
32
+ }
33
+
34
+ /**
35
+ * @method verify_registry_counts
36
+ * @private
37
+ * @param info
38
+ * @return {boolean}
39
+ */
40
+ ResourceLeakDetector.prototype.verify_registry_counts = function(info) {
41
+ const errorMessages = [];
42
+
43
+ const self = this;
44
+
45
+ if (self.clearIntervalCallCount !== self.setIntervalCallCount) {
46
+ errorMessages.push(" setInterval doesn't match number of clearInterval calls : \n " +
47
+ " setIntervalCallCount = " + self.setIntervalCallCount +
48
+ " clearIntervalCallCount = " + self.clearIntervalCallCount);
49
+ }
50
+ if ((self.clearTimeoutCallCount + self.honoredTimeoutFuncCallCount) !== self.setTimeoutCallCount) {
51
+ errorMessages.push(" setTimeout doesn't match number of clearTimeout or achieved timer calls : \n " +
52
+ " setTimeoutCallCount = " + self.setTimeoutCallCount +
53
+ " clearTimeoutCallCount = " + self.clearTimeoutCallCount +
54
+ " honoredTimeoutFuncCallCount = " + self.honoredTimeoutFuncCallCount);
55
+ }
56
+ if (self.setTimeoutCallPendingCount !== 0) {
57
+ errorMessages.push(" setTimeoutCallPendingCount is not zero: some timer are still pending " +
58
+ self.setTimeoutCallPendingCount);
59
+ }
60
+
61
+ const monitoredResource = ObjectRegistry.registries;
62
+
63
+ let totalLeak = 0;
64
+ for (let i = 0; i < monitoredResource.length; i++) {
65
+ const res = monitoredResource[i];
66
+ if (res.count() !== 0) {
67
+ errorMessages.push(chalk.cyan(" some Resource have not been properly terminated: \n"));
68
+ errorMessages.push(" " + res.toString());
69
+ }
70
+ totalLeak += res.count();
71
+ }
72
+
73
+ if (errorMessages.length) {
74
+
75
+ if (!info.silent) {
76
+
77
+ console.log(chalk.bgWhite.red("+----------------------------------------------------------------------------------------+"));
78
+ console.log(chalk.bgWhite.red("| RESOURCE LEAK DETECTED !!! |"));
79
+ console.log(chalk.bgWhite.red("+----------------------------------------------------------------------------------------+"));
80
+
81
+ console.log("----------------------------------------------- more info");
82
+
83
+ console.log(chalk.cyan("test filename : "), self.ctx ? self.ctx.test.parent.file + " " + self.ctx.test.parent.title : "???");
84
+ console.log(chalk.cyan("setInterval/clearInterval leaks : "), Object.entries(self.interval_map).length);
85
+ for (const [key, value] of Object.entries(self.interval_map)) {
86
+ if (value && !value.disposed) {
87
+ console.log("key =", key, "value.disposed = ", value.disposed);
88
+ console.log(value.stack);//.split("\n"));
89
+ }
90
+ }
91
+ console.log(chalk.cyan("setTimeout/clearTimeout leaks : "), Object.entries(self.timeout_map).length);
92
+ for (const [key, value] of Object.entries(self.timeout_map)) {
93
+ if (value && !value.disposed) {
94
+ console.log("setTimeout key =", key, "value.disposed = ", value.disposed);
95
+ console.log(value.stack);//.split("\n"));
96
+ }
97
+ }
98
+ console.log(chalk.cyan("object leaks : "), totalLeak);
99
+ for (const resource of Object.values(monitoredResource)) {
100
+ if (resource.count() !== 0) {
101
+ console.log(" ", chalk.yellow(resource.getClassName()).padEnd(38), ":", resource.count());
102
+ }
103
+ }
104
+
105
+ console.log(errorMessages.join("\n"));
106
+
107
+
108
+ console.log("you can get trace information if you set NODEOPCUA_REGISTRY=DEBUG and rerun")
109
+ //
110
+ throw new Error("LEAKS !!!" + errorMessages.join("\n"));
111
+ }
112
+ }
113
+ };
114
+
115
+ global.hasResourceLeakDetector = false;
116
+ ResourceLeakDetector.prototype.start = function(info) {
117
+
118
+ global.ResourceLeakDetectorStarted = true;
119
+
120
+ const self = ResourceLeakDetector.singleton;
121
+ if (trace) {
122
+ console.log(" starting resourceLeakDetector");
123
+ }
124
+ assert(!self.setInterval_old, "resourceLeakDetector.stop hasn't been called !");
125
+ assert(!self.clearInterval_old, "resourceLeakDetector.stop hasn't been called !");
126
+ assert(!self.setTimeout_old, "resourceLeakDetector.stop hasn't been called !");
127
+ assert(!self.clearTimeout_old, "resourceLeakDetector.stop hasn't been called !");
128
+
129
+ self.setIntervalCallCount = 0;
130
+ self.clearIntervalCallCount = 0;
131
+
132
+ self.setInterval_old = global.setInterval;
133
+ self.clearInterval_old = global.clearInterval;
134
+
135
+ self.setTimeoutCallCount = 0;
136
+ self.clearTimeoutCallCount = 0;
137
+ self.setTimeoutCallPendingCount = 0;
138
+ self.honoredTimeoutFuncCallCount = 0;
139
+ self.setTimeout_old = global.setTimeout;
140
+ self.clearTimeout_old = global.clearTimeout;
141
+
142
+ self.interval_map = {};
143
+ self.timeout_map = {};
144
+
145
+ self.verify_registry_counts(self, info);
146
+
147
+ if (monitor_intervals) {
148
+ global.setTimeout = function(func, delay) {
149
+
150
+ assert(arguments.length === 2, "current limitation: setTimeout must be called with 2 arguments");
151
+ // detect invalid delays
152
+ assert(delay !== undefined);
153
+ assert(isFinite(delay));
154
+ if (delay < 0) {
155
+ console.log("GLOBAL#setTimeout called with a too small delay = " + delay.toString());
156
+ throw new Error("GLOBAL#setTimeout called with a too small delay = " + delay.toString());
157
+ }
158
+
159
+ // increase number of pending timers
160
+ self.setTimeoutCallPendingCount += 1;
161
+
162
+ // increase overall timeout counter;
163
+ self.setTimeoutCallCount += 1;
164
+
165
+ const key = self.setTimeoutCallCount;
166
+
167
+ const timeoutId = self.setTimeout_old(function() {
168
+
169
+ if (!self.timeout_map[key] || self.timeout_map[key].isCleared) {
170
+ // throw new Error("Invalid timeoutId, timer has already been cleared - " + key);
171
+ console.log("WARNING : setTimeout: Invalid timeoutId, timer has already been cleared - " + key);
172
+ return;
173
+ }
174
+ if (self.timeout_map[key].hasBeenHonored) {
175
+ throw new Error("setTimeout: " + key + " time out has already been honored");
176
+ }
177
+ self.honoredTimeoutFuncCallCount += 1;
178
+ self.setTimeoutCallPendingCount -= 1;
179
+
180
+ self.timeout_map[key].hasBeenHonored = true;
181
+ self.timeout_map[key].disposed = true;
182
+ func();
183
+
184
+ }, delay);
185
+
186
+ self.timeout_map[key] = {
187
+ timeoutId: timeoutId,
188
+ disposed: false,
189
+ stack: get_stack() // stack when created
190
+ };
191
+ return key + 100000;
192
+ };
193
+
194
+ global.clearTimeout = function(timeoutId) {
195
+ // workaround for a bug in 'backoff' module, which call clearTimeout with -1 ( invalid ide)
196
+ if (timeoutId === -1) {
197
+ console.log("warning clearTimeout is called with illegal timeoutId === 1, this call will be ignored ( backoff module bug?)");
198
+ return;
199
+ }
200
+
201
+ if (timeoutId >= 0 && timeoutId < 100000) {
202
+ throw new Error("clearTimeout has been called instead of clearInterval");
203
+ }
204
+ timeoutId -= 100000;
205
+
206
+ if (!self.timeout_map[timeoutId]) {
207
+ console.log("timeoutId" + timeoutId, " has already been discarded or doesn't exist");
208
+ console.log("self.timeout_map", self.timeout_map);
209
+ throw new Error("clearTimeout: Invalid timeoutId " + timeoutId + " this may happen if clearTimeout is called inside the setTimeout function");
210
+ }
211
+ if (self.timeout_map[timeoutId].isCleared) {
212
+ throw new Error("clearTimeout: Invalid timeoutId " + timeoutId + " time out has already been cleared");
213
+ }
214
+ if (self.timeout_map[timeoutId].hasBeenHonored) {
215
+ throw new Error("clearTimeout: Invalid timeoutId " + timeoutId + " time out has already been honored");
216
+ }
217
+
218
+ const data = self.timeout_map[timeoutId];
219
+ self.timeout_map[timeoutId] = null;
220
+
221
+ data.isCleared = true;
222
+ data.disposed = true;
223
+
224
+ self.setTimeoutCallPendingCount -= 1;
225
+
226
+ // increase overall timeout counter;
227
+ self.clearTimeoutCallCount += 1;
228
+
229
+ // call original clearTimeout
230
+ const retValue = self.clearTimeout_old(data.timeoutId);
231
+
232
+ //xx delete self.timeout_map[timeoutId];
233
+ return retValue;
234
+ };
235
+
236
+ }
237
+
238
+ global.setInterval = function(func, delay) {
239
+ assert(arguments.length === 2);
240
+ assert(delay !== undefined);
241
+ assert(isFinite(delay));
242
+ if (delay <= 10) {
243
+ throw new Error("GLOBAL#setInterval called with a too small delay = " + delay.toString());
244
+ }
245
+
246
+ // increase number of pending timers
247
+ self.setIntervalCallCount += 1;
248
+
249
+ const key = self.setIntervalCallCount;
250
+
251
+ const intervalId = self.setInterval_old(func, delay);
252
+
253
+ let stack = null;
254
+ try {
255
+ stack = get_stack();
256
+ }
257
+ catch (err) {
258
+ /** */
259
+ }
260
+ self.interval_map[key] = {
261
+ intervalId: intervalId,
262
+ disposed: false,
263
+ stack: stack
264
+ };
265
+
266
+ if (trace) {
267
+ console.log("setInterval \n", get_stack(), "\n");
268
+ }
269
+
270
+ return key;
271
+ };
272
+
273
+ global.clearInterval = function(intervalId) {
274
+
275
+ if (intervalId >= 100000) {
276
+ throw new Error("clearInterval has been called instead of clearTimeout");
277
+ }
278
+ self.clearIntervalCallCount += 1;
279
+
280
+ if (trace) {
281
+ console.log("clearInterval " + intervalId, get_stack());
282
+ }
283
+ const key = intervalId;
284
+
285
+ const data = self.interval_map[key];
286
+
287
+ self.interval_map[key] = null;
288
+ delete self.interval_map[key];
289
+
290
+ data.disposed = true;
291
+ const retValue = self.clearInterval_old(data.intervalId);
292
+
293
+ return retValue;
294
+ };
295
+
296
+ };
297
+
298
+ ResourceLeakDetector.prototype.check = function() {
299
+
300
+ };
301
+
302
+ ResourceLeakDetector.prototype.stop = function(info) {
303
+ if (!global.ResourceLeakDetectorStarted) {
304
+ return;
305
+ }
306
+ global.ResourceLeakDetectorStarted = false;
307
+
308
+ const self = ResourceLeakDetector.singleton;
309
+ if (trace) {
310
+ console.log(" stop resourceLeakDetector");
311
+ }
312
+ assert(typeof self.setInterval_old === "function", " did you forget to call resourceLeakDetector.start() ?");
313
+
314
+ global.setInterval = self.setInterval_old;
315
+ self.setInterval_old = null;
316
+
317
+ global.clearInterval = self.clearInterval_old;
318
+ self.clearInterval_old = null;
319
+
320
+ global.setTimeout = self.setTimeout_old;
321
+ self.setTimeout_old = null;
322
+
323
+ global.clearTimeout = self.clearTimeout_old;
324
+ self.clearTimeout_old = null;
325
+
326
+
327
+ const results = self.verify_registry_counts(info);
328
+
329
+ self.interval_map = {};
330
+ self.timeout_map = {};
331
+
332
+
333
+ // call garbage collector
334
+ if (typeof global.gc === "function") {
335
+ global.gc(true);
336
+ }
337
+
338
+ const doHeapdump = false;
339
+ if (doHeapdump) {
340
+ const heapdump = require('heapdump');
341
+ heapdump.writeSnapshot(function(err, filename) {
342
+ console.log('dump written to', filename);
343
+ });
344
+ }
345
+
346
+ return results;
347
+ };
348
+
349
+ ResourceLeakDetector.singleton = new ResourceLeakDetector();
350
+ const resourceLeakDetector = ResourceLeakDetector.singleton;
351
+
352
+ const { traceFromThisProjectOnly } = require("node-opcua-debug");
353
+
354
+ let testHasFailed = false;
355
+
356
+ exports.installResourceLeakDetector = function(isGlobal, func) {
357
+
358
+ const trace = traceFromThisProjectOnly();
359
+ testHasFailed = false;
360
+ if (isGlobal) {
361
+ before(function() {
362
+ testHasFailed = false;
363
+ const self = this;
364
+ resourceLeakDetector.ctx = self.test.ctx;
365
+ resourceLeakDetector.start();
366
+ // make sure we start with a garbage collected situation
367
+ if (global.gc) {
368
+ global.gc(true);
369
+ }
370
+ });
371
+ beforeEach(function() {
372
+ // make sure we start with a garbage collected situation
373
+ if (global.gc) {
374
+ global.gc(true);
375
+ }
376
+ });
377
+ if (func) {
378
+ func.call(this);
379
+ }
380
+ after(function() {
381
+ resourceLeakDetector.stop({ silent: testHasFailed });
382
+ resourceLeakDetector.ctx = false;
383
+ // make sure we start with a garbage collected situation
384
+ if (global.gc) {
385
+ global.gc(true);
386
+ }
387
+ });
388
+
389
+ } else {
390
+ beforeEach(function() {
391
+
392
+ if (global.gc) {
393
+ global.gc(true);
394
+ }
395
+
396
+ const self = this;
397
+ resourceLeakDetector.ctx = self.test.ctx;
398
+ resourceLeakDetector.start();
399
+ });
400
+ afterEach(function() {
401
+ resourceLeakDetector.stop({ silent: testHasFailed });
402
+ resourceLeakDetector.ctx = false;
403
+ // make sure we start with a garbage collected situation
404
+ if (global.gc) {
405
+ global.gc(true);
406
+ }
407
+ });
408
+
409
+ }
410
+ };
411
+
412
+ const global_describe = describe;
413
+ const global_it = it;
414
+ function replacement_it(testName, f) {
415
+ if (!f) { return; }
416
+ if (f.length) {
417
+ const f1 = function(done) {
418
+ f.call(this, (err) => {
419
+ if (err) {
420
+ testHasFailed = true;
421
+ }
422
+ done(err);
423
+ });
424
+ }
425
+ global_it(testName, f1);
426
+ return;
427
+ }
428
+ const ff = async function() {
429
+ let r;
430
+ try {
431
+ r = await f.call(this);
432
+ }
433
+ catch (err) {
434
+ testHasFailed = true;
435
+ throw err;
436
+ }
437
+ return r;
438
+ }
439
+ global_it(testName, ff);
440
+ }
441
+ assert(typeof global_describe === "function", " expecting mocha to be defined");
442
+
443
+
444
+ let g_inDescribeWithLeakDetector = false;
445
+ exports.describeWithLeakDetector = function(message, func) {
446
+ if (g_inDescribeWithLeakDetector) {
447
+ return global_describe(message, func);
448
+ }
449
+ g_inDescribeWithLeakDetector = true;
450
+ global.it = replacement_it;
451
+ global_describe.call(this, message, function() {
452
+ exports.installResourceLeakDetector.call(this, true, func);
453
+ g_inDescribeWithLeakDetector = false;
454
+ global.it = global_it;
455
+ });
456
+ };
457
+
458
+
459
+