Created
August 26, 2013 18:32
-
-
Save sleverbor/6344853 to your computer and use it in GitHub Desktop.
ey unicorn startup script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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