glue.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. var wrap = require('shimmer').wrap;
  2. /*
  3. *
  4. * CONSTANTS
  5. *
  6. */
  7. var HAS_CREATE_AL = 1 << 0;
  8. var HAS_BEFORE_AL = 1 << 1;
  9. var HAS_AFTER_AL = 1 << 2;
  10. var HAS_ERROR_AL = 1 << 3;
  11. /**
  12. * There is one list of currently active listeners that is mutated in place by
  13. * addAsyncListener and removeAsyncListener. This complicates error-handling,
  14. * for reasons that are discussed below.
  15. */
  16. var listeners = [];
  17. /**
  18. * There can be multiple listeners with the same properties, so disambiguate
  19. * them by assigning them an ID at creation time.
  20. */
  21. var uid = 0;
  22. /**
  23. * Ensure that errors coming from within listeners are handed off to domains,
  24. * process._fatalException, or uncaughtException without being treated like
  25. * user errors.
  26. */
  27. var inAsyncTick = false;
  28. /**
  29. * Because asynchronous contexts can be nested, and errors can come from anywhere
  30. * in the stack, a little extra work is required to keep track of where in the
  31. * nesting we are. Because JS arrays are frequently mutated in place
  32. */
  33. var listenerStack = [];
  34. /**
  35. * The error handler on a listener can capture errors thrown during synchronous
  36. * execution immediately after the listener is added. To capture both
  37. * synchronous and asynchronous errors, the error handler just uses the
  38. * "global" list of active listeners, and the rest of the code ensures that the
  39. * listener list is correct by using a stack of listener lists during
  40. * asynchronous execution.
  41. */
  42. var asyncCatcher;
  43. /**
  44. * The guts of the system -- called each time an asynchronous event happens
  45. * while one or more listeners are active.
  46. */
  47. var asyncWrap;
  48. /**
  49. * Simple helper function that's probably faster than using Array
  50. * filter methods and can be inlined.
  51. */
  52. function union(dest, added) {
  53. var destLength = dest.length;
  54. var addedLength = added.length;
  55. var returned = [];
  56. if (destLength === 0 && addedLength === 0) return returned;
  57. for (var j = 0; j < destLength; j++) returned[j] = dest[j];
  58. if (addedLength === 0) return returned;
  59. for (var i = 0; i < addedLength; i++) {
  60. var missing = true;
  61. for (j = 0; j < destLength; j++) {
  62. if (dest[j].uid === added[i].uid) {
  63. missing = false;
  64. break;
  65. }
  66. }
  67. if (missing) returned.push(added[i]);
  68. }
  69. return returned;
  70. }
  71. /*
  72. * For performance, split error-handlers and asyncCatcher up into two separate
  73. * code paths.
  74. */
  75. // 0.9+
  76. if (process._fatalException) {
  77. /**
  78. * Error handlers on listeners can throw, the catcher needs to be able to
  79. * discriminate between exceptions thrown by user code, and exceptions coming
  80. * from within the catcher itself. Use a global to keep track of which state
  81. * the catcher is currently in.
  82. */
  83. var inErrorTick = false;
  84. /**
  85. * Throwing always happens synchronously. If the current array of values for
  86. * the current list of asyncListeners is put in a module-scoped variable right
  87. * before a call that can throw, it will always be correct when the error
  88. * handlers are run.
  89. */
  90. var errorValues;
  91. asyncCatcher = function asyncCatcher(er) {
  92. var length = listeners.length;
  93. if (inErrorTick || length === 0) return false;
  94. var handled = false;
  95. /*
  96. * error handlers
  97. */
  98. inErrorTick = true;
  99. for (var i = 0; i < length; ++i) {
  100. var listener = listeners[i];
  101. if ((listener.flags & HAS_ERROR_AL) === 0) continue;
  102. var value = errorValues && errorValues[listener.uid];
  103. handled = listener.error(value, er) || handled;
  104. }
  105. inErrorTick = false;
  106. /* Test whether there are any listener arrays on the stack. In the case of
  107. * synchronous throws when the listener is active, there may have been
  108. * none pushed yet.
  109. */
  110. if (listenerStack.length > 0) listeners = listenerStack.pop();
  111. errorValues = undefined;
  112. return handled && !inAsyncTick;
  113. };
  114. asyncWrap = function asyncWrap(original, list, length) {
  115. var values = [];
  116. /*
  117. * listeners
  118. */
  119. inAsyncTick = true;
  120. for (var i = 0; i < length; ++i) {
  121. var listener = list[i];
  122. values[listener.uid] = listener.data;
  123. if ((listener.flags & HAS_CREATE_AL) === 0) continue;
  124. var value = listener.create(listener.data);
  125. if (value !== undefined) values[listener.uid] = value;
  126. }
  127. inAsyncTick = false;
  128. /* One of the main differences between this polyfill and the core
  129. * asyncListener support is that core avoids creating closures by putting a
  130. * lot of the state managemnt on the C++ side of Node (and of course also it
  131. * bakes support for async listeners into the Node C++ API through the
  132. * AsyncWrap class, which means that it doesn't monkeypatch basically every
  133. * async method like this does).
  134. */
  135. return function () {
  136. // put the current values where the catcher can see them
  137. errorValues = values;
  138. /* More than one listener can end up inside these closures, so save the
  139. * current listeners on a stack.
  140. */
  141. listenerStack.push(listeners);
  142. /* Activate both the listeners that were active when the closure was
  143. * created and the listeners that were previously active.
  144. */
  145. listeners = union(list, listeners);
  146. /*
  147. * before handlers
  148. */
  149. inAsyncTick = true;
  150. for (var i = 0; i < length; ++i) {
  151. if ((list[i].flags & HAS_BEFORE_AL) > 0) {
  152. list[i].before(this, values[list[i].uid]);
  153. }
  154. }
  155. inAsyncTick = false;
  156. // save the return value to pass to the after callbacks
  157. var returned = original.apply(this, arguments);
  158. /*
  159. * after handlers (not run if original throws)
  160. */
  161. inAsyncTick = true;
  162. for (i = 0; i < length; ++i) {
  163. if ((list[i].flags & HAS_AFTER_AL) > 0) {
  164. list[i].after(this, values[list[i].uid]);
  165. }
  166. }
  167. inAsyncTick = false;
  168. // back to the previous listener list on the stack
  169. listeners = listenerStack.pop();
  170. errorValues = undefined;
  171. return returned;
  172. };
  173. };
  174. wrap(process, '_fatalException', function (_fatalException) {
  175. return function _asyncFatalException(er) {
  176. return asyncCatcher(er) || _fatalException(er);
  177. };
  178. });
  179. }
  180. // 0.8 and below
  181. else {
  182. /**
  183. * If an error handler in asyncWrap throws, the process must die. Under 0.8
  184. * and earlier the only way to put a bullet through the head of the process
  185. * is to rethrow from inside the exception handler, so rethrow and set
  186. * errorThrew to tell the uncaughtHandler what to do.
  187. */
  188. var errorThrew = false;
  189. /**
  190. * Under Node 0.8, this handler *only* handles synchronously thrown errors.
  191. * This simplifies it, which almost but not quite makes up for the hit taken
  192. * by putting everything in a try-catch.
  193. */
  194. asyncCatcher = function uncaughtCatcher(er) {
  195. // going down hard
  196. if (errorThrew) throw er;
  197. var handled = false;
  198. /*
  199. * error handlers
  200. */
  201. var length = listeners.length;
  202. for (var i = 0; i < length; ++i) {
  203. var listener = listeners[i];
  204. if ((listener.flags & HAS_ERROR_AL) === 0) continue;
  205. handled = listener.error(null, er) || handled;
  206. }
  207. /* Rethrow if one of the before / after handlers fire, which will bring the
  208. * process down immediately.
  209. */
  210. if (!handled && inAsyncTick) throw er;
  211. };
  212. asyncWrap = function asyncWrap(original, list, length) {
  213. var values = [];
  214. /*
  215. * listeners
  216. */
  217. inAsyncTick = true;
  218. for (var i = 0; i < length; ++i) {
  219. var listener = list[i];
  220. values[listener.uid] = listener.data;
  221. if ((listener.flags & HAS_CREATE_AL) === 0) continue;
  222. var value = listener.create(listener.data);
  223. if (value !== undefined) values[listener.uid] = value;
  224. }
  225. inAsyncTick = false;
  226. /* One of the main differences between this polyfill and the core
  227. * asyncListener support is that core avoids creating closures by putting a
  228. * lot of the state managemnt on the C++ side of Node (and of course also it
  229. * bakes support for async listeners into the Node C++ API through the
  230. * AsyncWrap class, which means that it doesn't monkeypatch basically every
  231. * async method like this does).
  232. */
  233. return function () {
  234. /*jshint maxdepth:4*/
  235. // after() handlers don't run if threw
  236. var threw = false;
  237. // ...unless the error is handled
  238. var handled = false;
  239. /* More than one listener can end up inside these closures, so save the
  240. * current listeners on a stack.
  241. */
  242. listenerStack.push(listeners);
  243. /* Activate both the listeners that were active when the closure was
  244. * created and the listeners that were previously active.
  245. */
  246. listeners = union(list, listeners);
  247. /*
  248. * before handlers
  249. */
  250. inAsyncTick = true;
  251. for (var i = 0; i < length; ++i) {
  252. if ((list[i].flags & HAS_BEFORE_AL) > 0) {
  253. list[i].before(this, values[list[i].uid]);
  254. }
  255. }
  256. inAsyncTick = false;
  257. // save the return value to pass to the after callbacks
  258. var returned;
  259. try {
  260. returned = original.apply(this, arguments);
  261. }
  262. catch (er) {
  263. threw = true;
  264. for (var i = 0; i < length; ++i) {
  265. if ((listeners[i].flags & HAS_ERROR_AL) == 0) continue;
  266. try {
  267. handled = listeners[i].error(values[list[i].uid], er) || handled;
  268. }
  269. catch (x) {
  270. errorThrew = true;
  271. throw x;
  272. }
  273. }
  274. if (!handled) {
  275. // having an uncaughtException handler here alters crash semantics
  276. process.removeListener('uncaughtException', asyncCatcher);
  277. process._originalNextTick(function () {
  278. process.addListener('uncaughtException', asyncCatcher);
  279. });
  280. throw er;
  281. }
  282. }
  283. finally {
  284. /*
  285. * after handlers (not run if original throws)
  286. */
  287. if (!threw || handled) {
  288. inAsyncTick = true;
  289. for (i = 0; i < length; ++i) {
  290. if ((list[i].flags & HAS_AFTER_AL) > 0) {
  291. list[i].after(this, values[list[i].uid]);
  292. }
  293. }
  294. inAsyncTick = false;
  295. }
  296. // back to the previous listener list on the stack
  297. listeners = listenerStack.pop();
  298. }
  299. return returned;
  300. };
  301. };
  302. // will be the first to fire if async-listener is the first module loaded
  303. process.addListener('uncaughtException', asyncCatcher);
  304. }
  305. // for performance in the case where there are no handlers, just the listener
  306. function simpleWrap(original, list, length) {
  307. inAsyncTick = true;
  308. for (var i = 0; i < length; ++i) {
  309. var listener = list[i];
  310. if (listener.create) listener.create(listener.data);
  311. }
  312. inAsyncTick = false;
  313. // still need to make sure nested async calls are made in the context
  314. // of the listeners active at their creation
  315. return function () {
  316. listenerStack.push(listeners);
  317. listeners = union(list, listeners);
  318. var returned = original.apply(this, arguments);
  319. listeners = listenerStack.pop();
  320. return returned;
  321. };
  322. }
  323. /**
  324. * Called each time an asynchronous function that's been monkeypatched in
  325. * index.js is called. If there are no listeners, return the function
  326. * unwrapped. If there are any asyncListeners and any of them have callbacks,
  327. * pass them off to asyncWrap for later use, otherwise just call the listener.
  328. */
  329. function wrapCallback(original) {
  330. var length = listeners.length;
  331. // no context to capture, so avoid closure creation
  332. if (length === 0) return original;
  333. // capture the active listeners as of when the wrapped function was called
  334. var list = listeners.slice();
  335. for (var i = 0; i < length; ++i) {
  336. if (list[i].flags > 0) return asyncWrap(original, list, length);
  337. }
  338. return simpleWrap(original, list, length);
  339. }
  340. function AsyncListener(callbacks, data) {
  341. if (typeof callbacks.create === 'function') {
  342. this.create = callbacks.create;
  343. this.flags |= HAS_CREATE_AL;
  344. }
  345. if (typeof callbacks.before === 'function') {
  346. this.before = callbacks.before;
  347. this.flags |= HAS_BEFORE_AL;
  348. }
  349. if (typeof callbacks.after === 'function') {
  350. this.after = callbacks.after;
  351. this.flags |= HAS_AFTER_AL;
  352. }
  353. if (typeof callbacks.error === 'function') {
  354. this.error = callbacks.error;
  355. this.flags |= HAS_ERROR_AL;
  356. }
  357. this.uid = ++uid;
  358. this.data = data === undefined ? null : data;
  359. }
  360. AsyncListener.prototype.create = undefined;
  361. AsyncListener.prototype.before = undefined;
  362. AsyncListener.prototype.after = undefined;
  363. AsyncListener.prototype.error = undefined;
  364. AsyncListener.prototype.data = undefined;
  365. AsyncListener.prototype.uid = 0;
  366. AsyncListener.prototype.flags = 0;
  367. function createAsyncListener(callbacks, data) {
  368. if (typeof callbacks !== 'object' || !callbacks) {
  369. throw new TypeError('callbacks argument must be an object');
  370. }
  371. if (callbacks instanceof AsyncListener) {
  372. return callbacks;
  373. }
  374. else {
  375. return new AsyncListener(callbacks, data);
  376. }
  377. }
  378. function addAsyncListener(callbacks, data) {
  379. var listener;
  380. if (!(callbacks instanceof AsyncListener)) {
  381. listener = createAsyncListener(callbacks, data);
  382. }
  383. else {
  384. listener = callbacks;
  385. }
  386. // Make sure the listener isn't already in the list.
  387. var registered = false;
  388. for (var i = 0; i < listeners.length; i++) {
  389. if (listener === listeners[i]) {
  390. registered = true;
  391. break;
  392. }
  393. }
  394. if (!registered) listeners.push(listener);
  395. return listener;
  396. }
  397. function removeAsyncListener(listener) {
  398. for (var i = 0; i < listeners.length; i++) {
  399. if (listener === listeners[i]) {
  400. listeners.splice(i, 1);
  401. break;
  402. }
  403. }
  404. }
  405. process.createAsyncListener = createAsyncListener;
  406. process.addAsyncListener = addAsyncListener;
  407. process.removeAsyncListener = removeAsyncListener;
  408. module.exports = wrapCallback;