I'm trying to write a script which restarts a python3 -m http.server
process when a certain directory (_site
) is deleted and then recreated. The script is below. There's an inotify command in the waitdel
function which is supposed to only block until the directory is deleted. When it is deleted, the execution goes on with a simple polling wait until the directory is created, then the server restarts, and finally we're back to waiting.
The trouble is, when _site
is deleted, inotifywait
never exits in the shell script, even tho the exact same command does exit when I run it at the very same bash prompt I run this script in, both on DELETE
and DELETE_SELF
.
I've verified that the correct inotifywait
command is run, and that the server process is not blocking the execution of the script. So, why is it not exiting in the script?
#!/usr/bin/env bash
# serve.bash --- serve content, respawn server when _site/ is deleted
# bash strict mode
set -euo pipefail
IFS=$'\n\t'
PID=0
DIR="$PWD/_site" # NO FOLLOWING SLASH OR BREAKS INOTIFYWAIT
PIDFILE="$PWD/.server.pid"
die () {
echo $0: error: $@
exit 2
}
say () {
echo $0 \[$(date)\]: $@
}
serve () {
cleanup
old="$PWD"
cd "$DIR" || die Could not cd to "$DIR"
python3 -m http.server 8000 --bind 127.0.0.1 2>&1 &
echo $! > "$PIDFILE"
cd "$old"
}
waitdel () {
while true; do
say Set up watcher for "$DIR"...
inotifywait -e delete_self "$DIR"
say "$DIR" deleted, restarting server...
# Wait&poll till the directory is recreated.
while [ ! -e "$DIR" ]; do
sleep 0.1
done
serve
done
}
cleanup () {
if [[ ! -e "$PIDFILE" ]]; then
return
fi
sync
PID="$(cat $PIDFILE)" && rm "$PIDFILE"
say Kill pid="$PID"...
[ "0" = "$PID" ] || kill -9 "$PID" \
|| die Failed to kill preexisting server on pid "$PID"
}
trap cleanup SIGINT SIGTERM EXIT
if [ -e "$PIDFILE" ]; then
if pgrep -a python3 | grep http\\.server >/dev/null; then
trap - SIGINT SIGTERM EXIT
die Stale pidfile found at "$PIDFILE", a potentially orphaned \
server might be running. Please kill it before proceeding.
else
rm "$PIDFILE" # remove stale pidfile when no server proc found
fi
fi
serve
waitdel
As per @oguz ismail's suggestion, I've tried to produce a minimal version of the script that can reproduce the issue, and here it is:
DIR="$PWD/_site"
mkdir -p $DIR
old="$PWD"
cd "$DIR" || die Could not cd to "$DIR"
python3 -m http.server 8000 --bind 127.0.0.1 2>&1 & # (1)
cd "$old"
pid=$!
inotifywait -e delete_self "$DIR" & # (2)
sleep 1
rmdir $DIR
sleep 1
kill $pid
echo kill # (3)
What's going on here: past the boilerplate stuff, in expression (1) we start a python http.server process whose CWD is $DIR
. If it is not such, i.e. the CWD is $(dirname $DIR)
, inotifywait does successfully terminate. In (3) we clean up the server process. If we kill the python process, inotifywait terminates, if we don't, it doesn't. The output from the process
Setting up watches.
Watches established.
Serving HTTP on 127.0.0.1 port 8000 (http://127.0.0.1:8000/) ...
kill
/home/g/co/gkayaalp.com/www/_site/ DELETE_SELF
suggests that inotifywait terminates after (3).
So inotifywait hangs because $DIR
is busy (I guess it is because inotify works with inodes and the python process hangs on to the inode, delaying the propagation of the deletion event). A quick way then to remedy this is to watch the parent directory instead. I modified waitdel
as such:
waitdel () {
while true; do
say Set up watcher for "$DIR"...
while inotifywait -qq -e delete "$DIR/.."; do
if [ ! -e $DIR ]; then
say "$DIR" deleted, restarting server...
# Wait&poll till the directory is recreated.
while [ ! -e "$DIR" ]; do
sleep 0.1
done
serve
fi
done
done
}
where now it tracks DELETE events on $DIR/..
, and on each, checks if $DIR
is deleted. When it is, it waits for the directory to be regenerated, and then runs serve
which kills the existing python process and spawns a new one.