Upload
islam-sharabash
View
472
Download
2
Embed Size (px)
Citation preview
Continuation-Local-Storage and the Magic of AsyncListener
Islam Sharabash @ibashes
@DataHero
Agenda• Problems caused by the event loop of Node.js
• Solving problems with AsyncListener API
• How AsyncListener works
Mo Node Mo ProblemsThings that are hard with the event loop:
• Having a request ID propagate throughout code
• Short stack traces from errors in async functions
• Profiling async functions is painful
Mo Node Mo ProblemsThings that are hard with the event loop:
• Having a request ID propagate throughout code
• Short stack traces from errors in async functions
• Profiling async functions is painful
Propagating Request ID (naive)
Propagating Request ID (naive)
var ctx = {rid: uuid.v4()}; users.save(ctx, attrs, function(error, userId) { users.load(ctx, userId, function(error, user) { log.info(‘user created’, {rid: ctx.rid, userId: user.id}); mailer.sendWelcome(ctx, user, function(error) { log.info(‘welcome email sent’, {rid: ctx.rid, userId: user.id}); }); });});
Propagating Request ID (naive)
var ctx = {rid: uuid.v4()}; users.save(ctx, attrs, function(error, userId) { users.load(ctx, userId, function(error, user) { log.info(‘user created’, {rid: ctx.rid, userId: user.id}); mailer.sendWelcome(ctx, user, function(error) { log.info(‘welcome email sent’, {rid: ctx.rid, userId: user.id}); }); });});
Propagating Request ID (naive)
var ctx = {rid: uuid.v4()}; users.save(ctx, attrs, function(error, userId) { users.load(ctx, userId, function(error, user) { log.info(‘user created’, {rid: ctx.rid, userId: user.id}); mailer.sendWelcome(ctx, user, function(error) { log.info(‘welcome email sent’, {rid: ctx.rid, userId: user.id}); }); });});
Mo Node Mo ProblemsThings that are hard with the event loop:
• Having a request ID propagate throughout code
• Short stack traces from errors in async functions
• Profiling async functions is painful
Short Stack Traces
users.save(attrs, function(error, userId) { if (error) { return callback(error); } users.load(userId, function(error, user) { if (error) { return callback(error); } mailers.sendWelcome(user, function(error) { if (error) { return callback(error); } }); });});
Short Stack Traces
users.save(attrs, function(error, userId) { if (error) { return callback(error); } users.load(userId, function(error, user) { if (error) { return callback(error); } mailers.sendWelcome(user, function(error) { if (error) { return callback(error); } }); });});
Short Stack Traces
Error: ER_PARSE_ERROR You have an error in your SQL syntax...at getError (lib/models/database.js:479:24)at Query._callback (lib/models/database.js:110:34) at Query.Sequence.end (node_modules/mysql/lib/protocol/sequences/Sequence.js:75:24)at Query.ErrorPacket (node_modules/mysql/lib/protocol/sequences/Query.js:93:8)at Protocol._parsePacket (node_modules/mysql/lib/protocol/Protocol.js:192:24)at Parser.write (node_modules/mysql/lib/protocol/Parser.js:62:12)at Socket.ondata (stream.js:51:26) at Socket.emit (events.js:117:20) at Socket. (_stream_readable.js:748:14)at Socket.emit (events.js:92:17) at emitReadable_ (_stream_readable.js:410:10) at emitReadable (_stream_readable.js:406:5) at readableAddChunk (_stream_readable.js:168:9)at Socket.Readable.push (_stream_readable.js:130:10) at TCP.onread (net.js:528:21)
Short Stack Traces
Error: ER_PARSE_ERROR You have an error in your SQL syntax...at getError (lib/models/database.js:479:24)at Query._callback (lib/models/database.js:110:34) at Query.Sequence.end (node_modules/mysql/lib/protocol/sequences/Sequence.js:75:24)at Query.ErrorPacket (node_modules/mysql/lib/protocol/sequences/Query.js:93:8)at Protocol._parsePacket (node_modules/mysql/lib/protocol/Protocol.js:192:24)at Parser.write (node_modules/mysql/lib/protocol/Parser.js:62:12)at Socket.ondata (stream.js:51:26) at Socket.emit (events.js:117:20) at Socket. (_stream_readable.js:748:14)at Socket.emit (events.js:92:17) at emitReadable_ (_stream_readable.js:410:10) at emitReadable (_stream_readable.js:406:5) at readableAddChunk (_stream_readable.js:168:9)at Socket.Readable.push (_stream_readable.js:130:10) at TCP.onread (net.js:528:21)
Mo Node Mo ProblemsThings that are hard with the event loop:
• Having a request ID propagate throughout code
• Short stack traces from errors in async functions
• Profiling async functions is painful
Profiling Async Functions
var asyncFunc = function(callback) { setTimeout(callback, 1000); };
Profiling Async Functions
var orig = asyncFunc;asyncFunc = function() { var start = new Date(); var args = Array.prototype.slice.call(arguments, 0); var cb = args[args.length - 1]; args[args.length - 1] = function() { console.log((new Date()) - start); return cb.apply(this, arguments); } return orig.apply(this, args);}
Profiling Async Functions
var orig = asyncFunc;asyncFunc = function() { var start = new Date(); var args = Array.prototype.slice.call(arguments, 0); var cb = args[args.length - 1]; args[args.length - 1] = function() { console.log((new Date()) - start); return cb.apply(this, arguments); } return orig.apply(this, args);}
Where Do These Problems Come From?
Where Do These Problems Come From?
Where Do These Problems Come From?
• Asynchronicity and the Event Loop
Where Do These Problems Come From?
• Asynchronicity and the Event Loop
process .nextTick(myFunc);
Event Loop myFunc Executed
Where Do These Problems Come From?
• Asynchronicity and the Event Loop
process .nextTick(myFunc);
Event Loop myFunc Executed
State and stack lost
Where Do These Problems Come From?
• Asynchronicity and the Event Loop
process .nextTick(myFunc);
Event Loop myFunc Executed
State and stack lost
How do we solve it?• AsyncListener API
process.createAsyncListenerprocess.addAsyncListenerprocess.removeAsyncListener
• Available in Node >0.11.9; Polyfill for Node <= 0.11.8
AsyncListener
process .nextTick(myFunc);
AsyncListener
process .nextTick(myFunc);
Event Loop
AsyncListener
process .nextTick(myFunc);
Event Loop myFunc Executed
Some time later…
AsyncListener
process .nextTick(myFunc);
Event Loop myFunc Executed myFunc returns
myFunc errors
Some time later…
AsyncListener
process .nextTick(myFunc);
Event Loop myFunc Executed myFunc returns
myFunc errors
Some time later…
Create Callback
AsyncListener
process .nextTick(myFunc);
Event Loop myFunc Executed myFunc returns
myFunc errors
Some time later…
Create Callback Before Callback
AsyncListener
process .nextTick(myFunc);
Event Loop myFunc Executed myFunc returns
myFunc errors
Some time later…
Create Callback Before Callback After / Error Callback
Propagate Request IDsHow do we solve it?
Propagate Request IDsHow do we solve it?
• Don’t use AsyncListener Directly
Propagate Request IDsHow do we solve it?
• Don’t use AsyncListener Directly
• Use continuation-local-storage
Propagate Request IDsHow do we solve it?
• Don’t use AsyncListener Directly
• Use continuation-local-storage
• But what is it?
continuation-local-storage
continuation-local-storageasync function 1
continuation-local-storageBag Of Holding (CLS makes this) async function 1
continuation-local-storageBag Of Holding (CLS makes this) async function 1
async function 2
continuation-local-storageBag Of Holding (CLS makes this) async function 1
async function 2
async function 3
continuation-local-storageBag Of Holding (CLS makes this)
Each async chain gets it’s own bag
async function 1
async function 2
async function 3
continuation-local-storageBag Of Holding (CLS makes this) async function 1
async function 2
async function 3
Each async chain gets it’s own bag
async function 1
async function 2
async function 3
Propagate Request IDsWant
• Request ID in every log message
• Easy
Propagate Request IDsWant
2013-12-20T18:12:20.037Z - Executing SQL BEGIN statement 2013-12-20T18:12:20.385Z - Values and label are different datasources 2013-12-20T18:12:20.530Z - Executing SQL COMMIT statement 2013-12-20T18:12:20.531Z - Picking filter implementation: DEFAULT
Propagate Request IDsWant
2013-12-20T18:12:20.037Z - Executing SQL BEGIN statement - 3dd4ee56-e8e3-4270-bda7-90c5ee409782
2013-12-20T18:12:20.385Z - Values and label are different datasources - 02671cb7-d22b-4a3e-9bb8-282608999688
2013-12-20T18:12:20.530Z - Executing SQL COMMIT statement - 3dd4ee56-e8e3-4270-bda7-90c5ee409782
2013-12-20T18:12:20.531Z - Picking filter implementation: DEFAULT - 02671cb7-d22b-4a3e-9bb8-282608999688
Propagate Request IDsHow?
• Create continuation-local-storage namespace
• Middleware to create/assign Request ID
• Look up Request ID when logging
Propagate Request IDsCreate namespace
Propagate Request IDsCreate namespace
var createNamespace = require(‘continuation-local-storage') .createNamespace;
var namespace = createNamespace('com.datahero');
Propagate Request IDsCreate namespace
var createNamespace = require(‘continuation-local-storage') .createNamespace;
var namespace = createNamespace('com.datahero');
Once at top of application
Propagate Request IDsMiddleware
Propagate Request IDsMiddleware
var getNamespace = require(‘continuation-local-storage') .getNamespace;
var namespace = getNamespace('com.datahero'), uuid = require('node-uuid');
Propagate Request IDsMiddleware
var getNamespace = require(‘continuation-local-storage') .getNamespace;
var namespace = getNamespace('com.datahero'), uuid = require('node-uuid');
Just requiring… nothing special
Propagate Request IDsMiddleware
app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });
Propagate Request IDsMiddleware
app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });
Propagate Request IDsMiddleware
app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });
Propagate Request IDsMiddleware
app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });
Propagate Request IDsMiddleware
app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });
Propagate Request IDsLogger
Propagate Request IDsLogger
var getNamespace = require(‘continuation-local-storage') .getNamespace;
var namespace = getNamespace(‘com.datahero');
Propagate Request IDsLogger
Logger.debug = function(message) { console.log( (new Date()).toISOString() + ‘ - ‘ + message + ‘ - ‘ + namespace.get(‘rid') ); };
Propagate Request IDsLogger
Logger.debug = function(message) { console.log( (new Date()).toISOString() + ‘ - ‘ + message + ‘ - ‘ + namespace.get(‘rid') ); };
Propagate Request IDsBoom!
Short Stack Traces
Short Stack Traces
setImmediate(function start() { process.nextTick(function howDeep() { setImmediate(function isTheStack() { throw new Error('Oh no!'); }); }); });
Short Stack Traces
setImmediate(function start() { process.nextTick(function howDeep() { setImmediate(function isTheStack() { throw new Error('Oh no!'); }); }); });
Error: Oh no! at Object.isTheStack [as _onImmediate] (short-stack.js:4:13) at processImmediate [as _immediateCallback] (timers.js:336:15)
Short Stack Traces
setImmediate(function start() { process.nextTick(function howDeep() { setImmediate(function isTheStack() { throw new Error('Oh no!'); }); }); });
Error: Oh no! at Object.isTheStack [as _onImmediate] (short-stack.js:4:13) at processImmediate [as _immediateCallback] (timers.js:336:15)
Short Stack Traces
Short Stack Traces
process .nextTick(myFunc);
Event Loop myFunc Executed myFunc returns
myFunc errors
Save copy of stack Stitch stack together
Short Stack TracesUse stackup
• https://www.npmjs.org/package/stackup
Short Stack TracesUse stackup
• https://www.npmjs.org/package/stackup
npm install --save stackup
Short Stack TracesUse stackup
• https://www.npmjs.org/package/stackup
npm install --save stackup
require(‘stackup’);
Profiling Async Code
Profiling Async Code
process .nextTick(myFunc);
Event Loop myFunc Executed myFunc returns
myFunc errors
Record TimeCompute / RecordTime Difference
Profiling Async CodeUse async-profile
• https://www.npmjs.org/package/async-profile
Profiling Async CodeUse async-profile
• https://www.npmjs.org/package/async-profile
npm install --save async-profile
Profiling Async CodeUse async-profile
• https://www.npmjs.org/package/async-profile
var AsyncProfile = require('async-profile') function () { … new AsyncProfile(); doAsyncWork(); }
Summary• Problems
• Can we solve them? Yes we can!
• Use continuation-local-storage, stackup, and async-profile
• All built on AsyncListener
• Prepare for more tracing packages
AsyncListener APIrequire(‘async-listener’);var al = process.createAsyncListener({ create: function(data) {}, before: function(context, data) {}, after: function(context, data) {}, error: function(data, error) {}}, data)
process.addAsyncListener(al);
Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk); }
Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk); }
Stack:bazbarfoo
Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk); }
Stack:
Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk);}
Stack:— tick — bonk