Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Returns the current execution context identified with the current asyncId.

Runs a given function under a dedicated AsyncResource, exposing given initial context to the process and it's child processes.

### monitor(): ExecutionMapUsage

Returns an monitoring report over the current execution map resources

### API Usage


Expand Down Expand Up @@ -107,6 +111,7 @@ Promise.resolve().then(() => {
}, 1000);

console.log(Context.get()); // outputs: {"value": true}
console.log(Context.monitor) // Will result with monitor result
});
});
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-execution-context",
"version": "1.1.4",
"version": "1.1.5",
"description": "Provides execution context wrapper for node JS, can be used to create execution wrapper for handling requests and more",
"author": "Oded Goldglas <odedglas@gmail.com>",
"license": "ISC",
Expand Down
6 changes: 4 additions & 2 deletions src/hooks/constants.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/**
* The excluded async process from our context map.
* Connections async processes tend not to be destroyed, which potentially will cause memory leak issues.
* We can skip those types assuming the execution context won't be used for process types.
* We can skip those types assuming the execution context won't be used for these process types.
* @type {Set<String>}
*/
exports.EXCLUDED_ASYNC_TYPES = new Set([
'DNSCHANNEL',
'TLSWRAP',
'TCPWRAP',
'HTTPPARSER',
'ZLIB'
'ZLIB',
'UDPSENDWRAP',
'UDPWRAP'
]);
10 changes: 7 additions & 3 deletions src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ const init = (executionContextMap) => (asyncId, type, triggerAsyncId) => {

// Setting child process entry as ref to parent context
executionContextMap.set(asyncId, {
ref
ref,
type,
created: Date.now()
});

const { context = {}, children = [] } = executionContextMap.get(ref);
const { context = {}, children = [], ...meta } = executionContextMap.get(ref);

// Adding current async as child to parent context in order to control cleanup better
executionContextMap.set(ref, {
...meta,
context,
children: [...children, asyncId]
});
Expand All @@ -53,7 +56,7 @@ const init = (executionContextMap) => (asyncId, type, triggerAsyncId) => {
* @param {Number} ref - The parent process ref asyncId
*/
const onChildProcessDestroy = (executionContextMap, asyncId, ref) => {
const { children: parentChildren, context } = executionContextMap.get(ref);
const { children: parentChildren, context, ...meta } = executionContextMap.get(ref);
const children = parentChildren.filter((id) => id !== asyncId);

// Parent context will be released upon last child removal
Expand All @@ -64,6 +67,7 @@ const onChildProcessDestroy = (executionContextMap, asyncId, ref) => {
}

executionContextMap.set(ref, {
...meta,
context,
children
});
Expand Down
8 changes: 4 additions & 4 deletions src/hooks/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('Context / Hooks', () => {
it('Register sub process as ref entry under root process', () => {
expect(executionMap.size).toEqual(2);

expect(executionMap.get(childAsyncId)).toEqual({
expect(executionMap.get(childAsyncId)).toMatchObject({
ref: triggerAsyncId
});
});
Expand All @@ -76,7 +76,7 @@ describe('Context / Hooks', () => {
const nestedChildAsyncId = await spawn(childAsyncId);

expect(executionMap.size).toEqual(3);
expect(executionMap.get(nestedChildAsyncId)).toEqual({
expect(executionMap.get(nestedChildAsyncId)).toMatchObject({
ref: triggerAsyncId
});

Expand Down Expand Up @@ -152,11 +152,11 @@ describe('Context / Hooks', () => {
it('Triggers parent cleanup when all child process died', (done) => {
children.forEach(destroy);

process.nextTick(() => {
setTimeout(() => {
expect(executionMap.size).toEqual(0);

done();
});
}, 200)
});
});
});
Expand Down
12 changes: 11 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const asyncHooks = require('async_hooks');
const ExecutionContextResource = require('./lib/ExecutionContextResource')
const { isProduction } = require('./lib');
const { isProduction, monitorMap } = require('./lib');
const { create: createHooks } = require('./hooks');
const { ExecutionContextErrors } = require('./constants');

Expand Down Expand Up @@ -48,7 +48,9 @@ const createExecutionContext = () => {
if (executionContextMap.has(asyncId)) handleError(ExecutionContextErrors.CONTEXT_ALREADY_DECLARED);

executionContextMap.set(asyncId, {
asyncId,
context: { ...initialContext, executionId: asyncId },
created: Date.now(),
children: []
});
},
Expand Down Expand Up @@ -105,6 +107,14 @@ const createExecutionContext = () => {

fn();
});
},

/**
* Monitors current execution map usage
* @return {ExecutionMapUsage}
*/
monitor: () => {
return monitorMap(executionContextMap);
}
};

Expand Down
39 changes: 38 additions & 1 deletion src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,45 @@ const PRODUCTION = 'production';

const env = process.env.NODE_ENV || PRODUCTION;

/**
* Calculates a duration between a given moment and now
* @param {Number} now - The current time
* @param {Number} created - The created time to calculate it's duration
* @return {Number}
*/
const getDuration = (now, created) => now - created

module.exports = {
env,

isProduction: (environment = env) => environment === PRODUCTION,
isUndefined: (thing) => [null, undefined].includes(thing)

isUndefined: (thing) => [null, undefined].includes(thing),

/**
* Returns a monitoring report over the "executionContext" memory usage.
* @param {ExecutionContextMap} executionContextMap The execution map to monitor
* @return {ExecutionMapUsage}
*/
monitorMap: (executionContextMap) => {
const now = Date.now();
const entries = [...executionContextMap.values()]
.filter(({ children }) => !!children)
.map(({ asyncId, created, children, context = {} }) => ({
asyncId,
created,
contextSize: JSON.stringify(context).length,
duration: getDuration(now, created),
children: children.map((childId) => {
const { type, created } = executionContextMap.get(childId);

return { asyncId: childId, type, created, duration: getDuration(now, created) }
})
}));

return {
size: executionContextMap.size,
entries
}
}
};
52 changes: 52 additions & 0 deletions src/lib/spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const lib = require ('.');
const Context = require('../');

describe('Lib', () => {
describe('isProduction', () => {
Expand Down Expand Up @@ -32,4 +33,55 @@ describe('Lib', () => {
expect(lib.isUndefined(val)).toBeTruthy();
});
});

describe('monitorMap', () => {
describe('When no context is open', () => {
let report;
beforeEach(() => {
report = Context.monitor();
});
it('Returns empty usage', () => {
expect(report.size).toEqual(0);
});

it('Return empty array entries', () => {
expect(Array.isArray(report.entries)).toBeTruthy();
expect(report.entries).toHaveLength(0);
});
});

describe('When context is created', () => {
const contextAware = (fn) => {
Context.create({ value: true });
fn();
};

const spwan = () => new Promise((resolve) => setTimeout(resolve, 100));

describe('When a single process is present', () => {
it('Reports with empty usage', () => {
contextAware(() => {
const report = Context.monitor();

expect(report.size).toEqual(1);
expect(report.entries).toHaveLength(1);
expect(report.entries[0].children).toHaveLength(0);
});
});
});

describe('When sub process is present', () => {
it('Reports root context entries', (done) => {
contextAware(() => {
spwan();
const report = Context.monitor();

expect(report.size > 0).toBeTruthy();
expect(report.entries.length > 0).toBeTruthy();
done()
});
});
});
});
});
});
29 changes: 27 additions & 2 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ interface HookCallbacks {
destroy?(asyncId: number): void;
}

interface ExecutionMapUsageBaseEntry {
asyncId: number;
created: number;
duration: number;
}

interface ExecutionMapUsageChildEntry extends ExecutionMapUsageBaseEntry {
type: string;
}

interface ExecutionMapUsageEntry extends ExecutionMapUsageBaseEntry {
asyncId: number;
children: ExecutionMapUsageChildEntry[];
}

interface ExecutionMapUsage {
size: number;
entries: ExecutionMapUsageEntry[];
}

interface ExecutionContextAPI {

/**
Expand All @@ -48,12 +68,17 @@ interface ExecutionContextAPI {
/**
* Gets the current async process execution context.
*/
get(): object
get(): object;

/**
* Runs a given function within an async resource context
* @param fn
* @param initialContext
*/
run(fn: Function, initialContext: object): void
run(fn: Function, initialContext: object): void;

/**
* Monitors the current execution map usage
*/
monitor(): ExecutionMapUsage;
}