by
clemens
(06.10.2021)
Beware of Zombie Watchers in Elixir
tl;dr
Running custom background watchers with e.g. watchexec
will lead to zombie processes if not wrapped in custom shell script that kills the process once elixir quits.
In a project I wanted to run rsync
to copy my static assets to the /priv/static
folder, so that I can
easily clean that up on each build (i.e. delete old digest files).
So we need the following for this to work:
First the wrapper script in assets/watcher, don’t forget to chmod 744
the file to make it executable.
!/usr/bin/env bash
# See https://hexdocs.pm/elixir/Port.html#module-zombie-operating-system-processes
# Start the program in the background
exec "$@" &
pid1=$!
# Silence warnings from here on
exec >/dev/null 2>&1
# Read from stdin in the background and
# kill running program when stdin closes
exec 0<&0 $(
while read; do :; done
kill -KILL $pid1
) &
pid2=$!
# Clean up
wait $pid1
ret=$?
kill -KILL $pid2
exit $ret
fatal: unable to read 57c1b0bcd5c9d60f7b85ec2eb79981a66042fc37
Then add the following config to your config/dev.ex to watch all files in assets/static/ and rsync them to priv/static/ on each change:
assets_dir = Path.expand("../assets", __DIR__)
config :my_app, MyAppWeb.Endpoint,
...
watchers: [
...
{Path.join(assets_dir, "watcher"),
[
"watchexec",
"-w",
"static",
"rsync -r --delete --exclude /assets static/ ../priv/static/",
cd: assets_dir
]}
]