phoenixframework / esbuild

An installer for esbuild
MIT License
269 stars 36 forks source link

Multiple watchers create multiple downloads and a conflict when installing #58

Closed azizk closed 1 year ago

azizk commented 1 year ago

Given the following config.exs:

config :project, ProjectWeb.Endpoint,
  http: [ip: {0, 0, 0, 0}, port: 4000],
  debug_errors: true,
  code_reloader: true,
  cache_static_lookup: false,
  check_origin: false,
  watchers: [
    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
    another: {Esbuild, :install_and_run, [:another, ~w(--sourcemap=inline --watch)]}
  ]

When the application is started with iex -S mix phx.server, the watchers are started concurrently and they try to download and install esbuild at the same time. My educated guess is that one of them succeeds, but the other one encounters the installed binary and raises this error:

[error] Task #PID<0.1147.0> started from ProjectWeb.Endpoint terminating
** (File.CopyError) could not copy from "/home/work/.cache/phx-esbuild/package/bin/esbuild" to "/home/work/src/project/_build/esbuild-linux-x64": text file or pseudo-device busy
    (elixir 1.14.2) lib/file.ex:838: File.cp!/3
    (esbuild 0.7.0) lib/esbuild.ex:192: Esbuild.install_and_run/2
    (phoenix 1.6.15) lib/phoenix/endpoint/watcher.ex:19: Phoenix.Endpoint.Watcher.watch/2
    (elixir 1.14.2) lib/task/supervised.ex:89: Task.Supervised.invoke_mfa/2
    (stdlib 3.17.2.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Function: &Phoenix.Endpoint.Watcher.watch/2
    Args: ["another", {Esbuild, :install_and_run, [:another, ["--sourcemap=inline", "--watch"]]}]

The two esbuild profiles exist because they are separate and produce different outputs and therefore cannot be handled by one esbuild invocation.

cw789 commented 1 year ago

Use :run instead of :install_and_run on all others should be enough.


watchers: [
    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
    another: {Esbuild, :run, [:another, ~w(--sourcemap=inline --watch)]}
]
azizk commented 1 year ago

@cw789 That sounded like a great solution at first but then I thought how is the second instance going to run when it hasn't been downloaded and installed yet by the first watcher? :shrug: :smile:

I think the right solution is to write a little DownloadGenServer that runs for all watchers and notifies them when the installation is complete. What do you think?

josevalim commented 1 year ago

I think we can ship with a supervision tree and spawn a process with a name (or ID). If the name is taken, it is because it is already running. So we don't need a GenServer per se, just the supervisor is enough. PRs welcome!