Comparing version 0.5.0 to 0.6.0
122
index.js
'use strict'; | ||
const asyncHooks = require('async_hooks'); | ||
const v8 = require('v8'); | ||
const vm = require('vm'); | ||
let gc = global.gc; | ||
if (!gc) { | ||
v8.setFlagsFromString('--expose_gc'); | ||
gc = vm.runInNewContext('gc'); | ||
} | ||
// Module global variables. | ||
const resourceTree = {}; | ||
const contexts = {}; | ||
const contextAges = {}; | ||
const childResources = {}; | ||
@@ -21,3 +13,49 @@ const parentResources = {}; | ||
let createdContextNumberSinceLastCleanCheck = 0; | ||
const CLEAN_CHECK_CONTEXT_SIZE = process.env.CONTEXTOR_CLEAN_CHECK_CONTEXT_SIZE || 100; | ||
const CONTEXT_TTL = process.env.CONTEXTOR_CONTEXT_TTL || 6e4; | ||
/** | ||
* Retrieve context id for an async resource. | ||
* @param {number} asyncId - The resource id. | ||
* @returns {number|undefined} The context id or undefined if not in a context. | ||
*/ | ||
function retrieveContextId(asyncId) { | ||
let contextId = asyncId; | ||
while (contextId !== undefined && !(contextId in contexts)) { | ||
contextId = resourceTree[contextId]; | ||
} | ||
return contextId; | ||
} | ||
/** | ||
* Retrieve current context id. | ||
* @returns {number|undefined} The current context id or undefined if not in a context. | ||
*/ | ||
function retrieveCurrentContextId() { | ||
return retrieveContextId(asyncHooks.executionAsyncId()); | ||
} | ||
/** | ||
* Retrieve current context. | ||
* @returns {Object} The current context. | ||
* @throws {ReferenceError} On missing current context. | ||
*/ | ||
function retrieveCurrentContext() { | ||
const asyncId = retrieveCurrentContextId(); | ||
const exists = asyncId in contexts; | ||
if (!exists) { | ||
throw new ReferenceError( | ||
'No current context found; use \'create\' method to create one' | ||
); | ||
} | ||
return contexts[asyncId]; | ||
} | ||
/** | ||
* Async hook init callback. | ||
@@ -29,6 +67,2 @@ * - build resource tree allowing to resolve current context | ||
function init(asyncId, type, triggerAsyncId) { | ||
if (!triggerAsyncId) { | ||
return; | ||
} | ||
resourceTree[asyncId] = triggerAsyncId; | ||
@@ -57,3 +91,3 @@ | ||
// Clean memory. | ||
// Clean memory references. | ||
const resources = [asyncId].concat(parentResources[asyncId]); | ||
@@ -78,6 +112,8 @@ | ||
const destroyed = parentAsyncId in destroyedResources; | ||
const hasContext = !!retrieveContextId(parentAsyncId); | ||
if (!hasChildren && destroyed) { | ||
if ((!hasChildren && destroyed) || !hasContext) { | ||
delete resourceTree[parentAsyncId]; | ||
delete contexts[parentAsyncId]; | ||
delete contextAges[parentAsyncId]; | ||
delete childResources[parentAsyncId]; | ||
@@ -98,38 +134,26 @@ delete parentResources[parentAsyncId]; | ||
/** | ||
* Retrieve current context id. | ||
* @returns {number|undefined} The current context id. | ||
* Clean deprecated and useless resources. | ||
*/ | ||
function retrieveCurrentContextId() { | ||
let asyncId = asyncHooks.executionAsyncId(); | ||
function cleanResources() { | ||
const now = Date.now(); | ||
while (asyncId !== undefined && !(asyncId in contexts)) { | ||
asyncId = resourceTree[asyncId]; | ||
// Clean expired contexts (in case a destroy has not been fired). | ||
if (CONTEXT_TTL) { | ||
Object.getOwnPropertyNames(contexts).forEach((asyncId) => { | ||
if (now - contextAges[asyncId] >= CONTEXT_TTL) { | ||
delete contexts[asyncId]; | ||
delete contextAges[asyncId]; | ||
} | ||
}); | ||
} | ||
return asyncId; | ||
// Clean resources not attached to a context. | ||
Object.getOwnPropertyNames(resourceTree).forEach((asyncId) => { | ||
if (!retrieveContextId(asyncId)) { | ||
destroy(asyncId); | ||
} | ||
}); | ||
} | ||
/** | ||
* Retrieve current context. | ||
* @returns {Object} The current context. | ||
* @throws {ReferenceError} On missing current context. | ||
*/ | ||
function retrieveCurrentContext() { | ||
const asyncId = retrieveCurrentContextId(); | ||
const exists = asyncId in contexts; | ||
if (!exists) { | ||
throw new ReferenceError( | ||
'No current context found; use \'create\' method to create one' | ||
); | ||
} | ||
return contexts[asyncId]; | ||
} | ||
let createdContextNumberSinceLastCleanCheck = 0; | ||
const CLEAN_CHECK_CONTEXT_SIZE = process.env.CONTEXTOR_CLEAN_CHECK_CONTEXT_SIZE || 100; | ||
const DESTROY_STACK_SIZE = process.env.CONTEXTOR_DESTROY_STACK_SIZE || 1000; | ||
/** | ||
* Create a new context. | ||
@@ -141,11 +165,7 @@ * @returns {self} | ||
contexts[asyncId] = {}; | ||
contextAges[asyncId] = Date.now(); | ||
// Call manual garbage collector execution on garbage stacking. | ||
if (createdContextNumberSinceLastCleanCheck >= CLEAN_CHECK_CONTEXT_SIZE) { | ||
if (Object.getOwnPropertyNames(destroyedResources).length >= DESTROY_STACK_SIZE) { | ||
gc(); | ||
} | ||
if (++createdContextNumberSinceLastCleanCheck >= CLEAN_CHECK_CONTEXT_SIZE) { | ||
cleanResources(); | ||
createdContextNumberSinceLastCleanCheck = 0; | ||
} else { | ||
createdContextNumberSinceLastCleanCheck++; | ||
} | ||
@@ -152,0 +172,0 @@ |
{ | ||
"name": "contextor", | ||
"description": "Package allowing to pass a context along an asynchronous process", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"author": "Thomas Prelot <tprelot@gmail.com> (https://github.com/Gnucki)", | ||
@@ -28,7 +28,7 @@ "contributors": [], | ||
"lint-fix": "gnodi-lint --fix", | ||
"test": "NODE_ENV='test' mocha --recursive index.js test.js", | ||
"test": "NODE_ENV=test CONTEXTOR_CLEAN_CHECK_CONTEXT_SIZE=2 CONTEXTOR_CONTEXT_TTL=200 mocha --recursive index.js test.js", | ||
"test-coverage": "nyc npm test && nyc report --reporter text-summary", | ||
"test-coveralls": "nyc npm test && nyc report --reporter text-lcov | coveralls", | ||
"test-debug": "NODE_ENV='test' mocha --recursive --full-trace --check-leaks index.js test.js", | ||
"test-watch": "NODE_ENV='test' mocha -w -b --recursive index.js test.js", | ||
"test-debug": "NODE_ENV=test CONTEXTOR_CLEAN_CHECK_CONTEXT_SIZE=2 CONTEXTOR_CONTEXT_TTL=200 mocha --recursive --full-trace --check-leaks index.js test.js", | ||
"test-watch": "NODE_ENV=test CONTEXTOR_CLEAN_CHECK_CONTEXT_SIZE=2 CONTEXTOR_CONTEXT_TTL=200 mocha -w -b --recursive index.js test.js", | ||
"preversion": "npm run check", | ||
@@ -35,0 +35,0 @@ "postversion": "git push && git push --tags" |
@@ -65,2 +65,3 @@ # contextor | ||
- [Get a value in the current context](#get-a-value-in-the-current-context) | ||
- [Customize cleaning](#customize-cleaning) | ||
- [Debugging](#debugging) | ||
@@ -111,2 +112,7 @@ - [Testing](#testing) | ||
### Customize cleaning | ||
Some environment variables are available in order to customize context cleaning: | ||
- `CONTEXTOR_CLEAN_CHECK_CONTEXT_SIZE`: number of created contexts before a cleaning is executed (default: 100) | ||
- `CONTEXTOR_CONTEXT_TTL`: TTL of contexts in ms; set to 0 to make it infinite (default: 6e4) | ||
### Debugging | ||
@@ -113,0 +119,0 @@ Contextor create context for async hooks. A bad usage can lead to memory leaks.<br> |
32
test.js
@@ -138,2 +138,34 @@ 'use strict'; | ||
}); | ||
it('should clean expired contexts', (done) => { | ||
let memoryUsage = contextor.getMemoryUsage(); | ||
expect(memoryUsage.sizes.contexts).to.be.greaterThan(1); | ||
setTimeout(() => { | ||
contextor.create(); | ||
contextor.create(); | ||
memoryUsage = contextor.getMemoryUsage(); | ||
expect(memoryUsage.sizes.contexts).equal(1); | ||
setTimeout(() => { | ||
contextor.create(); | ||
contextor.create(); | ||
memoryUsage = contextor.getMemoryUsage(); | ||
expect(memoryUsage.sizes.contexts).equal(1); | ||
done(); | ||
}, 400); | ||
}, 400); | ||
setTimeout(() => { | ||
contextor.create(); | ||
contextor.create(); | ||
memoryUsage = contextor.getMemoryUsage(); | ||
expect(memoryUsage.sizes.contexts).equal(2); | ||
}, 500); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 2 instances in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
21785
394
171
3