Skip to content

Commit fa9c21b

Browse files
authored
Merge pull request #7 from daniloisr/handle-async-context
Fix context between two async requests
2 parents 426fb18 + 7f9fa5e commit fa9c21b

File tree

13 files changed

+185
-15
lines changed

13 files changed

+185
-15
lines changed

.travis.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
language: node_js
22
node_js:
3-
- "4"
4-
- "5"
3+
- "8"
54
- "stable"
65

76
sudo: required

lib/async-context.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const asyncHooks = require('async_hooks');
4+
5+
class AsyncContext {
6+
constructor() {
7+
this.map = new Map();
8+
asyncHooks.createHook({
9+
init: (id, _type, triggerId) => {
10+
if (this.map.has(triggerId))
11+
this.map.set(id, this.map.get(triggerId));
12+
},
13+
destroy: (id) => this.map.delete(id)
14+
}).enable();
15+
}
16+
17+
get() {
18+
const id = asyncHooks.executionAsyncId();
19+
if (this.map.has(id))
20+
return this.map.get(id);
21+
}
22+
23+
set(val) {
24+
this.map.set(asyncHooks.executionAsyncId(), val);
25+
}
26+
}
27+
28+
module.exports = new AsyncContext();

lib/middlewares/express.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
'use strict';
22

3+
const asyncContext = require('../async-context');
4+
35
module.exports = {
46
buildMiddleware: function(provider) {
57
return function(req, res, next) {
68
provider.handler(req, res, next);
79
};
810
},
9-
mainMiddleware: function(enable, authorize, handleRequest) {
11+
mainMiddleware: function(enable, authorize, handleRequest, cls) {
1012
return function(req, res, next) {
1113
handleRequest(enable, authorize, req, res).then((handled) => {
1214
res.locals.miniprofiler = req.miniprofiler;
1315

16+
asyncContext.set(req.miniprofiler);
17+
Object.defineProperty(req, 'miniprofiler', { get: () => asyncContext.get() });
18+
1419
var render = res.render;
1520
res.render = function() {
1621
var renderArguments = arguments;
@@ -24,4 +29,4 @@ module.exports = {
2429
}).catch(next);
2530
};
2631
}
27-
};
32+
};

lib/middlewares/hapi.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const asyncContext = require('../async-context');
4+
35
module.exports = {
46
buildMiddleware: function(provider) {
57
var plugin = {
@@ -25,7 +27,9 @@ module.exports = {
2527
register: (server, options, next) => {
2628
server.ext('onRequest', function(request, reply) {
2729
handleRequest(enable, authorize, request.raw.req, request.raw.res).then((handled) => {
28-
request.app.miniprofiler = request.raw.req.miniprofiler;
30+
asyncContext.set(request.raw.req.miniprofiler);
31+
Object.defineProperty(request.app, 'miniprofiler', { get: () => asyncContext.get() });
32+
Object.defineProperty(request.raw.req, 'miniprofiler', { get: () => asyncContext.get() });
2933

3034
if (!handled)
3135
reply.continue();

lib/middlewares/koa.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const asyncContext = require('../async-context');
4+
35
module.exports = {
46
buildMiddleware: function(provider) {
57
return function *(next) {
@@ -12,7 +14,10 @@ module.exports = {
1214
mainMiddleware: function(enable, authorize, handleRequest) {
1315
return function *(next) {
1416
var handled = yield handleRequest(enable, authorize, this.req, this.res);
15-
this.state.miniprofiler = this.req.miniprofiler;
17+
18+
asyncContext.set(this.req.miniprofiler);
19+
Object.defineProperty(this.state, 'miniprofiler', { get: () => asyncContext.get() });
20+
Object.defineProperty(this.req, 'miniprofiler', { get: () => asyncContext.get() });
1621

1722
if (this.render) {
1823
var render = this.render;

lib/miniprofiler.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ function handleRequest(enable, authorize, req, res) {
7474
}
7575

7676
if (!requestPath.startsWith(resourcePath)) {
77-
var id = startProfiling(req, enabled, authorized);
77+
var extension = startProfiling(req, enabled, authorized);
7878
if (enabled) {
7979
res.on('finish', () => {
80-
stopProfiling(req);
80+
stopProfiling(extension, req);
8181
});
82-
res.setHeader('X-MiniProfiler-Ids', `["${id}"]`);
82+
res.setHeader('X-MiniProfiler-Ids', `["${extension.id}"]`);
8383
}
8484
return resolve(false);
8585
}
@@ -279,21 +279,19 @@ function include(id) {
279279
};
280280

281281
request.miniprofiler = currentRequestExtension;
282-
return currentRequestExtension.id;
282+
283+
return currentRequestExtension;
283284
}
284285

285286
/*
286287
* Stops profiling the given request.
287288
*/
288-
function stopProfiling(request){
289-
var extension = request.miniprofiler;
289+
function stopProfiling(extension, request){
290290
var time = process.hrtime();
291291

292292
extension.stopTime = time;
293293
extension.stepGraph.stopTime = time;
294294

295-
delete request.miniprofiler;
296-
297295
var json = describePerformance(extension, request);
298296
storage.set(extension.id, JSON.stringify(json));
299297
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "miniprofiler",
3-
"version": "1.2.3",
3+
"version": "2.0.0",
44
"description": "A simple but effective mini-profiler.",
55
"main": "lib/miniprofiler.js",
66
"scripts": {
@@ -46,6 +46,7 @@
4646
"koa": "^1.2.1",
4747
"koa-route": "^2.4.2",
4848
"koa-views": "^4.1.0",
49+
"debug": "^2.6.1",
4950
"mocha": "^2.5.3",
5051
"pug": "^2.0.0-beta2",
5152
"redis": "^2.6.1",

tests/concurrent-async-test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
var expect = require('chai').expect;
4+
5+
module.exports = function(server) {
6+
describe('Concurrent Async Requests', function() {
7+
before(server.setUp.bind(null, 'async'));
8+
after(server.tearDown);
9+
10+
it('Each profile runs on its own context', function(done) {
11+
let countDone = 0;
12+
const partialDone = () => { if (++countDone === 2) done(); };
13+
14+
server.get('/', (err, response) => {
15+
var ids = JSON.parse(response.headers['x-miniprofiler-ids']);
16+
expect(ids).to.have.lengthOf(1);
17+
18+
server.post('/mini-profiler-resources/results/', { id: ids[0], popup: 1 }, (err, response, body) => {
19+
var result = JSON.parse(body);
20+
expect(result.Root.CustomTimings.async).to.have.lengthOf(2);
21+
partialDone();
22+
});
23+
});
24+
25+
server.get('/?once=true', (err, response) => {
26+
var ids = JSON.parse(response.headers['x-miniprofiler-ids']);
27+
expect(ids).to.have.lengthOf(1);
28+
29+
server.post('/mini-profiler-resources/results/', { id: ids[0], popup: 1 }, (err, response, body) => {
30+
var result = JSON.parse(body);
31+
expect(result.Root.CustomTimings.async).to.have.lengthOf(1);
32+
partialDone();
33+
});
34+
});
35+
});
36+
});
37+
};

tests/servers/async-provider.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
module.exports = function(obj) {
4+
return {
5+
name: 'dummy-async',
6+
handler: function(req, res, next) {
7+
obj.asyncFn = function() {
8+
const timing = req.miniprofiler.startTimeQuery('async', 'dummy call');
9+
10+
return new Promise(resolve => {
11+
setTimeout(() => {
12+
req.miniprofiler.stopTimeQuery(timing);
13+
resolve();
14+
}, 25);
15+
});
16+
};
17+
18+
next();
19+
}
20+
};
21+
};

tests/servers/dummy-module.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
module.exports = {
4+
asyncFn: () => Promise.resolve()
5+
};

0 commit comments

Comments
 (0)