Restart Process on File Change in Linux

Restart process on file change in Linux

Yes, you can watch a directory via the inotify system using inotifywait or inotifywatch from the inotify-tools.

inotifywait will exit upon detecting an event. Pass option -r to watch directories recursively. Example: inotifywait -r mydirectory.

You can also specify the event to watch for instead of watching all events. To wait only for file or directory content changes use option -e modify.

Automatically restart server on file change

cargo watch is actually a plugin for the Rust build tool cargo, but it can watch any files and also run shell commands:

cargo watch -w src/ -w index.html -s "./start_server.sh &"

The start_server.sh script should contain something like this:

kill $(pidof simple-http-server) # kill any running instances to free up port
simple-http-server

because when a server is still running in the background, the new instance won't be able to access the port.

This will run the command specified with -s "…" and will rerun it whenever any files or directories watched with -w change.

How do you setup script RELOAD/RESTART upon file changes using bash?

My suggestion is to let docker start a wrapper script that simply starts the real script in the background.
Then in an infinite loop:

  • using inotifywait the wrapper waits for the appropriate change
  • then kills/stop/reload/... the child process
  • starts a new one in the background again.

How do I write a bash script to restart a process if it dies?

Avoid PID-files, crons, or anything else that tries to evaluate processes that aren't their children.

There is a very good reason why in UNIX, you can ONLY wait on your children. Any method (ps parsing, pgrep, storing a PID, ...) that tries to work around that is flawed and has gaping holes in it. Just say no.

Instead you need the process that monitors your process to be the process' parent. What does this mean? It means only the process that starts your process can reliably wait for it to end. In bash, this is absolutely trivial.

until myserver; do
echo "Server 'myserver' crashed with exit code $?. Respawning.." >&2
sleep 1
done

The above piece of bash code runs myserver in an until loop. The first line starts myserver and waits for it to end. When it ends, until checks its exit status. If the exit status is 0, it means it ended gracefully (which means you asked it to shut down somehow, and it did so successfully). In that case we don't want to restart it (we just asked it to shut down!). If the exit status is not 0, until will run the loop body, which emits an error message on STDERR and restarts the loop (back to line 1) after 1 second.

Why do we wait a second? Because if something's wrong with the startup sequence of myserver and it crashes immediately, you'll have a very intensive loop of constant restarting and crashing on your hands. The sleep 1 takes away the strain from that.

Now all you need to do is start this bash script (asynchronously, probably), and it will monitor myserver and restart it as necessary. If you want to start the monitor on boot (making the server "survive" reboots), you can schedule it in your user's cron(1) with an @reboot rule. Open your cron rules with crontab:

crontab -e

Then add a rule to start your monitor script:

@reboot /usr/local/bin/myservermonitor

Alternatively; look at inittab(5) and /etc/inittab. You can add a line in there to have myserver start at a certain init level and be respawned automatically.


Edit.

Let me add some information on why not to use PID files. While they are very popular; they are also very flawed and there's no reason why you wouldn't just do it the correct way.

Consider this:

  1. PID recycling (killing the wrong process):

    • /etc/init.d/foo start: start foo, write foo's PID to /var/run/foo.pid
    • A while later: foo dies somehow.
    • A while later: any random process that starts (call it bar) takes a random PID, imagine it taking foo's old PID.
    • You notice foo's gone: /etc/init.d/foo/restart reads /var/run/foo.pid, checks to see if it's still alive, finds bar, thinks it's foo, kills it, starts a new foo.
  2. PID files go stale. You need over-complicated (or should I say, non-trivial) logic to check whether the PID file is stale, and any such logic is again vulnerable to 1..

  3. What if you don't even have write access or are in a read-only environment?

  4. It's pointless overcomplication; see how simple my example above is. No need to complicate that, at all.

See also: Are PID-files still flawed when doing it 'right'?

By the way; even worse than PID files is parsing ps! Don't ever do this.

  1. ps is very unportable. While you find it on almost every UNIX system; its arguments vary greatly if you want non-standard output. And standard output is ONLY for human consumption, not for scripted parsing!
  2. Parsing ps leads to a LOT of false positives. Take the ps aux | grep PID example, and now imagine someone starting a process with a number somewhere as argument that happens to be the same as the PID you stared your daemon with! Imagine two people starting an X session and you grepping for X to kill yours. It's just all kinds of bad.

If you don't want to manage the process yourself; there are some perfectly good systems out there that will act as monitor for your processes. Look into runit, for example.

Terminating and restarting a process

Start the new version of main.py, and then exit the old version of main.py normally, by just returning from your main code branch or by calling sys.exit. - there's no reason to force kill the old version. There's no reason that both versions can't be running at the same time as long as the old just quits after starting the new one.

You could do something like this:

def force_restart():  
os.system("python main.py")
sys.exit(0)

On Linux and Mac, it doesn't matter if you've move or delete the original version of main.py after starting the old version of the app. Linux will deal with that by keeping the old file so long as some process, in this case the initial version of the program, is still referring to that file.

Don't restart service more than 1 time within 60 seconds inotify

  1. Remember the last time you restarted httpd with lasttime=$(date +%s)
  2. If the next restart is triggered, sleep up to the specified time subtracted with the difference between the last time it was restarted and now
    difftime=$(($(date +%s) - lasttime)); if (( difftime < 60 )); then sleep $(( 60 - difftime )); fi; lasttime=$( ... )

So smth like:

    if [[.... ]] ; then # Does the file end with .py css html js

// delay up until 60 seconds from the last restart
if [ -n "${lasttime:-}" ]; then
difftime=$((60 - ($(date +%s) - lasttime)))
if ((difftime > 0)); then
sleep "$difftime"
fi
fi
lasttime=$(date +%s)

systemctl restart httpd # If so, do your thing here!
#touch /home/centos/log.txt
echo "test"
fi


Related Topics



Leave a reply



Submit