├── .eslintrc ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.adoc ├── index.js ├── lib ├── async-tracker.js ├── binding.js ├── bindings │ └── fs.js ├── deferred.js ├── linked-list.js ├── next-helper.js ├── node-lib │ └── native_module.js ├── require.js ├── scheduler.js ├── setup.js └── uid.js ├── package.json ├── showcase └── cls │ ├── cls.js │ └── test-cls.js └── test ├── test-add-remove-listener.js ├── test-clearImmediate.js ├── test-fastest-wrap.js ├── test-fs-open-close.js ├── test-nextTick.js └── test-setImmediate.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "node": true, 5 | "amd": true 6 | }, 7 | 8 | "globals": { 9 | "asyncTracker": true 10 | }, 11 | 12 | "rules": { 13 | "no-alert": 2, 14 | "no-array-constructor": 2, 15 | "no-bitwise": 2, 16 | "no-caller": 2, 17 | "no-catch-shadow": 2, 18 | "no-comma-dangle": 2, 19 | "no-cond-assign": 2, 20 | "no-console": 1, 21 | "no-constant-condition": 2, 22 | "no-control-regex": 2, 23 | "no-debugger": 2, 24 | "no-delete-var": 2, 25 | "no-div-regex": 1, 26 | "no-dupe-keys": 2, 27 | "no-else-return": 2, 28 | "no-empty": 2, 29 | "no-empty-class": 2, 30 | "no-empty-label": 2, 31 | "no-eq-null": 2, 32 | "no-eval": 2, 33 | "no-ex-assign": 2, 34 | "no-extend-native": 2, 35 | "no-extra-boolean-cast": 2, 36 | "no-extra-parens": 1, 37 | "no-extra-semi": 2, 38 | "no-extra-strict": 2, 39 | "no-fallthrough": 2, 40 | "no-floating-decimal": 1, 41 | "no-func-assign": 2, 42 | "no-global-strict": 2, 43 | "no-implied-eval": 2, 44 | "no-inner-declarations": [2, "functions"], 45 | "no-invalid-regexp": 2, 46 | "no-iterator": 2, 47 | "no-label-var": 2, 48 | "no-labels": 2, 49 | "no-lone-blocks": 2, 50 | "no-lonely-if": 2, 51 | "no-loop-func": 2, 52 | "no-mixed-requires": [2, false], 53 | "no-multi-str": 2, 54 | "no-native-reassign": 2, 55 | "no-negated-in-lhs": 2, 56 | "no-nested-ternary": 1, 57 | "no-new": 2, 58 | "no-new-func": 2, 59 | "no-new-object": 2, 60 | "no-new-require": 1, 61 | "no-new-wrappers": 2, 62 | "no-obj-calls": 2, 63 | "no-octal": 2, 64 | "no-octal-escape": 2, 65 | "no-path-concat": 0, 66 | "no-plusplus": 2, 67 | "no-process-exit": 2, 68 | "no-proto": 2, 69 | "no-redeclare": 2, 70 | "no-regex-spaces": 2, 71 | "no-restricted-modules": 0, 72 | "no-return-assign": 2, 73 | "no-script-url": 2, 74 | "no-self-compare": 0, 75 | "no-sequences": 2, 76 | "no-shadow": 2, 77 | "no-shadow-restricted-names": 2, 78 | "no-spaced-func": 2, 79 | "no-space-before-semi": 2, 80 | "no-sparse-arrays": 2, 81 | "no-sync": 0, 82 | "no-ternary": 2, 83 | "no-trailing-spaces": 2, 84 | "no-undef": 2, 85 | "no-undefined": 1, 86 | "no-undef-init": 2, 87 | "no-underscore-dangle": 0, 88 | "no-unreachable": 2, 89 | "no-unused-expressions": 2, 90 | "no-unused-vars": [2, {"vars": "all", "args": "after-used"}], 91 | "no-use-before-define": 2, 92 | "no-warning-comments": [1, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 93 | "no-with": 2, 94 | "no-wrap-func": 2, 95 | "no-mixed-spaces-and-tabs": [2, false], 96 | 97 | "block-scoped-var": 1, 98 | "brace-style": [2, "1tbs"], 99 | "camelcase": 2, 100 | "complexity": [0, 11], 101 | "consistent-return": 2, 102 | "consistent-this": [2, "that"], 103 | "curly": [2, "all"], 104 | "default-case": 1, 105 | "dot-notation": 2, 106 | "eol-last": 2, 107 | "eqeqeq": 2, 108 | "func-names": 0, 109 | "func-style": [2, "declaration"], 110 | "guard-for-in": 1, 111 | "max-depth": [0, 4], 112 | "max-len": [1, 80, 4], 113 | "max-nested-callbacks": [1, 2], 114 | "max-params": [0, 3], 115 | "max-statements": [0, 10], 116 | "handle-callback-err": 1, 117 | "new-cap": 2, 118 | "new-parens": 2, 119 | "one-var": 0, 120 | "quote-props": 1, 121 | "quotes": [2, "single"], 122 | "radix": 2, 123 | "semi": 2, 124 | "sort-vars": 1, 125 | "space-after-keywords": [2, "always"], 126 | "space-in-brackets": [1, "never"], 127 | "space-infix-ops": 2, 128 | "space-return-throw-case": 2, 129 | "space-unary-word-ops": 1, 130 | "strict": 0, 131 | "use-isnan": 2, 132 | "valid-jsdoc": 2, 133 | "valid-typeof": 2, 134 | "wrap-iife": 1, 135 | "wrap-regex": 1, 136 | "yoda": [2, "never"] 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ._* 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `async-tracker`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `async-tracker` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/async-tracker) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Contributor License Agreement ### 23 | 24 | ``` 25 | Individual Contributor License Agreement 26 | 27 | By signing this Individual Contributor License Agreement 28 | ("Agreement"), and making a Contribution (as defined below) to 29 | StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and 30 | agree to the following terms and conditions for Your present and 31 | future Contributions submitted to StrongLoop. Except for the license 32 | granted in this Agreement to StrongLoop and recipients of software 33 | distributed by StrongLoop, You reserve all right, title, and interest 34 | in and to Your Contributions. 35 | 36 | 1. Definitions 37 | 38 | "You" or "Your" shall mean the copyright owner or the individual 39 | authorized by the copyright owner that is entering into this 40 | Agreement with StrongLoop. 41 | 42 | "Contribution" shall mean any original work of authorship, 43 | including any modifications or additions to an existing work, that 44 | is intentionally submitted by You to StrongLoop for inclusion in, 45 | or documentation of, any of the products owned or managed by 46 | StrongLoop ("Work"). For purposes of this definition, "submitted" 47 | means any form of electronic, verbal, or written communication 48 | sent to StrongLoop or its representatives, including but not 49 | limited to communication or electronic mailing lists, source code 50 | control systems, and issue tracking systems that are managed by, 51 | or on behalf of, StrongLoop for the purpose of discussing and 52 | improving the Work, but excluding communication that is 53 | conspicuously marked or otherwise designated in writing by You as 54 | "Not a Contribution." 55 | 56 | 2. You Grant a Copyright License to StrongLoop 57 | 58 | Subject to the terms and conditions of this Agreement, You hereby 59 | grant to StrongLoop and recipients of software distributed by 60 | StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, 61 | royalty-free, irrevocable copyright license to reproduce, prepare 62 | derivative works of, publicly display, publicly perform, 63 | sublicense, and distribute Your Contributions and such derivative 64 | works under any license and without any restrictions. 65 | 66 | 3. You Grant a Patent License to StrongLoop 67 | 68 | Subject to the terms and conditions of this Agreement, You hereby 69 | grant to StrongLoop and to recipients of software distributed by 70 | StrongLoop a perpetual, worldwide, non-exclusive, no-charge, 71 | royalty-free, irrevocable (except as stated in this Section) 72 | patent license to make, have made, use, offer to sell, sell, 73 | import, and otherwise transfer the Work under any license and 74 | without any restrictions. The patent license You grant to 75 | StrongLoop under this Section applies only to those patent claims 76 | licensable by You that are necessarily infringed by Your 77 | Contributions(s) alone or by combination of Your Contributions(s) 78 | with the Work to which such Contribution(s) was submitted. If any 79 | entity institutes a patent litigation against You or any other 80 | entity (including a cross-claim or counterclaim in a lawsuit) 81 | alleging that Your Contribution, or the Work to which You have 82 | contributed, constitutes direct or contributory patent 83 | infringement, any patent licenses granted to that entity under 84 | this Agreement for that Contribution or Work shall terminate as 85 | of the date such litigation is filed. 86 | 87 | 4. You Have the Right to Grant Licenses to StrongLoop 88 | 89 | You represent that You are legally entitled to grant the licenses 90 | in this Agreement. 91 | 92 | If Your employer(s) has rights to intellectual property that You 93 | create, You represent that You have received permission to make 94 | the Contributions on behalf of that employer, that Your employer 95 | has waived such rights for Your Contributions, or that Your 96 | employer has executed a separate Corporate Contributor License 97 | Agreement with StrongLoop. 98 | 99 | 5. The Contributions Are Your Original Work 100 | 101 | You represent that each of Your Contributions are Your original 102 | works of authorship (see Section 8 (Submissions on Behalf of 103 | Others) for submission on behalf of others). You represent that to 104 | Your knowledge, no other person claims, or has the right to claim, 105 | any right in any intellectual property right related to Your 106 | Contributions. 107 | 108 | You also represent that You are not legally obligated, whether by 109 | entering into an agreement or otherwise, in any way that conflicts 110 | with the terms of this Agreement. 111 | 112 | You represent that Your Contribution submissions include complete 113 | details of any third-party license or other restriction (including, 114 | but not limited to, related patents and trademarks) of which You 115 | are personally aware and which are associated with any part of 116 | Your Contributions. 117 | 118 | 6. You Don't Have an Obligation to Provide Support for Your Contributions 119 | 120 | You are not expected to provide support for Your Contributions, 121 | except to the extent You desire to provide support. You may provide 122 | support for free, for a fee, or not at all. 123 | 124 | 6. No Warranties or Conditions 125 | 126 | StrongLoop acknowledges that unless required by applicable law or 127 | agreed to in writing, You provide Your Contributions on an "AS IS" 128 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 129 | EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES 130 | OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR 131 | FITNESS FOR A PARTICULAR PURPOSE. 132 | 133 | 7. Submission on Behalf of Others 134 | 135 | If You wish to submit work that is not Your original creation, You 136 | may submit it to StrongLoop separately from any Contribution, 137 | identifying the complete details of its source and of any license 138 | or other restriction (including, but not limited to, related 139 | patents, trademarks, and license agreements) of which You are 140 | personally aware, and conspicuously marking the work as 141 | "Submitted on Behalf of a Third-Party: [named here]". 142 | 143 | 8. Agree to Notify of Change of Circumstances 144 | 145 | You agree to notify StrongLoop of any facts or circumstances of 146 | which You become aware that would make these representations 147 | inaccurate in any respect. Email us at callback@strongloop.com. 148 | ``` 149 | 150 | [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html 151 | [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) IBM Corp. 2014,2015. All Rights Reserved. 2 | Node module: async-tracker 3 | This project is licensed under the MIT License, full text below. 4 | 5 | -------- 6 | 7 | MIT license 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Async Tracker Library 2 | Krishna Raman 3 | 4 | :toc: manual 5 | :toclevels: 4 6 | :toc-placement: preamble 7 | 8 | The AsyncTracker API is the JavaScript interface which allows developers to be 9 | notified about key events in the lifetime of observed objects and scheduled 10 | asynchronous events. 11 | 12 | NOTE: Node performs a lot of asynchronous events internally, and use of this 13 | API may have a significant performance impact on your application. 14 | 15 | == Objectives 16 | 17 | The goal of this library is to build a common API which allows creation of node 18 | application monitoring tools, exception management, long stack-traces, resource 19 | tracking and cleanup etc. 20 | 21 | Most of the existing libraries which provide this functionality monkey-patch 22 | the node API layer and typically use closures to maintain context across async 23 | callbacks. 24 | 25 | However when one or more of these libraries are used simultaneously, there are 26 | drawbacks such as: 27 | 28 | * speed degradation 29 | * instability 30 | * loss of function information such as function.length, function.toString() etc. 31 | 32 | Ideally, there would be a common API that other libraries can build upon. The 33 | Async Tracker library attempts to do just that by providing a single interface 34 | on which other libraries can be built. The use-cases captured in Async Tracker 35 | include: 36 | 37 | * Associating context which is carried across async callback boundaries 38 | * Notification when async callback is scheduled 39 | ** Optionally capturing significant arguments from the parent function 40 | * Notification of callback being triggered 41 | ** Optionally allow running tasks before and after the callback 42 | * Notification when a callback is un-scheduled (timers, etc.) 43 | * Notification when `Observable` objects are created or released 44 | * Enable cleanup of `Disposable` objects 45 | 46 | It is meant as a conversation starter and I would like to discuss any missing 47 | functionality or other feedback you have around this topic. 48 | 49 | == API Overview 50 | 51 | Async Tracker has 4 core APIs: 52 | 53 | . AsyncTracker Listeners: allows developers to register an object which will be 54 | notified of events 55 | . AsyncTracker Observables: allows developers to instrument their classes so 56 | they trigger events when they are created or released. 57 | . AsyncTracker Disposables: allows developers to enable cleanup of their 58 | classes (used by Zones/Domains). 59 | . Deferred: allows developers to wrap callback methods so they trigger events 60 | when they are created, invoked or released. 61 | 62 | === AsyncTracker Listener interface 63 | 64 | The AsyncTracker object is exposed as a global variable named `asyncTracker`. 65 | 66 | [[asynctrackeraddlistenerlistenerobj-name]] 67 | ==== asyncTracker.addListener(listenerObj, [name]) 68 | 69 | `name`: Optional. Listener name for the new listener, or an existing listener which should be replaced. 70 | `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. 71 | 72 | [[asynctrackerremovelistenerlistenername]] 73 | ==== asyncTracker.removeListener(listenerName) 74 | 75 | Prevents the `listenerObj` associated with `listenerName` from being associated with any future async callbacks or objects. Any async callbacks or objects already associated with the listener will continue to trigger. 76 | 77 | === AsyncTracker Trigger interface 78 | 79 | These methods are intended to be called from inside node modules and will result in corresponding functions on the listener to be triggered. 80 | 81 | [[asynctrackerdeferredcreatedfname-cbid-fdata]] 82 | ==== AsyncTracker.deferredCreated(fName, cbId, fData) 83 | 84 | Called when a function is registered for deferred execution. This function may be executed one or more times in the future. 85 | 86 | * `fName`: A name of the function that scheduled the callback. 87 | * `cbId`: Unique ID of this callback 88 | * `fData`: If the listener specified interest in function arguments, `fData` 89 | 90 | [[asynctrackerrundeferredfname-cbid-next]] 91 | ==== AsyncTracker.runDeferred(fName, cbId, next) 92 | 93 | Called when a function needs to be invoked. 94 | 95 | * `fName`: A name of the function that scheduled the callback. 96 | * `cbId`: Unique ID of this callback 97 | 98 | [[asynctrackerdeferredreleasedfname-cbid]] 99 | ==== AsyncTracker.deferredReleased(fName, cbId) 100 | 101 | Called when a function is complete and will no longer be called in the future. 102 | 103 | * `fName`: A name of the function that scheduled the callback. 104 | * `cbId`: Unique ID of this callback 105 | 106 | === Listener Object interface 107 | 108 | 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 109 | 110 | === Listener.deferredCreated 111 | 112 | Map of callback functions for events that this Listener is interested in. 113 | 114 | Callback: 115 | 116 | * `deferredCreated(fName, cbId, fData)`: A function which is called when an async callback is scheduled. 117 | ** `fName`: A name of the function that scheduled the callback. 118 | ** `cbId`: Unique ID of this callback 119 | ** `fData`: If the listener specified interest in function arguments, `fData` will contain a map of the significant arguments 120 | 121 | Example: 122 | 123 | ``` 124 | Listener.deferredCreated[fs.open] = function callback(fName, cbId, fData){...}; 125 | Listener.deferredCreated['default'] = function defaultCallback(fName, cbId, fData){...}; 126 | ``` 127 | 128 | === Listener.invokeDeferred 129 | 130 | Map of callback functions for events that this Listener is interested in. 131 | 132 | Callback: 133 | 134 | * `invokeDeferred(fName, cbId, next)`: A function can intercept the invocation of an async callback. This function behaves similar to Express middleware and you must call `next()` 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. 135 | ** `fName`: A name of the function that scheduled the callback. 136 | ** `cbId`: Unique ID of this callback 137 | ** `next`: The callback function to execute 138 | 139 | Example: 140 | 141 | ``` 142 | Listener.invokeDeferred[fs.open] = function callback(fName, cbId, next){...}; 143 | Listener.invokeDeferred['default'] = function defaultCallback(fName, cbId, next){...}; 144 | ``` 145 | 146 | === Listener.deferredReleased 147 | 148 | Map of callback functions for events that this Listener is interested in. 149 | 150 | Callback: 151 | 152 | * `deferredReleased(fName, cbId)`: A function 153 | ** `fName`: A name of the function that scheduled the callback. 154 | ** `cbId`: Unique ID of this callback 155 | 156 | Example: 157 | 158 | ``` 159 | Listener.deferredReleased[fs.open] = function callback(fName, cbId){...}; 160 | Listener.deferredReleased['default'] = function defaultCallback(fName, cbId){...}; 161 | ``` 162 | 163 | === Listener.trackObject(obj) 164 | 165 | * `trackObject(obj)`: A function which is called when an Observable object is created 166 | ** `obj`: The observable object 167 | 168 | Example: 169 | 170 | ``` 171 | Listener.trackObject = function callback(obj){...}; 172 | ``` 173 | 174 | === Listener.releaseObject(obj) 175 | 176 | * `releaseObject(obj)`: A function which is called when an Observable object is closed, destroyed or, explicitly released. 177 | ** `obj`: The observable object 178 | 179 | Example: 180 | 181 | ``` 182 | Listener.releaseObject = function callback(obj){...}; 183 | ``` 184 | 185 | === AsyncTracker Observable interface 186 | 187 | 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. 188 | 189 | For example, when you open a file with Node, it returns the file handle. This library maintains a list of open handles as https://github.com/kraman/async-tracker/blob/master/lib/bindings/fs.js#L15[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. 190 | 191 | [[asynctrackertrackobjectobj]] 192 | ==== asyncTracker.trackObject(obj) 193 | 194 | Associate `obj` with the currently active `listenerObj` and trigger the `trackObject` function. 195 | 196 | [[asynctrackerreleaseobjectobj]] 197 | ==== asyncTracker.releaseObject(obj) 198 | 199 | Un-associate `obj` with the `listenerObj` and trigger the `releaseObject` function. 200 | 201 | === Disposable 202 | 203 | 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. 204 | 205 | [[objectdispose]] 206 | ==== Object.dispose() 207 | 208 | This method is called by Zones or similar libraries when they are exiting and wish to cleanup a tracked object. 209 | 210 | === Deferred (Helper API) 211 | 212 | 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. 213 | 214 | [[deferredwrapfname-fargs-fcallback]] 215 | ==== Deferred.wrap(fName, fArgs, fCallback) 216 | 217 | Developers can use this function to wrap a generic callback. This function will return a closure which will trigger the appropriate `listenerObj` functions. 218 | 219 | * `fName`: The name of function that uses this callback. Eg: fs.open 220 | * `fArgs`: Map of argument name to values. This will be passed to listeners that are interested in function arguments 221 | * `fCallback`: The callback function to be wrapped 222 | 223 | [[deferredwrapwithargumentsfname-fargs-fcallback-callbackargs]] 224 | ==== Deferred.wrapWithArguments(fName, fArgs, fCallback, callbackArgs) 225 | 226 | Developers can use this function to wrap a generic callback. This function will return a closure which will trigger the appropriate `listenerObj` functions. 227 | 228 | * `fName`: The name of function that uses this callback. Eg: fs.open 229 | * `fArgs`: Map of argument name to values. This will be passed to listeners that are interested in function arguments 230 | * `fCallback`: The callback function to be wrapped 231 | 232 | [[deferredwrapmethodfmethod-argmap-callbackpos]] 233 | ==== Deferred.wrapMethod(fMethod, argMap, callbackPos) 234 | 235 | * `fMethod`: The method to be wrapped 236 | * `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 `?`. 237 | * `callbackPos`: Optional position of the callback function. If not provided, it assumes the last argument is the callback function. 238 | 239 | [[deferredwraprequestfmethod-arglist-callbackpos]] 240 | ==== Deferred.wrapRequest(fMethod, argList, callbackPos) 241 | 242 | * `fMethod`: The request method to be wrapped 243 | * `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 `?`. 244 | * `callbackPos`: Optional position of the callback function. If not provided, it assumes the last argument is the callback function. 245 | 246 | == Implementation 247 | 248 | 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. 249 | 250 | 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: 251 | 252 | * Cares-wrap (DNS APIs) 253 | * Process-wrap (child-process APIs) 254 | * Stream-wrap 255 | * Cluster 256 | * Crypto (`pbkdf2`, `randomBytes`, `pseudoRandomBytes`) 257 | * fs Watch APIs: (`fs.watch`, `fs.watchFile`, `fs.FSWatcher`) 258 | * process object: `process.on('SIGHUP')` and other signals. 259 | * tls / https 260 | * udp 261 | * zlib 262 | 263 | 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. 264 | 265 | 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. 266 | 267 | === Possible optimizations 268 | 269 | https://github.com/bnoordhuis[Ben] has written https://github.com/joyent/node/pull/8090[a patch] which builds upon the AsyncWrap API to allow tracking calls across async boundaries without having to maintain a closure. 270 | 271 | == Related work 272 | 273 | * AsyncWrap is a part of some very useful work that https://github.com/trevnorris[Trevor Norris] did while implementing Async Listener in v0.11. While the https://github.com/joyent/node/pull/8110[JS part of Async Listeners is being removed], the AsyncWrap C++ classes remain. 274 | * https://github.com/Qard[Stephen Belanger] has also built https://github.com/Qard/stacks-concept[a proof-of-concept API] which attempts to solve some of the same issues as AsyncTracker. 275 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | require('./lib/async-tracker'); 7 | require('./lib/setup'); 8 | exports.Deferred = require('./lib/deferred'); -------------------------------------------------------------------------------- /lib/async-tracker.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | uid = require('./uid'); 7 | 8 | function AsyncTracker() { 9 | this.listeners = {}; 10 | this.listenerGeneration = 0; 11 | this.hasRef = false; 12 | this.events = {}; 13 | } 14 | 15 | function _copyListeners(listeners, removeListener) { 16 | var listenersCopy = {}; 17 | var keys = Object.keys(listeners); 18 | var len = keys.length; 19 | for (var i=0; i= _runListKeys.length) { 79 | _next(); 80 | } else { 81 | var curListener = _runList[_runListKeys[_runItrPos]]; 82 | if (curListener.invokeDeferred[_fName]) { 83 | curListener.invokeDeferred[_fName](_fName, _cbId, _runDeferred); 84 | } else if(curListener.invokeDeferred['default']) { 85 | curListener.invokeDeferred['default'](_fName, _cbId, _runDeferred); 86 | } else { 87 | _runDeferred(); 88 | } 89 | } 90 | } 91 | 92 | AsyncTracker.prototype.invokeDeferred = function(fName, cbId, next) { 93 | _runItrPos = -1; 94 | _runList = this.listeners; 95 | _runListKeys = Object.keys(this.listeners); 96 | _fName = fName; 97 | _cbId = cbId; 98 | _next = next; 99 | 100 | _runDeferred(); 101 | }; 102 | 103 | AsyncTracker.prototype.deferredReleased = function(fName, cbId) { 104 | var keys = Object.keys(this.listeners); 105 | var len = keys.length; 106 | for (var i = 0; i < len; i += 1) { 107 | var fn = this.listeners[keys[i]].deferredReleased[fName]; 108 | if (!fn && this.listeners[keys[i]].deferredReleased['default']) { 109 | fn = this.listeners[keys[i]].deferredReleased['default']; 110 | } 111 | 112 | if (fn) fn(fName, cbId); 113 | } 114 | }; 115 | 116 | AsyncTracker.prototype.objectCreated = function(obj) { 117 | obj._listeners = this.listeners; 118 | this.hasRef = true; 119 | 120 | var keys = Object.keys(obj._listeners); 121 | var len = keys.length; 122 | for (var i = 0; i < len; i += 1) { 123 | if (obj._listeners[keys[i]].objectCreated) { 124 | obj._listeners[keys[i]].objectCreated(obj); 125 | } 126 | } 127 | }; 128 | 129 | AsyncTracker.prototype.objectReleased = function(obj) { 130 | var keys = Object.keys(obj._listeners); 131 | var len = keys.length; 132 | for (var i = 0; i < len; i += 1) { 133 | if (obj._listeners[keys[i]].objectReleased) { 134 | obj._listeners[keys[i]].objectReleased(obj); 135 | } 136 | } 137 | }; 138 | 139 | if (!global.asyncTracker) { 140 | global.asyncTracker = new AsyncTracker(); 141 | global.asyncTracker.AsyncTracker = AsyncTracker; 142 | } 143 | -------------------------------------------------------------------------------- /lib/binding.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var realBinding = process.binding; 7 | var bindingCache = {}; 8 | 9 | function binding(name) { 10 | if (name in bindingCache){ 11 | return bindingCache[name]; 12 | } 13 | 14 | var wb; 15 | switch (name) { 16 | // case 'pipe_wrap': 17 | // case 'tcp_wrap': 18 | // case 'tty_wrap': 19 | // var wb = require('./binding/stream-wrap.js')(realBinding); 20 | // bindingCache.pipe_wrap = wb; 21 | // bindingCache.tcp_wrap = wb; 22 | // bindingCache.tty_wrap = wb; 23 | // return wb; 24 | 25 | // case 'cares_wrap': 26 | // var wb = require('./bindings/cares-wrap.js')(realBinding); 27 | // bindingCache.cares_wrap = wb; 28 | // return wb; 29 | 30 | case 'fs': 31 | wb = require('./bindings/fs.js')(realBinding); 32 | bindingCache.fs = wb; 33 | return wb; 34 | 35 | // case 'process_wrap': 36 | // var wb = require('./binding/process-wrap.js')(realBinding); 37 | // bindingCache.process_wrap = wb; 38 | // return wb; 39 | 40 | default: 41 | return realBinding(name); 42 | } 43 | } 44 | 45 | process.binding = binding; 46 | -------------------------------------------------------------------------------- /lib/bindings/fs.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var assert = require('assert'); 7 | var uid = require('../uid.js'); 8 | var util = require('util'); 9 | var deferred = require('../deferred'); 10 | var nextHelper = require('../next-helper'); 11 | 12 | module.exports = function(binding) { 13 | var fs = binding('fs'); 14 | 15 | /** 16 | * The FDTracker class tracks open files. 17 | * 18 | * @private 19 | */ 20 | function FDTracker(fd, path) { 21 | this.fd = fd; 22 | this.path = path; 23 | }; 24 | 25 | /** 26 | * Debug function which returns a string with the current state. 27 | * 28 | * @return {String} 29 | */ 30 | FDTracker.prototype.toString = function(options) { 31 | options = options || {}; 32 | var indent = options.indent || 0; 33 | var prefix = (new Array(indent + 1)).join(' '); 34 | 35 | return util.format('%s [File ] #%d (fd: %d, path: %s)\n', 36 | prefix, this.__id, this.fd, this.path); 37 | }; 38 | 39 | FDTracker.table = []; 40 | 41 | /** 42 | * Create a new FDTracker and register it with the Zone 43 | */ 44 | FDTracker.register = function(fd, path) { 45 | assert(this.table[fd] === undefined); 46 | var fdTracker = new FDTracker(fd, path); 47 | this.table[fd] = fdTracker; 48 | asyncTracker.objectCreated(fdTracker); 49 | return fdTracker; 50 | }; 51 | 52 | /** 53 | * Release the FDTracker class 54 | */ 55 | FDTracker.unregister = function(fd) { 56 | var fdTracker = this.table[fd]; 57 | assert(fdTracker); 58 | fdTracker.release(); 59 | delete this.table[fd]; 60 | return fdTracker; 61 | }; 62 | 63 | FDTracker.prototype.release = function() { 64 | asyncTracker.objectReleased(this); 65 | } 66 | 67 | asyncTracker.events.fs = { 68 | open: asyncTracker.generateEventName('fs', 'open'), 69 | close: asyncTracker.generateEventName('fs', 'close'), 70 | fchmod: asyncTracker.generateEventName('fs', 'fchmod'), 71 | fchown: asyncTracker.generateEventName('fs', 'fchown'), 72 | read: asyncTracker.generateEventName('fs', 'read'), 73 | writeBuffer: asyncTracker.generateEventName('fs', 'writeBuffer'), 74 | writeString: asyncTracker.generateEventName('fs', 'writeString'), 75 | fstat: asyncTracker.generateEventName('fs', 'fstat'), 76 | fsync: asyncTracker.generateEventName('fs', 'fsync'), 77 | ftruncate: asyncTracker.generateEventName('fs', 'ftruncate'), 78 | futimes: asyncTracker.generateEventName('fs', 'futimes'), 79 | stat: asyncTracker.generateEventName('fs', 'stat'), 80 | link: asyncTracker.generateEventName('fs', 'link'), 81 | lstat: asyncTracker.generateEventName('fs', 'lstat'), 82 | chmod: asyncTracker.generateEventName('fs', 'chmod'), 83 | chown: asyncTracker.generateEventName('fs', 'chown'), 84 | rename: asyncTracker.generateEventName('fs', 'rename'), 85 | readlink: asyncTracker.generateEventName('fs', 'readlink'), 86 | readdir: asyncTracker.generateEventName('fs', 'readdir'), 87 | unlink: asyncTracker.generateEventName('fs', 'unlink'), 88 | symlink: asyncTracker.generateEventName('fs', 'symlink'), 89 | utimes: asyncTracker.generateEventName('fs', 'utimes') 90 | } 91 | 92 | /** 93 | * Helper method to isolate try-catch from optimized code 94 | */ 95 | function callWrapped(method, thisArg, args) { 96 | var error, result; 97 | try { 98 | result = method.apply(thisArg, args); 99 | } catch (err) { 100 | error = err; 101 | } 102 | return [result, error]; 103 | } 104 | 105 | // Opening and closing a file descriptor 106 | var realFsOpen = fs.open; 107 | fs.open = function open(path, flags, mode) { 108 | var callbackPos = arguments.length-1; 109 | var callback = arguments[callbackPos]; 110 | 111 | // If no callback was specified then call the synchronous binding. 112 | if (typeof callback !== 'function') { 113 | var fd = realFsOpen(path, flags, mode); 114 | if (fd >= 0) { 115 | FDTracker.register(fd, path); 116 | } 117 | return fd; 118 | } 119 | 120 | var id = uid(); 121 | var savedListener = asyncTracker.listeners; 122 | asyncTracker.deferredCreated( 123 | asyncTracker.events.fs.open, 124 | id, 125 | {'path': path}); 126 | function openCallback(err, fd) { 127 | var curListeners = asyncTracker.listeners; 128 | asyncTracker.listeners = savedListener; 129 | 130 | if (!err && fd >= 0) { 131 | FDTracker.register(fd, path); 132 | } 133 | 134 | nextHelper.prepareNext(this, callback, [err, fd]); 135 | asyncTracker.invokeDeferred( 136 | asyncTracker.events.fs.open, 137 | id, 138 | nextHelper.next); 139 | 140 | asyncTracker.deferredReleased(asyncTracker.events.fs.open, id); 141 | asyncTracker.listeners = curListeners; 142 | } 143 | 144 | var result, error; 145 | result = callWrapped(realFsOpen, this, [path, flags, mode, openCallback]); 146 | error = result[1]; 147 | result = result[0]; 148 | 149 | if (error || result < 0) { 150 | //release the wrappedCb 151 | listner.deferredReleased(asyncTracker.events.fs.open, id); 152 | } 153 | 154 | if (error) { 155 | throw error; 156 | } else { 157 | return result; 158 | } 159 | }; 160 | 161 | var realFsClose = fs.close; 162 | fs.close = function close(fd, callback) { 163 | var fdTracker = FDTracker.unregister(fd); 164 | 165 | // If no callback was specified then call the synchronous binding. 166 | if (typeof callback !== 'function') { 167 | return realFsClose.apply(this, arguments); 168 | } 169 | 170 | var wrappedCallback = deferred.wrap( 171 | asyncTracker.events.fs.close, 172 | {'path': fdTracker.path}, 173 | callback); 174 | 175 | var result, error; 176 | result = callWrapped(realFsClose, this, [fd, wrappedCallback]); 177 | error = result[1]; 178 | result = result[0]; 179 | 180 | if (error || result < 0) { 181 | deferred.releaseDeferred(callback); 182 | } 183 | 184 | if (error) { 185 | throw error; 186 | } else { 187 | return result; 188 | } 189 | }; 190 | 191 | var evt = asyncTracker.events.fs; 192 | fs.fchmod = deferred.wrapMethod(evt.fchmod, fs.fchmod, {1: 'fd', 2: 'mode'}); 193 | fs.fchown = deferred.wrapMethod(evt.fchown, fs.fchown, 194 | {1: 'fd', 2: 'uid', 3: 'gid'}); 195 | fs.read = deferred.wrapMethod(evt.read, fs.read, {1: 'fd'}); 196 | fs.writeBuffer = deferred.wrapMethod(evt.writeBuffer, fs.writeBuffer, 197 | {1: 'fd'}); 198 | fs.writeString = deferred.wrapMethod(evt.writeString, fs.writeString, 199 | {1: 'fd'}); 200 | fs.fstat = deferred.wrapMethod(evt.fstat, fs.fstat, {1: 'fd'}); 201 | fs.fsync = deferred.wrapMethod(evt.fsync, fs.fsync, {1: 'fd'}); 202 | fs.ftruncate = deferred.wrapMethod(evt.ftruncate, fs.ftruncate, {1: 'fd'}); 203 | fs.futimes = deferred.wrapMethod(evt.futimes, fs.futimes, {1: 'fd'}); 204 | 205 | fs.stat = deferred.wrapMethod(evt.stat, fs.stat, {1: 'path'}); 206 | fs.link = deferred.wrapMethod(evt.link, fs.link, 207 | {1: 'srcpath', 2: 'dstpath'}); 208 | fs.lstat = deferred.wrapMethod(evt.lstat, fs.lstat, {1: 'path'}); 209 | fs.chmod = deferred.wrapMethod(evt.chmod, fs.chmod, {1: 'path', 2: 'mode'}); 210 | fs.chown = deferred.wrapMethod(evt.chown, fs.chown, 211 | {1: 'path', 2: 'uid', 3: 'gid'}); 212 | fs.rename = deferred.wrapMethod(evt.rename, fs.rename, 213 | {1: 'oldPath', 2: 'newPath'}); 214 | fs.readlink = deferred.wrapMethod(evt.readlink, fs.readlink, {1: 'path'}); 215 | fs.readdir = deferred.wrapMethod(evt.readdir, fs.readdir, {1: 'path'}); 216 | fs.unlink = deferred.wrapMethod(evt.unlink, fs.unlink, {1: 'path'}); 217 | fs.symlink = deferred.wrapMethod(evt.symlink, fs.symlink, 218 | {1: 'srcpath', 2: 'destpath'}); 219 | fs.utimes = deferred.wrapMethod(evt.utimes, fs.utimes, {1: 'path'}); 220 | 221 | return fs; 222 | }; 223 | -------------------------------------------------------------------------------- /lib/deferred.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var uid = require('./uid'); 7 | var nextHelper = require('./next-helper'); 8 | var RELEASE_CMD = {}; 9 | 10 | function wrap(fName, fArgs, fCallback) { 11 | var id = uid(); 12 | var savedListener = asyncTracker.listeners; 13 | asyncTracker.deferredCreated(fName, id, fArgs); 14 | function fn() { 15 | var curListeners = asyncTracker.listeners; 16 | asyncTracker.listeners = savedListener; 17 | if (arguments[0] !== RELEASE_CMD) { 18 | asyncTracker.invokeDeferred(fName, id, fCallback); 19 | } 20 | asyncTracker.deferredReleased(fName, id); 21 | asyncTracker.listeners = curListeners; 22 | } 23 | return fn; 24 | } 25 | 26 | function wrapWithArguments(fName, fArgs, fCallback, callbackArgs) { 27 | var id = uid(); 28 | var savedListener = asyncTracker.listeners; 29 | asyncTracker.deferredCreated(fName, id, fArgs); 30 | function fn() { 31 | var curListeners = asyncTracker.listeners; 32 | asyncTracker.listeners = savedListener; 33 | if (arguments[0] !== RELEASE_CMD) { 34 | nextHelper.prepareNext(this, fCallback, callbackArgs); 35 | asyncTracker.invokeDeferred(fName, id, nextHelper.next); 36 | } 37 | asyncTracker.deferredReleased(fName, id); 38 | asyncTracker.listeners = curListeners; 39 | } 40 | return fn; 41 | } 42 | 43 | function releaseDeferred(fn) { 44 | fn(RELEASE_CMD); 45 | } 46 | 47 | /** 48 | * Wrap a method so it follows AsyncTracker symantics. The callback is assumed to be the last argument. 49 | * Only async invocations (with a callback) will generate events 50 | * 51 | * @parameter method The method to wrap 52 | * @parameter argMap A map of position -> argument name which should be passed to the listener 53 | */ 54 | function wrapMethod(fName, method, argMap) { 55 | return function() { 56 | var callbackPos = arguments.length-1; 57 | 58 | // If the method is called synchronously, call the function directly. 59 | if (typeof arguments[callbackPos] !== 'function') { 60 | return method.apply(this, arguments); 61 | } 62 | 63 | // Capture the original arguments and the callback. 64 | var args = new Array(arguments.length); 65 | var sArgs = {}; 66 | for (var i = 0; i < arguments.length; i += 1) { 67 | args[i] = arguments[i]; 68 | if (argMap[i]) { 69 | sArgs[argMap[i]] = arguments[i]; 70 | } 71 | } 72 | var callback = args[callbackPos]; 73 | 74 | args[callbackPos] = wrap(fName, sArgs, callback); 75 | return method.apply(this, args); 76 | }; 77 | } 78 | 79 | exports.wrapWithArguments = wrapWithArguments; 80 | exports.wrap = wrap; 81 | exports.releaseDeferred = releaseDeferred; 82 | exports.wrapMethod = wrapMethod; 83 | -------------------------------------------------------------------------------- /lib/linked-list.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | function LinkedListIterator(list, head, tail) { 7 | this._list = list; 8 | this._head = head; 9 | this._tail = tail; 10 | this.current = list; 11 | 12 | this.prev = function prev() { 13 | this.current = this.current.prev; 14 | if (this.current && this.current !== this._list) { 15 | return this.current.value; 16 | } 17 | 18 | this.current = null; 19 | return null; 20 | }; 21 | 22 | this.next = function next() { 23 | this.current = this.current.next; 24 | if (this.current && this.current !== this._list) { 25 | return this.current.value; 26 | } 27 | 28 | this.current = null; 29 | return null; 30 | }; 31 | } 32 | 33 | function ListItem(object, prev, next) { 34 | this.value = object; 35 | this.prev = prev; 36 | this.next = next; 37 | } 38 | 39 | function LinkedList(name) { 40 | this.prev = this; // tail 41 | this.next = this; // head 42 | this.name = name; 43 | 44 | this.push = function push(object) { 45 | if (this.empty()) { 46 | this.next = this.prev = new ListItem(object, this, this); 47 | return this.next; 48 | } 49 | 50 | var newEntry = new ListItem(object, this.prev, this); 51 | this.prev.next = newEntry; 52 | this.prev = newEntry; 53 | return newEntry; 54 | }; 55 | 56 | this.unshift = function unshift(object) { 57 | if (this.empty()) { 58 | this.next = this.prev = new ListItem(object, this, this); 59 | return this.next; 60 | } 61 | 62 | var newEntry = new ListItem(object, this, this.next); 63 | this.next.prev = newEntry; 64 | this.next = newEntry; 65 | return newEntry; 66 | }; 67 | 68 | this.pop = function pop() { 69 | if (this.empty()) { 70 | return null; 71 | } 72 | var entry = this.prev; 73 | if (entry.prev === this) { 74 | this.next = this; 75 | this.prev = this; 76 | } else { 77 | this.prev = entry.prev; 78 | } 79 | 80 | return entry.value; 81 | }; 82 | 83 | this.shift = function shift() { 84 | if (this.empty()) { 85 | return null; 86 | } 87 | 88 | var entry = this.next; 89 | if (entry.next === this) { 90 | this.next = this; 91 | this.prev = this; 92 | } else { 93 | this.next = entry.next; 94 | } 95 | 96 | return entry.value; 97 | }; 98 | 99 | this.tail = function tail() { 100 | if (this.empty()) { 101 | return null; 102 | } 103 | 104 | return this.prev.value; 105 | }; 106 | 107 | this.head = function head() { 108 | if (this.empty()) { 109 | return null; 110 | } 111 | 112 | return this.next.value; 113 | }; 114 | 115 | this.empty = function empty() { 116 | return this.next === this; 117 | }; 118 | 119 | this.remove = function remove(object) { 120 | if (!this.empty()) { 121 | for (var c = this.next; c !== this; c = c.next) { 122 | if (c.value === object) { 123 | this.removeListItem(c); 124 | return true; 125 | } 126 | } 127 | } 128 | return false; 129 | }; 130 | 131 | this.removeListItem = function(listItem) { 132 | listItem.prev.next = listItem.next; 133 | listItem.next.prev = listItem.prev; 134 | listItem.prev = listItem.next = null; 135 | }; 136 | 137 | this.iterator = function iterator() { 138 | return new LinkedListIterator(this, this.next, this.prev); 139 | }; 140 | } 141 | 142 | module.exports = LinkedList; 143 | -------------------------------------------------------------------------------- /lib/next-helper.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var context = null; 7 | var callback = null; 8 | var callbackArgs = null; 9 | function next() { 10 | callback.apply(context, callbackArgs); 11 | } 12 | 13 | function prepareNext(_context, cb, args){ 14 | context = _context; 15 | callback = cb; 16 | callbackArgs = args; 17 | } 18 | 19 | exports.next = next; 20 | exports.prepareNext = prepareNext; 21 | -------------------------------------------------------------------------------- /lib/node-lib/native_module.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | module.exports = NativeModule; 23 | 24 | 25 | var ContextifyScript = process.binding('contextify').ContextifyScript; 26 | function runInThisContext(code, options) { 27 | var script = new ContextifyScript(code, options); 28 | return script.runInThisContext(); 29 | } 30 | 31 | function NativeModule(id) { 32 | this.filename = id + '.js'; 33 | this.id = id; 34 | this.exports = {}; 35 | this.loaded = false; 36 | } 37 | 38 | NativeModule._source = process.binding('natives'); 39 | NativeModule._cache = {}; 40 | 41 | NativeModule.require = function(id) { 42 | if (id == 'native_module') { 43 | return NativeModule; 44 | } 45 | 46 | var cached = NativeModule.getCached(id); 47 | if (cached) { 48 | return cached.exports; 49 | } 50 | 51 | if (!NativeModule.exists(id)) { 52 | throw new Error('No such native module ' + id); 53 | } 54 | 55 | process.moduleLoadList.push('NativeModule ' + id); 56 | 57 | var nativeModule = new NativeModule(id); 58 | 59 | nativeModule.cache(); 60 | nativeModule.compile(); 61 | 62 | return nativeModule.exports; 63 | }; 64 | 65 | NativeModule.getCached = function(id) { return NativeModule._cache[id]; }; 66 | 67 | NativeModule.exists = 68 | function(id) { return NativeModule._source.hasOwnProperty(id); }; 69 | 70 | NativeModule.getSource = function(id) { return NativeModule._source[id]; }; 71 | 72 | NativeModule.wrap = function(script) { 73 | return NativeModule.wrapper[0] + script + NativeModule.wrapper[1]; 74 | }; 75 | 76 | NativeModule.wrapper = [ 77 | '(function (exports, require, module, __filename, __dirname) { ', 78 | '\n});' 79 | ]; 80 | 81 | NativeModule.prototype.compile = function() { 82 | var source = NativeModule.getSource(this.id); 83 | source = NativeModule.wrap(source); 84 | 85 | var fn = runInThisContext(source, {filename: this.filename}); 86 | fn(this.exports, require, this, this.filename); 87 | 88 | this.loaded = true; 89 | }; 90 | 91 | NativeModule.prototype.cache = 92 | function() { NativeModule._cache[this.id] = this; }; 93 | -------------------------------------------------------------------------------- /lib/require.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var Module = require('module').Module; 7 | var NativeModule = require('./node-lib/native_module'); 8 | 9 | var realRequire = Module.prototype.require; 10 | Module.prototype._realRequire = realRequire; 11 | 12 | function load(path) { 13 | switch (path) { 14 | case 'buffer': 15 | return realRequire.apply(this, arguments); 16 | default: 17 | if (NativeModule.exists(path)) { 18 | return NativeModule.require(path); 19 | } 20 | return realRequire.apply(this, arguments); 21 | } 22 | } 23 | 24 | Module.prototype.require = load; 25 | -------------------------------------------------------------------------------- /lib/scheduler.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var deferred = require('./deferred.js'); 7 | var uid = require('./uid.js'); 8 | var nextHelper = require('./next-helper.js'); 9 | var RELEASE_CMD = {}; 10 | 11 | asyncTracker.events.process = { 12 | nextTick: asyncTracker.generateEventName('process', 'nextTick') 13 | }; 14 | 15 | var realNextTick = process.nextTick; 16 | process.nextTick = function nextTick(cb) { 17 | cb = deferred.wrap(asyncTracker.events.process.nextTick, null, cb); 18 | realNextTick(cb); 19 | }; 20 | 21 | asyncTracker.events.global = { 22 | setImmediate: asyncTracker.generateEventName('global', 'setImmediate') 23 | } 24 | 25 | var realSetImmediate = global.setImmediate; 26 | var realClearImmediate = global.clearImmediate; 27 | 28 | global.setImmediate = function setImmediate() { 29 | var fn = arguments[0]; 30 | var args = new Array(arguments.length - 1); 31 | for (var i; i < args.length; i += 1) { 32 | args[i] = arguments[i + 1]; 33 | } 34 | 35 | var id = uid(); 36 | var savedListener = asyncTracker.listeners; 37 | var handle; 38 | var evt = asyncTracker.events.global; 39 | 40 | asyncTracker.deferredCreated(evt.setImmediate, id, null); 41 | function cb() { 42 | var curListeners = asyncTracker.listeners; 43 | asyncTracker.listeners = savedListener; 44 | 45 | if (arguments[0] === RELEASE_CMD) { 46 | asyncTracker.deferredReleased(evt.setImmediate, id); 47 | realClearImmediate(handle); 48 | } else { 49 | nextHelper.prepareNext(this, fn, args); 50 | asyncTracker.invokeDeferred(evt.setImmediate, id, nextHelper.next); 51 | asyncTracker.deferredReleased(evt.setImmediate, id); 52 | } 53 | 54 | asyncTracker.listeners = curListeners; 55 | } 56 | 57 | handle = realSetImmediate(cb); 58 | return cb; 59 | }; 60 | 61 | 62 | global.clearImmediate = function clearImmediate(handle) { 63 | handle(RELEASE_CMD); 64 | }; 65 | -------------------------------------------------------------------------------- /lib/setup.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | require('./binding.js'); 7 | require('./require.js'); 8 | require('./scheduler.js'); 9 | // require('./timer.js'); 10 | -------------------------------------------------------------------------------- /lib/uid.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var uidCounter = 0; 7 | function uid() { 8 | uidCounter += 1; 9 | return uidCounter; 10 | } 11 | 12 | module.exports = uid; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-tracker", 3 | "version": "0.0.0", 4 | "description": "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.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tap test/test-*.js", 8 | "lint": "eslint **/*.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/kraman/async-tracker" 13 | }, 14 | "keywords": [ 15 | "async" 16 | ], 17 | "author": "Krishna Raman ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/kraman/async-tracker/issues" 21 | }, 22 | "homepage": "https://github.com/kraman/async-tracker", 23 | "devDependencies": { 24 | "eslint": "^0.7.4", 25 | "tap": "^0.4.12" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /showcase/cls/cls.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | require('../../'); 7 | 8 | function Context(curContext){ 9 | if (curContext) { 10 | this.values = Object.create(curContext.values); 11 | } else { 12 | this.values = {}; 13 | } 14 | } 15 | 16 | Context.prototype.get = function get(key){ 17 | return this.values[key]; 18 | } 19 | 20 | Context.prototype.set = function get(key, value){ 21 | this.values[key] = value; 22 | } 23 | 24 | function Namespace(_name){ 25 | this.name = 'CLS_' + _name; 26 | } 27 | 28 | Namespace.prototype.set = function(key, value) { 29 | var context = asyncTracker.getListener(this.name); 30 | context.set(key, value); 31 | } 32 | 33 | Namespace.prototype.get = function(key) { 34 | var context = asyncTracker.getListener(this.name); 35 | return context.get(key); 36 | } 37 | 38 | Namespace.prototype.set = function(key, value) { 39 | var context = asyncTracker.getListener(this.name); 40 | return context.set(key, value); 41 | } 42 | 43 | Namespace.prototype.run = function(func) { 44 | var curContext = asyncTracker.getListener(this.name); 45 | var newContext = new Context(curContext); 46 | asyncTracker.addListener(newContext, this.name); 47 | func(newContext); 48 | asyncTracker.addListener(curContext, this.name); 49 | } 50 | 51 | process.namespaces = {} 52 | function createNamespace(name) { 53 | process.namespaces[name] = new Namespace(name); 54 | return process.namespaces[name]; 55 | } 56 | 57 | function getNamespace(name) { 58 | return process.namespaces[name]; 59 | } 60 | 61 | function destroyNamespace(name) { 62 | asyncTracker.removeListener(process.namespaces[name].name); 63 | delete process.namespaces[name]; 64 | } 65 | 66 | exports.createNamespace = createNamespace; 67 | exports.getNamespace = getNamespace; 68 | exports.destroyNamespace = destroyNamespace; -------------------------------------------------------------------------------- /showcase/cls/test-cls.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | // based on example from readme 7 | 8 | cls = require('./cls'); 9 | assert = require('assert'); 10 | 11 | var createNamespace = cls.createNamespace; 12 | 13 | function requestHandler() { 14 | writer.run(function(outer) { 15 | assert.equal(writer.get('value'), 0); 16 | assert.equal(outer.get('value'), 0); 17 | writer.set('value', 1); 18 | assert.equal(writer.get('value'), 1); 19 | assert.equal(outer.get('value'), 1); 20 | process.nextTick(function() { 21 | assert.equal(writer.get('value'), 1); 22 | assert.equal(outer.get('value'), 1); 23 | writer.run(function(inner) { 24 | assert.equal(writer.get('value'), 1); 25 | assert.equal(outer.get('value'), 1); 26 | assert.equal(inner.get('value'), 1); 27 | writer.set('value', 2); 28 | assert.equal(writer.get('value'), 2); 29 | assert.equal(outer.get('value'), 1); 30 | assert.equal(inner.get('value'), 2); 31 | }); 32 | }); 33 | }); 34 | 35 | setImmediate(function() { 36 | assert.equal(writer.get('value'), 0); 37 | }); 38 | } 39 | 40 | var writer = createNamespace('writer'); 41 | writer.run(function () { 42 | writer.set('value', 0); 43 | requestHandler(); 44 | }); 45 | 46 | -------------------------------------------------------------------------------- /test/test-add-remove-listener.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var assert = require('assert'); 7 | require('../index.js'); 8 | 9 | var cnt=0; 10 | var Listener = function(_expectEvents) { 11 | var evtName = asyncTracker.events.process.nextTick; 12 | 13 | this.deferredCreated = {}; 14 | this.invokeDeferred = {}; 15 | this.deferredReleased = {}; 16 | 17 | this.deferredCreated[evtName] = function(fName, fId) { 18 | assert(_expectEvents); 19 | cnt++; 20 | }; 21 | 22 | this.invokeDeferred[evtName] = function(fName, fId, next) { 23 | assert(_expectEvents); 24 | cnt++; 25 | next(); 26 | }; 27 | 28 | this.deferredReleased[evtName] = function(fName, fId) { 29 | assert(_expectEvents); 30 | cnt++; 31 | }; 32 | }; 33 | 34 | function cb() { 35 | } 36 | 37 | var l1 = new Listener(true); 38 | var l2 = new Listener(true); 39 | var l3 = new Listener(false); 40 | 41 | asyncTracker.addListener(l1, 'l1'); 42 | asyncTracker.addListener(l2, 'l2'); 43 | process.nextTick(cb); 44 | asyncTracker.removeListener('l1'); 45 | asyncTracker.addListener(l3, 'l3'); -------------------------------------------------------------------------------- /test/test-clearImmediate.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var assert = require('assert'); 7 | var util = require('util'); 8 | require('../'); 9 | 10 | var cnt = 0; 11 | var Listener = function() { 12 | this.deferredCreated = {}; 13 | this.invokeDeferred = {}; 14 | this.deferredReleased = {}; 15 | var evtName = asyncTracker.events.global.setImmediate; 16 | 17 | this.deferredCreated[evtName] = function(fName, fId) { 18 | assert.equal(fName, evtName); 19 | assert.equal(cnt, 0); 20 | cnt++; 21 | }; 22 | 23 | this.invokeDeferred[evtName] = function(fName, fId, next) { 24 | assert.equal(fName, evtName); 25 | assert(false, 'should not be called'); 26 | cnt++; 27 | next(); 28 | }; 29 | 30 | this.deferredReleased[evtName] = function(fName, fId) { 31 | assert.equal(fName, evtName); 32 | assert.equal(cnt, 1); 33 | cnt++; 34 | }; 35 | }; 36 | 37 | function cb() { 38 | assert(false, 'should not be called'); 39 | cnt++; 40 | } 41 | 42 | var listener = new Listener(); 43 | asyncTracker.addListener(listener, 'listener'); 44 | h = setImmediate(cb); 45 | clearImmediate(h); -------------------------------------------------------------------------------- /test/test-fastest-wrap.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | // This test checks that setting up module globals is faster than creating a 7 | // closure to store context. 8 | 9 | var assert = require('assert'); 10 | 11 | function dummy(a1, a2) { 12 | return a1 + a2; 13 | } 14 | 15 | 16 | 17 | function closureApproach(){ 18 | (function() { 19 | dummy.apply(this, arguments); 20 | }) ('foo', 'bar'); 21 | } 22 | 23 | var iterations=1e7; 24 | var t1 = process.hrtime(); 25 | for (var i=iterations; i>0; i=i-1) { 26 | closureApproach(); 27 | } 28 | var diff = process.hrtime(t1); 29 | diffClosure = diff[0] * 1e9 + diff[1]; 30 | 31 | 32 | 33 | var context = null; 34 | var callback = null; 35 | var callbackArgs = null; 36 | function next() { 37 | callback.apply(context, callbackArgs); 38 | } 39 | 40 | function parameterApproach(){ 41 | context = this; 42 | callback = dummy; 43 | callbackArgs = new Array(2); 44 | callbackArgs[0] = 'foo'; 45 | callbackArgs[1] = 'bar'; 46 | 47 | next(); 48 | 49 | context = null; 50 | callback = null; 51 | callbackArgs = null; 52 | } 53 | 54 | t2 = process.hrtime(); 55 | for (var i=iterations; i>0; i=i-1) { 56 | parameterApproach(); 57 | } 58 | diff = process.hrtime(t1); 59 | diffPrepareNext = diff[0] * 1e9 + diff[1]; 60 | 61 | 62 | 63 | assert(diffPrepareNext > diffClosure); 64 | -------------------------------------------------------------------------------- /test/test-fs-open-close.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var assert = require('assert'); 7 | require('../index.js'); 8 | var fs = require('fs'); 9 | var util = require('util'); 10 | var cnt = 0; 11 | 12 | var Listener = function() { 13 | var evtName = asyncTracker.events.fs.open; 14 | 15 | this.deferredCreated = {}; 16 | this.invokeDeferred = {}; 17 | this.deferredReleased = {}; 18 | 19 | this.deferredCreated[evtName] = function(fName, fId, args) { 20 | assert.equal(cnt, 0); 21 | cnt += 1; 22 | }; 23 | 24 | this.deferredCreated['default'] = function(fName, fId, args) { 25 | assert.equal(cnt, 4); 26 | cnt += 1; 27 | }; 28 | 29 | this.invokeDeferred[evtName] = function(fName, fId, next) { 30 | assert.equal(cnt, 2); 31 | cnt += 1; 32 | next(); 33 | }; 34 | 35 | this.invokeDeferred['default'] = function(fName, fId, next) { 36 | assert.equal(cnt, 6); 37 | cnt += 1; 38 | next(); 39 | }; 40 | 41 | this.deferredReleased[evtName] = function(fName, fId) { 42 | assert.equal(cnt, 5); 43 | cnt += 1; 44 | }; 45 | 46 | this.deferredReleased['default'] = function(fName, fId) { 47 | assert.equal(cnt, 7); 48 | cnt += 1; 49 | }; 50 | 51 | this.objectCreated = function(obj) { 52 | assert.equal(cnt, 1); 53 | cnt += 1; 54 | }; 55 | 56 | this.objectReleased = function(obj) { 57 | assert.equal(cnt, 3); 58 | cnt += 1; 59 | }; 60 | }; 61 | 62 | var listener = new Listener(); 63 | asyncTracker.addListener(listener, 'listener'); 64 | 65 | function closeCallback() { 66 | } 67 | 68 | function openCallback(err, fd) { 69 | fs.close(fd, closeCallback); 70 | } 71 | 72 | fs.open(__filename, 'r', openCallback); 73 | asyncTracker.removeListener('listener'); -------------------------------------------------------------------------------- /test/test-nextTick.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var assert = require('assert'); 7 | var util = require('util'); 8 | require('../'); 9 | 10 | var cnt = 0; 11 | var Listener = function() { 12 | var evtName = asyncTracker.events.process.nextTick; 13 | 14 | this.deferredCreated = {}; 15 | this.invokeDeferred = {}; 16 | this.deferredReleased = {}; 17 | 18 | this.deferredCreated[evtName] = function(fName, fId) { 19 | assert.equal(fName, evtName); 20 | assert.equal(cnt, 0); 21 | cnt++; 22 | }; 23 | 24 | this.invokeDeferred[evtName] = function(fName, fId, next) { 25 | assert.equal(fName, evtName); 26 | assert.equal(cnt, 1); 27 | cnt++; 28 | next(); 29 | }; 30 | 31 | this.deferredReleased[evtName] = function(fName, fId) { 32 | assert.equal(fName, asyncTracker.events.process.nextTick); 33 | assert.equal(cnt, 3); 34 | cnt++; 35 | }; 36 | }; 37 | 38 | function cb() { 39 | assert.equal(cnt, 2); 40 | cnt++; 41 | } 42 | 43 | var listener = new Listener(); 44 | asyncTracker.addListener(listener, 'listener'); 45 | process.nextTick(cb); 46 | -------------------------------------------------------------------------------- /test/test-setImmediate.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: async-tracker 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 6 | var assert = require('assert'); 7 | var util = require('util'); 8 | require('../'); 9 | 10 | var cnt = 0; 11 | var Listener = function() { 12 | var evtName = asyncTracker.events.global.setImmediate; 13 | 14 | this.deferredCreated = {}; 15 | this.invokeDeferred = {}; 16 | this.deferredReleased = {}; 17 | 18 | this.deferredCreated[evtName] = function(fName, fId) { 19 | assert.equal(fName, evtName); 20 | assert.equal(cnt, 0); 21 | cnt++; 22 | }; 23 | 24 | this.invokeDeferred[evtName] = function(fName, fId, next) { 25 | assert.equal(fName, evtName); 26 | assert.equal(cnt, 1); 27 | cnt++; 28 | next(); 29 | }; 30 | 31 | this.deferredReleased[evtName] = function(fName, fId) { 32 | assert.equal(fName, evtName); 33 | assert.equal(cnt, 3); 34 | cnt++; 35 | }; 36 | }; 37 | 38 | function cb() { 39 | assert.equal(cnt, 2); 40 | cnt++; 41 | } 42 | 43 | var listener = new Listener(); 44 | asyncTracker.addListener(listener, 'listener'); 45 | setImmediate(cb); 46 | --------------------------------------------------------------------------------