JorgenVatle / meteor-vite

⚡ Replace Meteor's bundler with Vite for blazing fast build-times
MIT License
29 stars 10 forks source link

Timeouts and intervals not clearing up on live server reload in development environment #179

Open bomb-on opened 3 months ago

bomb-on commented 3 months ago

I have noticed that intervals and timeouts set with Meteor.setInterval() and Meteor.setTimeout() are not being cleared on server side upon live reload. To demonstrate this behaviour I created a test project here: https://github.com/bomb-on/meteor-tests (relevant piece of code) and also attaching a screen recording to possibly make it more clear.

Essentially, if I create an interval or a timeout in my server code and add a logging line to see when each of them is bing triggered, upon live reload I still see the log lines from previous run. This is not happening if I remove jorgenvatle:vite-bundler package. In that case, I don't see log lines from the previous run.

I observed this behaviour only in development environment and with version 2.0.1 as well as 3.0.0-meteor3.next.10 of this package.

Although this is not happening in production environment, during the development it can be quite annoying if I set a lot of them and even trigger rate limits in case I'm fetching data from a remote server and save my code very often.

Thanks for looking into this and let me know if I can assist in any way with helping to solve this :)

https://github.com/user-attachments/assets/73a58ac3-a4ac-4562-abf5-aa8e978334d2

bomb-on commented 2 months ago

Few people suggested me to try to reproduce this with Meteor 2, and I can confirm that I'm observing the same behaviour with Meteor 2.16 and following packages:

$ cat .meteor/release
METEOR@2.16

$ meteor list
ecmascript                0.16.8* Compiler plugin that supports ES2015+ in all .js files
es5-shim                  4.8.0* Shims and polyfills to improve ECMAScript 5 support
hot-module-replacement    0.5.3* Update code in development without reloading the page
jorgenvatle:vite-bundler  1.12.13* Integrate the Vite.js bundler with Meteor
meteor                    1.11.5* Core Meteor environment
server-render             0.4.1* Generic support for server-side rendering in Meteor apps
shell-server              0.5.0* Server-side component of the `meteor shell` command.
standard-minifier-css     1.9.2* Standard css minifier used with Meteor apps by default.
standard-minifier-js      2.8.1* Standard javascript minifiers used with Meteor apps by default.
static-html               1.3.2* Define static page content in .html files
typescript                4.9.5* Compiler plugin that compiles TypeScript and ECMAScript in .ts and .tsx files
webapp                    1.13.8* Serves a Meteor app over HTTP

$ cat package.json
{
  "name": "intervals-test",
  "private": true,
  "scripts": {
    "start": "meteor run",
    "test": "meteor test --once --driver-package meteortesting:mocha",
    "test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
    "visualize": "meteor --production --extra-packages bundle-visualizer"
  },
  "dependencies": {
    "@babel/runtime": "^7.23.5",
    "meteor-node-stubs": "^1.2.7",
    "meteor-vite": "^1.10.4"
  },
  "meteor": {
    "mainModule": {
      "client": "client/main.js",
      "server": "server/main.js"
    },
    "testModule": "tests/main.js"
  },
  "devDependencies": {
    "vite": "^4.5.3"
  }
}

Going to paste easily reproducible steps from Meteor Forums, just to have it in a single place:

  1. Create new Meteor project, e.g.

    $ meteor create repro --minimal --release 3.0.1  // or --release 2.16
    $ cd repro
  2. Replace the content of server/main.js with following code:

    
    import { Meteor } from 'meteor/meteor';

Meteor.startup(() => { test() });

const test = async () => { console.log('Processing data'); Meteor.setTimeout(test, 2 * 1000); };


3. Run your app:
```shell
$ meteor
  1. Change the "Processing data" string in your console.log() line, save it, and observe in logs that after live reload you will see only new string printing out. Change the string multiple times and observe that after saving the file, you’ll always see only the latest string printing out in logs.

  2. Stop your Meteor app.

  3. Add jorgenvatle:vite-bundler package and its dependencies:

    $ meteor npm install vite@4 meteor-vite -D
    $ meteor add jorgenvatle:vite-bundler
  4. Create a minimal vite.config.js file in the root of your project (optional, just to avoid Vite’s warning printing out):

    
    import { defineConfig } from 'vite';

export default defineConfig({ plugins: [], meteor: { clientEntry: 'client/main.js', }, });


8 Run your app:
```shell
$ meteor
  1. Do the same thing as in step 4 above, but this time observe that after changing the string in console.log() and saving the file, you will see the old string and the new string printing out in logs. Change the string multiple times and save your file after each change and observe that after multiple live reloads you’ll see the string from each and every change still printing out in logs.
JorgenVatle commented 1 month ago

Thanks for the very detailed report.

It is an issue I've been struggling to find a good solution for. The Vite dev server is spawned as a child process of Meteor when you start Meteor in development mode. When Meteor restarts, the web server instance appears to be restarted before there's enough time for signaling Vite to shut down gracefully. With Vite still running as a background process, the old Meteor instance likely can't fully shut down.

It is possible to just run Vite and Meteor as separate processes though. There's a bit of extra configuration necessary for Vite to get this to run reliably, but should probably lead to some better performance once you've racked up a few server restarts.

The Meteor and Vite processes need some sort of communication - mostly just to retrieve the current host and port from Vite so it Meteor can inject the Vite client code into your app's HTML template.

I'll try to have a closer look at better ways of dealing with this as soon as I have the chance. In the meantime I can follow up with you with an example config you can manually run the two processes separately which should fix the issue you're seeing.

Do share your thoughts if something comes to mind. 👍

JorgenVatle commented 3 weeks ago

Had a closer look at it this morning. It appears to be an issue with the way meteor-tool handles event listeners registered with process.on(<exitCode>, ...) within Atmosphere packages. We register some listeners to gracefully shut down Vite when Meteor is terminated, but it appears to have an opposite effect since Meteor appears to only call the most recently registered listeners before being terminated prematurely.

Made some changes to avoid the use of those listeners, which has resolved a majority of the string-along processes. There might still be a process or two that isn't caught if the dev server is hit with a lot of restarts within a few seconds, but the issue should self correct once it detects the parent process isn't running anymore. I'll have a final release ready for you sometime this week. 👍

JorgenVatle commented 2 weeks ago

This should now be fixed with meteor-vite@1.11.1 and jorgenvatle:vite-bundler@2.1.3. Let me know how it goes! 🤞