Skip to content
This repository has been archived by the owner on Oct 9, 2023. It is now read-only.

[Question] Runtime loading/unloading modules #144

Open
kpelelis opened this issue Oct 31, 2018 · 2 comments
Open

[Question] Runtime loading/unloading modules #144

kpelelis opened this issue Oct 31, 2018 · 2 comments

Comments

@kpelelis
Copy link
Contributor

We have been experimenting with hypernova for our SSR architecture. A problem arose when we wanted to add or remove a module in the getComponent method on runtime. Is there a suggested way that we could do this without restarting the server?

@kpelelis kpelelis changed the title Runtime loading/unloading modules [Question] Runtime loading/unloading modules Oct 31, 2018
@agis
Copy link

agis commented Nov 2, 2018

Specifically, we want to deploy hypernova in such a way that it supports multiple versions (ie. releases) of our SSR code, without having to restart it. However, we've noticed memory leaks.

The scheme is the following:

  • we keep a map with the known versions in hypernova.js
  • when a client requests a version, hypernova goes to the fs and loads the respective files
  • the version is kept in the map
  • if a client requests the same version, the contents are already present in the map so there's no touching the fs, otherwise, the version is searched in the fs and loaded
  • if a version is not touched for some amount of time, it's deleted from the map

The SSR code is the following:

// components.js
const renderReact = require('hypernova-react').renderReact;
const MyComponent = require('./MyComponent');
module.exports = {
  MyComponent: renderReact('MyComponent', MyComponent),
};

and in hypernova.js we do the following:

// hypernova.js

// ...

function loadCodeSeparateContext(path) {
  return hypernova.createGetComponent({
    "0": path
  })("0");
}

const getComponent = async (name) => {
  var start = process.hrtime();
  const [componentName, release] = name.split('.');
  if (!componentName || !release) {
    return null;
  }

  if (!componentsPerRelease[release]) {
    var componentsPath = path.resolve(path.join('./testdata', release, 'components.js'));
    if (await fs.pathExists(componentsPath)){
      var releaseComponents = loadCodeSeparateContext(componentsPath);
      componentsPerRelease[release] = {components: releaseComponents};
    }
    else {
      return null;
    }
  }

  componentsPerRelease[release].lastAccessed = start;

  if (!componentsPerRelease[release].components[componentName]) {
    return null;
  }
  var [seconds, nanos] = process.hrtime(start);
  return componentsPerRelease[release].components[componentName];
};

function unloadReleases(expirySeconds) {
  Object.entries(componentsPerRelease).forEach(([releaseId, release]) => {
    if (release.lastAccessed) {
      var seconds = process.hrtime(release.lastAccessed)[0];
      if (seconds > expirySeconds) {
        delete componentsPerRelease[releaseId];
        console.log(`Unloaded release ${releaseId}`);
      }
    }
  })
}

setInterval(() => unloadReleases(5), 5000);

hypernova({
  devMode: true,
  getComponent: getComponent,
  port: 3030,
});

We run load tests with a total of 100 releases. The tests are made so that they keep a rolling window of 5 versions that moves by 1 version forward every ~5seconds. This means that hypernova unloads (ie. deletes from map) an old version and loads a new one every ~5secs. Unfortunately, we noticed that the instance is leaking memory at a constant rate. After some profiling, it seems that the code of a release (react etc.) is never reclaimed by the collector, since Module.cache or something in there keeps references to it.

However, after bundling all the SSR code using webpack, so as to avoid the requires (which seem to be the culprit), we noticed the leaks were gone and the memory stopped increasing.

I'm not sure if this is a supported use-case, so we'd like your advice here. Is that something hypernova supports (ie. multiple versions in an instance) and what is the suggested way to do this?

Thanks in advance.

@kedarv
Copy link

kedarv commented Jun 13, 2022

Did you ever figure out why memory was growing when loading new bundles in? Can you expand more on how you used Webpack to avoid requireing?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants