The AsyncTracker API is the JavaScript interface which allows developers to be notified about key events in the lifetime of observed objects and scheduled asynchronous events.
Note
|
Node performs a lot of asynchronous events internally, and use of this API may have a significant performance impact on your application. |
The goal of this library is to build a common API which allows creation of node application monitoring tools, exception management, long stack-traces, resource tracking and cleanup etc.
Most of the existing libraries which provide this functionality monkey-patch the node API layer and typically use closures to maintain context across async callbacks.
However when one or more of these libraries are used simultaneously, there are drawbacks such as:
-
speed degradation
-
instability
-
loss of function information such as function.length, function.toString() etc.
Ideally, there would be a common API that other libraries can build upon. The Async Tracker library attempts to do just that by providing a single interface on which other libraries can be built. The use-cases captured in Async Tracker include:
-
Associating context which is carried across async callback boundaries
-
Notification when async callback is scheduled
-
Optionally capturing significant arguments from the parent function
-
-
Notification of callback being triggered
-
Optionally allow running tasks before and after the callback
-
-
Notification when a callback is un-scheduled (timers, etc.)
-
Notification when
Observable
objects are created or released -
Enable cleanup of
Disposable
objects
It is meant as a conversation starter and I would like to discuss any missing functionality or other feedback you have around this topic.
Async Tracker has 4 core APIs:
-
AsyncTracker Listeners: allows developers to register an object which will be notified of events
-
AsyncTracker Observables: allows developers to instrument their classes so they trigger events when they are created or released.
-
AsyncTracker Disposables: allows developers to enable cleanup of their classes (used by Zones/Domains).
-
Deferred: allows developers to wrap callback methods so they trigger events when they are created, invoked or released.
The AsyncTracker object is exposed as a global variable named asyncTracker
.
name
: Optional. Listener name for the new listener, or an existing listener which should be replaced.
listenerObj
: An object which contains several optional fields. This listener will be associated with all async operations that will be scheduled and all objects that will be created.
These methods are intended to be called from inside node modules and will result in corresponding functions on the listener to be triggered.
Called when a function is registered for deferred execution. This function may be executed one or more times in the future.
-
fName
: A name of the function that scheduled the callback. -
cbId
: Unique ID of this callback -
fData
: If the listener specified interest in function arguments,fData
Called when a function needs to be invoked.
-
fName
: A name of the function that scheduled the callback. -
cbId
: Unique ID of this callback
Warning
|
Be very careful about the implementation of these functions. They will be invoked often and can result in significant performance issues. Calling function that are async from these callbacks can result in an infinite loop |
Map of callback functions for events that this Listener is interested in.
Callback:
-
deferredCreated(fName, cbId, fData)
: A function which is called when an async callback is scheduled.-
fName
: A name of the function that scheduled the callback. -
cbId
: Unique ID of this callback -
fData
: If the listener specified interest in function arguments,fData
will contain a map of the significant arguments
-
Example:
Listener.deferredCreated[fs.open] = function callback(fName, cbId, fData){...};
Listener.deferredCreated['default'] = function defaultCallback(fName, cbId, fData){...};
Map of callback functions for events that this Listener is interested in.
Callback:
-
invokeDeferred(fName, cbId, next)
: A function can intercept the invocation of an async callback. This function behaves similar to Express middleware and you must callnext()
to continue invocation of the callback. Other libraries can use this call to run their own code before and after function execution. They may also choose to wrap the function invocation in a try/catch/finally block to catch and handle exceptions.-
fName
: A name of the function that scheduled the callback. -
cbId
: Unique ID of this callback -
next
: The callback function to execute
-
Example:
Listener.invokeDeferred[fs.open] = function callback(fName, cbId, next){...};
Listener.invokeDeferred['default'] = function defaultCallback(fName, cbId, next){...};
Map of callback functions for events that this Listener is interested in.
Callback:
-
deferredReleased(fName, cbId)
: A function-
fName
: A name of the function that scheduled the callback. -
cbId
: Unique ID of this callback
-
Example:
Listener.deferredReleased[fs.open] = function callback(fName, cbId){...};
Listener.deferredReleased['default'] = function defaultCallback(fName, cbId){...};
-
trackObject(obj)
: A function which is called when an Observable object is created-
obj
: The observable object
-
Example:
Listener.trackObject = function callback(obj){...};
-
releaseObject(obj)
: A function which is called when an Observable object is closed, destroyed or, explicitly released.-
obj
: The observable object
-
Example:
Listener.releaseObject = function callback(obj){...};
The Observable API allows objects to trigger events so that they can be tracked by `listenerObj`s. Developers of other external libraries can also add these calls into their objects if they wish for them to be tracked.
For example, when you open a file with Node, it returns the file handle. This library maintains a list of open handles as FDTracker objects and triggers the Observable API events when a file is opened or closed. A library like Zones can then use this information to track and close file handles even if the user code has lost track of it.
Associate obj
with the currently active listenerObj
and trigger the trackObject
function.
This API should be implemented by tracked objects if they wish to be cleaned up by modules like Zones or Domains when they exit. This API relies on the object also registering using the Observable APIs.
The Deferred API provides helper functions which can be used by developers to wrap callback functions so that they trigger the appropriate functions on listenerObj
and maintain context.
Developers can use this function to wrap a generic callback. This function will return a closure which will trigger the appropriate listenerObj
functions.
-
fName
: The name of function that uses this callback. Eg: fs.open -
fArgs
: Map of argument name to values. This will be passed to listeners that are interested in function arguments -
fCallback
: The callback function to be wrapped
Developers can use this function to wrap a generic callback. This function will return a closure which will trigger the appropriate listenerObj
functions.
-
fName
: The name of function that uses this callback. Eg: fs.open -
fArgs
: Map of argument name to values. This will be passed to listeners that are interested in function arguments -
fCallback
: The callback function to be wrapped
-
fMethod
: The method to be wrapped -
argMap
: Map of argument name to argument positions. This is used to construct the argument map for listeners that are interested in function arguments. If an argument is optional, it should be prefixed with?
. -
callbackPos
: Optional position of the callback function. If not provided, it assumes the last argument is the callback function.
-
fMethod
: The request method to be wrapped -
argList
: List of request argument names. This is used to construct the argument map for listeners that are interested in function arguments. If an argument is optional, it should be prefixed with?
. -
callbackPos
: Optional position of the callback function. If not provided, it assumes the last argument is the callback function.
Ideally, all the events generated from this library would happen in core Node code. However, this library has been created using monkey-patching to experiment and stabilize the API before attempting to get in include in node core.
This library provides a very small subset of the implementation in order to demonstrate the API and concept. The following Node core APIs will need to be patched for a more complete implementation:
-
Cares-wrap (DNS APIs)
-
Process-wrap (child-process APIs)
-
Stream-wrap
-
Cluster
-
Crypto (
pbkdf2
,randomBytes
,pseudoRandomBytes
) -
fs Watch APIs: (
fs.watch
,fs.watchFile
,fs.FSWatcher
) -
process object:
process.on('SIGHUP')
and other signals. -
tls / https
-
udp
-
zlib
All the Listener calls are executed within the context of the functions creating the deferred callbacks or in the context of the callback execution. This allows libraries built using AsyncTracker to gather whatever structured data they require to operate.
Although the current code is completely written in JS, the API should be accessible from C/C++ code as well allowing for native modules to be use all AsyncTracker capabilities as long they comply with the interfaces.
-
AsyncWrap is a part of some very useful work that Trevor Norris did while implementing Async Listener in v0.11. While the JS part of Async Listeners is being removed, the AsyncWrap C++ classes remain.
-
Stephen Belanger has also built a proof-of-concept API which attempts to solve some of the same issues as AsyncTracker.