Skip to content

Instantly share code, notes, and snippets.

@sleverbor
Created August 26, 2013 18:32
Show Gist options
  • Save sleverbor/6344853 to your computer and use it in GitHub Desktop.
Save sleverbor/6344853 to your computer and use it in GitHub Desktop.
ey unicorn startup script
#!/bin/sh
#Unicorn Control Script 0.1
#Utility funcitons
export PATH="$PATH:/bin:/sbin:/usr/bin"
#Usage
usage() {
echo "Usage: $0 <application> {start, stop, reload, deploy, aworker, rworker, status} <force (optional on start/stop)>"
echo "Kill Worker Usage: $0 <application> kill_worker {0,1,2,etc}"
exit 1
}
wait_for_symlink() {
NAME=$1
SYMLINK_TIMEOUT=$2 # timeout in seconds
COUNT=0
logger -t "unicorn_${NAME}" -s "sleeping up to ${SYMLINK_TIMEOUT} seconds while we ensure that symlink /data/${NAME}/current is correct"
while [ $COUNT -lt ${SYMLINK_TIMEOUT}0 ]; do # lexically multiply ${SYMLINK_TIMEOUT} by 10
if [ $(readlink -e /data/${NAME}/current) = $(ls -d1 /data/${NAME}/releases/* | tail -1) ]; then
logger -t "unicorn_${NAME}" -s "symlink is correct"
return
fi
while [ $(readlink -e /data/${NAME}/current) != $(ls -d1 /data/${NAME}/releases/* | tail -1) ]; do
sleep 0.1
let COUNT=$COUNT+1
done
done
if [ $COUNT -ge ${SYMLINK_TIMEOUT}0 ]; then
logger -t "unicorn_${NAME}" -s "symlink timeout is reached. aborting $0."
exit 1
fi
}
murder_unicorn(){
for child in $(ps axo pid,ppid | awk "{ if ( \$2 == $1 ) { print \$1 }}");
do
echo $child
kill -9 $child
done
kill -9 $1
}
#Stab Unicorn - Takes care of cleanup and graceful killing
stab_unicorn() {
if [ -e "${UNICORN_PID}" ]; then
OLDPID=`cat ${UNICORN_PID}`
if [ ! -d /proc/$OLDPID ] ; then
rm -f $UNICORN_PID
OLDPID=""
elif [ `grep "^Name:" /proc/$OLDPID/status | awk '{print $2}'` != "${UNICORN_GREP}" ]; then
rm -f $UNICORN_PID
OLDPID=""
fi
fi
#Pid file didn't exist, ask pgrep for it
if [ "$OLDPID" = "" ]; then
OLDPID=`pgrep -f "${UNICORN_GREP} -c /data/${APP}"`
fi
if [ "$OLDPID" != "" ]; then
#Try stopping it gracefully, lets the workers finish unless force is set
if [ -n "$1" ]; then
logger -t "unicorn_${APP}" -s "Killing existing Unicorn master forcefully"
murder_unicorn $OLDPID
else
logger -t "unicorn_${APP}" -s "Killing existing Unicorn master gracefully"
kill -QUIT $OLDPID
fi
#Wait for the the APP_SHUTDOWN_TIME before checking again, break if the process is dead
SLEEP_COUNT=0
let TOTAL_SLEEP=$APP_SHUTDOWN_TIME*4
while [ $SLEEP_COUNT -le $TOTAL_SLEEP ]; do
if [ -e /proc/$OLDPID ]; then
sleep .25
let "SLEEP_COUNT+=1"
if [ -z "$SLEEP_ALERT" ]; then
if [ $SLEEP_COUNT -ge 40 ]; then
logger -t "unicorn_${APP}" -s "Unicorn workers still running after 10 seconds"
SLEEP_ALERT=1
fi
fi
else
break
fi
done
#Does the process still exist?
if [ -e /proc/$OLDPID ]; then
#Kill the Unicorn Master
logger -t "unicorn_${APP}" -s "Killing existing Unicorn master forcefully last time"
kill -9 $OLDPID
sleep 10
#If it still exists
if [ -e /proc/$OLDPID ]; then
#Log that the master won't die
logger -t "unicorn_${APP}" -s "Unicorn master won't die!"
exit 1
fi
fi
#clean up any worker pids and master socket
PID_DIR=`dirname $UNICORN_PID`
for cpid in $(ls $PID_DIR/*worker_*.pid 2> /dev/null); do
rm $cpid
done
[ -e "$UNICORN_SOCKET" ] && rm $UNICORN_SOCKET
[ -e "$UNICORN_PID" ] && rm -f $UNICORN_PID
clear_stoplock $OPT
else
if [ "$ACTION" == "stop" ]; then
logger -t "unicorn_${APP}" -s "Unicorn not running!"
clear_stoplock $OPT
fi
fi
}
check_master() {
if [ ! -e "${UNICORN_PID}" ]; then
logger -t "unicorn_${APP}" -s "${ACTION} sent to master - Master not running"
exit 1
fi
}
function pid_status() {
P_STATUS=`ps axo pid,rss,bsdstart,command | grep $1`
pid=`echo $P_STATUS | awk {'print $1'}`
mem=`echo $P_STATUS | awk {'print sprintf("%.1f", $2/1024)'}`
uptime=`echo $P_STATUS | awk {'print $3'}`
if echo $P_STATUS | grep -q "worker"; then
worker=`echo $P_STATUS | awk {'print $5'} | sed s/worker/Worker/`
echo "Unicorn $worker - PID: ${pid} MEM: ${mem}MB Started: ${uptime}"
else
echo "Unicorn Master - PID: ${pid} MEM: ${mem}MB Started: ${uptime}"
fi
}
check_status() {
if [ ! -e "${UNICORN_PID}" ]; then
logger -t "unicorn_${APP}" -s "Can't fetch status - Master not running"
exit 1
fi
master_pid=`cat $UNICORN_PID`
if [ ! -d /proc/$master_pid ]; then
logger -t "unicorn_${APP}" -s "Can't fetch status - Master not running - MASTER HAS A STALE PID!"
exit 1
fi
echo `pid_status $master_pid`
for child in $(ps axo pid,ppid | awk "{ if ( \$2 == $master_pid ) { print \$1 }}"); do
echo `pid_status $child`
done
}
drop_stoplock() {
if [ -e "${STOP_LOCKFILE}" ]; then
if [ -z "$1" ]; then
logger -t "unicorn_${APP}" -s "Unicorn stop lock found, Unicorn is already stopping"
exit 1
fi
else
logger -t "unicorn_${APP}" -s "Stop process dropping stop lock"
fi
touch $STOP_LOCKFILE
}
clear_stoplock() {
if [ -e $STOP_LOCKFILE ]; then
if [ -z "$1" ];then
if [ "$ACTION" == "start" ]; then
logger -t "unicorn_${APP}" -s "Start process removed stop_lockfile: $STOP_LOCKFILE"
else
logger -t "unicorn_${APP}" -s "Stop lock removed"
fi
else
logger -t "unicorn_${APP}" -s "Stop lock removed"
fi
rm $STOP_LOCKFILE
fi
}
lock_check() {
if [ "$1" == "force" ]; then
exit 0
fi
SLEEP_COUNT=1
while [ -e $STOP_LOCKFILE ]; do
logger -t "unicorn_${APP}" -s "$STOP_LOCKFILE exists sleeping for .5 and checking again"
sleep .5
let "SLEEP_COUNT+=1"
if(( "$SLEEP_COUNT" > 60 )); then
clear_stoplock force
fi
done
}
#Basic Setup of default values
APP=$1 ; ACTION=$2; OPT=$3
if [ -z "$APP" ]; then
usage
logger -t "unicorn_${APP}" -s "Unicorn app not specified"
fi
if [ -e "/data/${APP}/shared/config/unicorn.conf" ]; then
source "/data/${APP}/shared/config/unicorn.conf"
#App Shutdown is UNICORN_TIMEOUT (default of 30) + 2. This should prevent some failures with shutdown
#Baseconfig in unicorn.rb uses UNICORN_TIMEOUT, must be set
if [ -z $UNICORN_TIMEOUT ]; then
UNICORN_TIMEOUT=30
fi
export UNICORN_TIMEOUT
let APP_SHUTDOWN_TIME=$UNICORN_TIMEOUT+2
if [ -z $APP_TYPE ]; then
APP_TYPE="rails"
fi
else
logger -t "unicorn_${APP}" -s "/data/${APP}/shared/config/unicorn.conf not found for app: ${APP}"
exit 1
fi
export UNICORN_BASE_CONFIG="/data/${APP}/shared/config/unicorn.conf"
export INLINEDIR="/data/${APP}/.ruby_inline"
#STOP_LOCKFILE used to make sure start waits for any stop to finish (up to 30 seconds)
STOP_LOCKFILE=`dirname $UNICORN_PID`/unicorn.lock
#Handle options
case $APP_TYPE in
rails*)
UNICORN_EXEC="unicorn_rails"
UNICORN_GREP="unicorn_rails master"
;;
rack*)
UNICORN_EXEC="unicorn"
UNICORN_GREP="unicorn master"
;;
*)
logger -t "unicorn_${APP}" -s "Unknown APP_TYPE: ${APP_TYPE}"
exit 1
;;
esac
if [ -z "$UNICORN_EXEC" ]; then
logger -t "unicorn_${APP}" -s "No UNICORN_EXEC defined"
exit 1
fi
if [ "$APP_RUBY" == "ree" ]; then
export RUBY_HEAP_MIN_SLOTS=$RUBY_HEAP_MIN_SLOTS
export RUBY_HEAP_SLOTS_INCREMENT=$RUBY_HEAP_SLOTS_INCREMENT
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=$RUBY_HEAP_SLOTS_GROWTH_FACTOR
export RUBY_GC_MALLOC_LIMIT=$RUBY_GC_MALLOC_LIMIT
fi
case $ACTION in
#Start Unicorn kills any existing Unicorns
start*)
lock_check
stab_unicorn $OPT
logger -t "unicorn_${APP}" -s " Starting Unicorn Master using /data/${APP}/shared/config/unicorn.conf"
cd $APP_ROOT
PATH=/data/$APP/current/ey_bundler_binstubs:$PATH
echo "Going to run: 'exec 2<&1 $UNICORN_EXEC -c $UNICORN_CONF -E $APP_ENV -D' from `pwd`"
exec 2<&1 bundle exec $UNICORN_EXEC -c $UNICORN_CONF -E $APP_ENV -D
STATUS=$?
exit $STATUS
;;
#Kills all unicorns
stop*)
drop_stoplock $OPT
stab_unicorn $OPT
STATUS=$?
clear_stoplock $OPT
exit $STATUS
;;
#Sends a HUP to Unicorn - reloads config file and gracefully restart all workers.
#Note: If preloadapp is true this won't reload the code! Deploy for that!
reload*)
check_master
kill -HUP `cat $UNICORN_PID`
STATUS=$?
exit $STATUS
;;
#This is how a graceful restart is done. The unicorn.rb takes care off killing workers
#gracefully
deploy*)
wait_for_symlink $APP 30
check_master
kill -USR2 `cat $UNICORN_PID`
STATUS=$?
exit $STATUS
;;
#Adds a worker, THIS WORKER WON'T BE MONITORED!
aworker*)
check_master
kill -TTIN `cat $UNICORN_PID`
STATUS=$?
exit $STATUS
;;
#Removes a worker, THIS COULD FREAK OUT THE MONITORING!
rworker*)
check_master
kill -TTOU `cat $UNICORN_PID`
STATUS=$?
exit $STATUS
;;
kill_worker*)
if [ -z "$OPT" ];then
logger -t "unicorn_${APP}" -s "kill_worker called with no worker identifie
r"
exit 1
fi
WPID=$OPT
PID_DIR=`dirname $UNICORN_PID`
kill -s QUIT `cat ${PID_DIR}/unicorn_worker_$WPID.pid`
STATUS=$?
exit $STATUS
;;
status*)
check_status
STATUS=$?
exit $STATUS
;;
*)
echo 2>&1 "Unrecognized action : $ACTION"
usage
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment