Restarting Tomcat automatically on Mac OS with Monit
Posted
When I setup Tomcat on Mac OS with Homebrew I also setup Monit to monitor if daemons fail. What I didn't do is tell Monit how to restart Tomcat should it fail.
Unfortunately it's often not just a simple case of bouncing Tomcat with launchctl when something goes wrong. This is especially the case with Tomcat as we're running it via a launch script. If launchd times out and kills the script process, any frozen Java process will stay running. That process will still be holding open port 8080 and will prevent a new instance from starting.
I came up with the following shell script to restart Tomcat fully, even if the Java process has frozen. The script asks launchd for what PID it is managing (the catalina.sh script) and then kills its direct children (the Java process). First we try with a normal kill and then fall back on a kill -9 if it won't die. Once the existing process is dead, we can bounce the service with launchctl. Finally, we wait a few seconds to ensure the service has finished starting back up before we return control back to Monit.
Save the following as /usr/local/sbin/tomcat-restart and chmod +x it:
#!/bin/bash -eexport PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbinTEMPFILE="`mktemp -t tomcat-restart.XXXXXX`"trap '{ rm -f "$TEMPFILE"; }' EXITif launchctl list -x org.apache.tomcat 2> $TEMPFILEthenln "$TEMPFILE" "$TEMPFILE.plist"BASE_PID=$(defaults read "$TEMPFILE" PID 2> /dev/null || true)rm "$TEMPFILE.plist"CHILD_PID="$(ps -axo pid,ppid | awk "{ if ( \$2 == \"$BASE_PID\" ) { print \$1 }}")"if ! [ -z "$CHILD_PID" ]thenecho "Killing Tomcat softly..."kill $CHILD_PIDsleep 2if kill -0 $CHILD_PID 2> /dev/nullthensleep 2fiif kill -0 $CHILD_PID 2> /dev/nullthenecho "It's not dead yet. Waiting a little longer..."sleep 5fiif kill -0 $CHILD_PID 2> /dev/nullthenecho "Nuking from orbit..."kill -9 $CHILD_PID $BASE_PIDfififiecho "Reversing the polarity..."sudo launchctl unload -w /Library/LaunchDaemons/org.apache.tomcat.plist || echo "It's dead Jim."sudo launchctl load -w /Library/LaunchDaemons/org.apache.tomcat.plist || echo "I can't revive it."if ! curl --connect-timeout 5 --max-time 5 --silent localhost:8080 > /dev/nullthenecho "Waiting for launch..."sleep 5fiif ! curl --connect-timeout 5 --max-time 5 --silent localhost:8080 > /dev/nullthenecho "Not ready yet..."sleep 5fiif ! curl --connect-timeout 5 --max-time 5 --silent localhost:8080 > /dev/nullthenecho "It's broken."exit 1fiecho Done.
Then all we have to do is tell Monit to run this script when the service fails. My updated service entry for Tomcat in /etc/monitrc is as follows:
check host tomcat with address 127.0.0.1if failed port 8080send "HEAD / HTTP/1.0\r\n\r\n"expect "HTTP/1.1"with timeout 5 secondsthen exec "/usr/local/sbin/tomcat-restart"
Check the syntax is valid and then reload Monit:
sudo monit -tsudo monit reload