developit / greenlet

🦎 Move an async function into its own thread.
https://npm.im/greenlet
4.67k stars 100 forks source link

Releasing thread and URL reference when the workerized function is not needed #49

Open maneetgoyal opened 4 years ago

maneetgoyal commented 4 years ago

Great library, very useful stuff and absolutely love the size. :)

I've only recently started learning about Web Workers and took a look at the source code. So apologies in advance if I am wrong ;). 2 things caught my eyes:

const workerURL = URL.createObjectURL(new Blob([script]));
// Create an "inline" worker (1:1 at definition time)
const worker = new Worker(workerURL);

So if we do something like the following snippet (taken from the README), it seems that each new function instantiated via greenlet(...) will reserve a new thread and a new URL reference.

import greenlet from 'greenlet'

let getName = greenlet( async username => {
    let url = `https://api.github.com/users/${username}`
    let res = await fetch(url)
    let profile = await res.json()
    return profile.name
})

console.log(await getName('developit'))

So, if there is a case wherein I don't need to use getName after a certain point in my code, those resources are still trapped. They may be very less in size to be of a practical concern, but I am not sure about it and would love if anyone can comment on that.

However, if the output function getName comes with a dispose method which releases those references, it could be useful. WDYT? Something like:

getName.dispose() // Release references 

Internally, it could call:

window.URL.revokeObjectURL(workerURL);
worker.terminate();

Post dispose, getName can itself become undefined so it's not callable. Or can throw a more informative error: The function is disposed/discarded due to .dispose() call..

Is there a downside to this approach if the contributors already considered any similar approach?

ljluestc commented 9 months ago
function greenlet(fn) {
  const script = `self.onmessage = e => self.postMessage((${fn})(e.data));`;
  const workerURL = URL.createObjectURL(new Blob([script]));
  const worker = new Worker(workerURL);

  const instance = (...args) =>
    new Promise((resolve, reject) => {
      const messageHandler = (e) => {
        worker.removeEventListener('message', messageHandler);
        resolve(e.data);
      };
      worker.addEventListener('message', messageHandler);
      worker.postMessage(args);
    });

  instance.dispose = () => {
    URL.revokeObjectURL(workerURL);
    worker.terminate();
  };

  return instance;
}