Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

esbuild process lingers after successful build #985

Closed
vladmandic opened this issue Mar 16, 2021 · 9 comments
Closed

esbuild process lingers after successful build #985

vladmandic opened this issue Mar 16, 2021 · 9 comments

Comments

@vladmandic
Copy link

vladmandic commented Mar 16, 2021

when esbuild is executed with await esbuild.build(), it will leave esbuild process lingering on the system
when esbuild is executed with esbuild.buildSync(), it works just fine

in both cases, no issues with build itself and it completes as expected, just in async version, esbuild process is left lingering

process left lingering looks like:
node_modules/esbuild/bin/esbuild --service=0.9.2 --ping

quick reproduction:

const esbuild = require('esbuild');

function buildSync() {
  esbuild.buildSync({ entryPoints: ['test.js'], outdir: './dist', bundle: true, platform: 'node' });
}

async function buildAsync() {
  await esbuild.build({ entryPoints: ['test.js'], outdir: './dist', bundle: true, platform: 'node' });
}

async function main() {
  await buildAsync(); // completes ok but leaves esbuild process lingering on the system
  // buildSync(); // this works fine

  // wait for keypress before exiting so we can check for any lingering processes
  process.stdin.once('data', () => process.exit());
}

main();

this is new behavior in 0.9.x (tested with 0.9.2)

this is not a priority issue as no functionality is impaired

@evanw
Copy link
Owner

evanw commented Mar 17, 2021

By lingering do you mean it persists after node exits? The esbuild process persisting while node is running is by design; that's how esbuild works. What is your operating system and node version?

@vladmandic
Copy link
Author

no, it persists only while node process is active, but only when using esbuild.build().
(that's why i've added wait-for-keypress-to-exit in repro code)
but when using buildSync(), it doesn't persist at all.
you say it's by design, but why the difference between async build() and buildSync() then?

environment: esbuild 0.9.2 OS ubuntu 20.10 nodejs 15.7.0

@evanw
Copy link
Owner

evanw commented Mar 17, 2021

The only reason I added buildSync() is that there are some cases where builds must be synchronous. For example, this is the case when you need to convert TypeScript to JavaScript inside a callback that cannot return a promise (such as node's custom file extension "register" hook). If you don't need it to be synchronous, using build() instead of buildSync() is usually better. For example, plugins only work with build() but not with buildSync().

And repeated calls to build() are much faster than repeated calls to buildSync() because the child process is kept around in the async case between calls. The child process cannot currently be kept around in the sync case, although I may change buildSync() to also keep the child process around in between calls in the future if node fixes their bugs with the worker_threads API.

The reason why the child process is kept around is to improve performance for repeated calls. The reason why the API doesn't expose fine-grained control over the lifetime of the child process is that I'm trying to keep the API simple and easy to use. Previously the old startService() API (removed in 0.9.0) let you have more control over the lifetime of the child process, but that was hard to use efficiently. The problems included:

  • People were calling the build() API multiple times instead of the startService() API once and then calling service.build() because that was easier (less code). But that was creating a separate child process for each build and those child processes were competing for resources. Sharing one thread pool and one GC is more efficient than having multiple thread pools and multiple GCs.

  • Even with the startService() API, people were calling esbuild from multiple different libraries that weren't necessarily set up to communicate and share the underlying service object. It ended up with people creating multiple service objects that each had a separate child process, and those child processes also competed for resources in the same way.

Now there is just build() with no explicit service object, but all build() calls reuse an implicit service object internally. This gets the optimal performance of the old API without the ease-of-use difficulties. But it means that there is a long-lived child process (by design).

@vladmandic
Copy link
Author

that answers it...

only request would be to have an api such as esbuild.shutdown() to immediately shutdown child process when user knows it's no longer needed. in majority of cases existing behavior is spot on, this would help with corner cases.

@evanw
Copy link
Owner

evanw commented Mar 17, 2021

Can you say more about your use case? It would be helpful for me to understand what you are building and why you need the service to no longer be running.

@vladmandic
Copy link
Author

I use esbuild in 3 different typical scenarios (always via api)

  • separate build script
    no issues there since script exits when done and child process exits with it
  • dev environment with custom FS watcher that triggers esbuild and has a web server, etc.
    again, no issues since lingering is actually beneficial here
  • standalone node app that builds client module on startup and continues running as rest api server for that client
    here i don't need or want esbuild to linger and right now i've solved it by simply using buildSync() instead of build()

all-in-all, i'm covered. just having control over a child process is a good thing (and i understand your desire for simplicity)

@vladmandic
Copy link
Author

vladmandic commented Mar 17, 2021

anyhow, i'm closing this issue as the original question has been answered.

@mattfysh
Copy link

mattfysh commented Sep 14, 2023

Hi @evanw apologies to resurrect an old thread. I use the esbuild API from within a deployment script, and I've noticed this issue lately where esbuild is still running after the rest of the deployment has completed. This prevents the deployment script from ever completing, and I need the script to complete so I can save and archive the log file. Any idea why this may only be occurring since I upgraded esbuild version recently?

Here is my ps axf output, the first node script is a web server that kicks off the job, and the second is the deployment script that calls esbuild.build()

44 ?        Sl     0:00      \_ node --inspect=0.0.0.0:3111 .
64 ?        Ssl    0:01          \_ node cli <deployment_args>
84 ?        Sl     0:00              \_ /app/node_modules/.pnpm/@esbuild+linux-arm64@0.19.3/node_modules/@esbuild/linux-arm64/bin/esbuild --service=0.19.3 --ping

@L422Y
Copy link

L422Y commented Nov 5, 2023

@evanw in any case shouldn't the esbuild process terminate cleanly if it gets a SIGTERM?

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

No branches or pull requests

4 participants