4

We are building an Electron app that allows users to supply their own 'modules' to run. We are looking for a way to require the modules but then delete or kill the modules if need be. We have looked a few tutorials that seem to discuss this topic but we can't seem to get the modules to fully terminate. We explored this by using timers inside the modules and can observe the timers still running even after the module reference is deleted.

https://repl.it/repls/QuerulousSorrowfulQuery

index.js

// Load module
let Mod = require('./mod.js'); 

// Call the module function (which starts a setInterval)
Mod();

// Delete the module after 3 seconds
setTimeout(function () {
  Mod = null;
  delete Mod;
  console.log('Deleted!')
}, 3000);

./mod.js

function Mod() {
  setInterval(function () {
    console.log('Mod log');
  }, 1000);
}

module.exports = Mod;

Expected output

Mod log
Mod log
Deleted!

Actual output

Mod log
Mod log
Deleted!
Mod log 
...
(continues to log 'Mod log' indefinitely)

Maybe we are overthinking it and maybe the modules won't be memory hogs, but the modules we load will have very intensive workloads and having the ability to stop them at will seems important.

Edit with real use-case

This is how we are currently using this technique. The two issues are loading the module in the proper fashion and unloading the module after it is done.

renderer.js (runs in a browser context with access to document, etc)

const webview = document.getElementById('webview'); // A webview object essentially gives us control over a webpage similar to how one can control an iframe in a regular browser.
const url = 'https://ourserver.com/module.js';
let mod;
request({
  method: 'get',
  url: url,
}, function (err, httpResponse, body) {
  if (!err) {
    mod = requireFromString(body, url); // Module is loaded
    mod(webview); // Module is run
    // ...
    // Some time later, the module needs to be 'unloaded'. 
    // We are currently 'unloading' it by dereferencing the 'mod' variable, but as mentioned above, this doesn't really work. So we would like to have a way to wipe the module and timers and etc and free up any memory or resources it was using!
    mod = null;
    delete mod;
  } 
})

function requireFromString(src, filename) {
  var Module = module.constructor;
  var m = new Module();
  m._compile(src, filename);
  return m.exports;
}

https://ourserver.com/module.js

// This code module will only have access to node modules that are packaged with our app but that is OK for now!
let _ = require('lodash'); 
let obj = {
  key: 'value'
}
async function main(webview) {
  console.log(_.get(obj, 'key')) // prints 'value'
  webview.loadURL('https://google.com') // loads Google in the web browser
}

module.exports = main;

Just in case anyone reading is not familiar with Electron, the renderer.js has access to 'webview' elements which are almost identical to iframes. This is why passing it to the 'module.js' will allow the module to access manipulate the webpage such as change URL, click buttons on that webpage, etc.

T Mack
  • 950
  • 3
  • 12
  • 27
  • Does this answer your question? [How to remove module after "require" in node.js?](https://stackoverflow.com/questions/15666144/how-to-remove-module-after-require-in-node-js) – Ahmet Zeybek Jan 26 '20 at 04:11
  • Yes, I saw that question and tried practically every answer. However, even when applying the answers to the example in this question, the timer still logs things! Do you have any other suggestions? – T Mack Jan 26 '20 at 04:24
  • Is it a web based app ? I mean is this module will run on browser side ? Btw, you can't use `delete` operator to delete functions, it can be only used to remove object properties – Ahmet Zeybek Jan 26 '20 at 04:45
  • Did you try to assign `Mod` to some local variable first and inside the timeout clear/set to null that variable? Or you can use `clearInterval`? – Dananjaya Ariyasena Jan 26 '20 at 05:38
  • @AhmetZeybek this is on an Electron app, should have mentioned that my bad. – T Mack Jan 26 '20 at 22:01
  • @Dananjaya Ariyasena that's a good idea but not feasible since there might be many timers and the timer was more used here as a proof of concept that the code wasn't really unloaded entirely. – T Mack Jan 26 '20 at 22:01

1 Answers1

5

There is no way to kill a module and stop or close any resources that it is using. That's just not a feature of node.js. Such a module could have timers, open files, open sockets, running servers, etc... In addition node.js does not provide a means of "unloading" code that was once loaded.

You can remove a module from the module cache, but that doesn't affect the existing, already loaded code or its resources.

The only foolproof way I know of would be to load the user's module in a separate node.js app loaded as a child process and then you can exit that process or kill that process and then the OS will reclaim any resources it was using and unload everything from memory. This child process scheme also has the advantage that the user's code is more isolated from your main server code. You could even further isolate it by running this other process in a VM if you wanted to.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • That is a very helpful explanation. I checked out other questions and none of them explained this so I think we've just been approaching it wrong. I would like to mark this as an accepted answer but is there a way you could provide an example of this using a child process or vm? We have tried with VM but the same issue still seems to be present where the timer will log indefinitely. If it helps, the environment this will be running in is an Electron app. Thank you :) – T Mack Jan 26 '20 at 21:58
  • @TMack - I'm asking what the user code needs to do because it matters what data from your app it needs access to and it matters what result it is trying to achieve. When you move things to another process, how you do that depends upon what you have to communicate to it and what it has to communicate back and whether that communication is ongoing on just a one time thing and then it's done. – jfriend00 Jan 26 '20 at 23:57
  • they will act sort of like plugins. The app is a browser that also has some automation functions so the users are able to write their own scripts to automate things like clicking buttons on websites and such. Ultimately they will need to be able to run multiple scripts like this and the scripts can be started and stopped at any time. In general, we want the context of these scripts to be pretty similar to a regular node module in that it has access to most node features and is able to `require` packages.I'm mentioning this because we ran into a problem where require didnt work in vm – T Mack Jan 27 '20 at 01:25
  • @TMack - That still doesn't explain what level of communication it has with your main server. Can you give me some examples of what they do? You said click buttons - but how? Is the user code running on your server? Or, being inserted into your webpages and running in the browser? I'm really confused now. If these are in continuous operation and potentially participating in lots of server events, that's a bit more work when moving the plug-in out of process, but it can be done. – jfriend00 Jan 27 '20 at 01:45
  • Hi again thank you for being patient I added an example to the question. Pretty simple, but the `renderer.js` will load the `plugins`/`modules` which have access to a `webview` which is a fancy version of an `iframe` that comes with Electron. The module can then manipulate the webview and also take advantage of `require`ing other modules that are preinstalled with our app! Really hope that makes sense thanks so much for taking a look. – T Mack Jan 27 '20 at 07:31
  • @TMack - Now that I'm understanding that this is an electron app and you're trying to allow this code to modify a webView, I don't understand that architecture well enough to advise further on how you do this out of process. – jfriend00 Jan 27 '20 at 07:41
  • Ok thanks for letting me know. Though, I think even a trivial example might help us get started and we can probably adapt it to work in our environment. – T Mack Jan 27 '20 at 07:45
  • @TMack - The simple examples of running child processes are here: https://nodejs.org/api/child_process.html. – jfriend00 Jan 27 '20 at 22:19