Dirty solution for NodeJS event loop problem, but it seems to work at leasst...
@@ -5,7 +5,6 @@ const Module = require('module');
const path = require('path');
const util = require('util');
const fs = require('fs');
-const async_hooks = require('async_hooks');
/* Require the JavaScript parser */
const cherow = require(path.join(__dirname, 'node_modules', 'cherow'));
@@ -383,29 +382,51 @@ module.exports = ((impl, ptr) => {
'destroy': node_loader_trampoline_destroy,
});
- function node_loader_trampoline_destroy() {
- /* Initialize async hooks for keeping track the amount of async handles that are in the event queue */
- const asyncHook = async_hooks.createHook({
- destroy: node_loader_trampoline_async_hook_destroy,
+ const activeTimers = new Map();
+
+ /* Timers are not included after Node.js 10 */
+ const NODE_VERSION = process.version.slice(1).split('.').map((v) => parseInt(v, 10));
+
+ const disableHooks = (NODE_VERSION[0] > 10) ? (() => {
+ /* Track them with the hooks */
+ const hook = asyncHooks.createHook({
+ init (asyncId, type, triggerAsyncId, resource) {
+ /* Track only timers */
+ if (type === 'Timeout') {
+ activeTimers.set(asyncId, {type, triggerAsyncId, resource});
+ }
+ },
+ destroy (asyncId) {
+ activeTimers.delete(asyncId);
+ },
});
- /* We need to use this cleanup variable because asyncHook.disable() seems not to work */
- let cleanup = false;
+ hook.enable();
- /* Enable async hooks for tracking all destroyed (and garbage collected) async resources */
- asyncHook.enable();
+ return () => { hook.disable() };
+ })() : () => {};
- function node_loader_trampoline_async_hook_destroy() {
+ function node_loader_trampoline_destroy() {
+ /* Initialize a timer for keeping track the amount of async handles that are in the event queue,
+ * before this async hooks have been used, but due to V8 GC, some destroy events were never triggered
+ * leaving the event loop always open, the only alternative I have found is this one, or refactoring
+ * the node loader, dropping support for Node 10 and 12, and implementing ourselves the event loop
+ * instead of using node::Start. This solution is dirty but at least works, lets keep it until
+ * we found something better, at least it makes the node loader stable */
+ const handle = setInterval(() => {
/* At this point we may have reached an "empty" event loop, it
* only contains the async resources that the Node Loader has populated,
- * but it does not contain any user defined async resource */
- if (trampoline.active_handles(node_loader_ptr) <= 0 && cleanup === false) {
- /* Disable Hooks and destroy the Node Loader */
- cleanup = true;
- asyncHook.disable();
+ * including this timer (1 for active_handles, 3 for _getActiveHandles() which includes
+ * stdin, stdout and the timer), but it does not contain any user defined async resource */
+ const timers = [...activeTimers.values()].filter(timer => !(typeof timer.resource.hasRef === 'function' && !timer.resource.hasRef()));
+
+ if (trampoline.active_handles(node_loader_ptr) <= 1 && process._getActiveHandles().length + timers.length <= 3 && process._getActiveRequests().length === 0) {
+ /* Clear the timer, disable hooks and destroy the Node Loader */
+ clearInterval(handle);
+ disableHooks();
trampoline.destroy(node_loader_ptr);
}
- }
+ }, 1000);
}
} catch (ex) {
console.log('Exception in bootstrap.js trampoline initialization:', ex);
