blob: ab664bbb6e235ba6d222ecc7397f59d0c194c42c [file] [log] [blame]
#!/bin/bash
# Copyright (c) 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
###
# A program wrapper that disallows multiple instances of the given program.
#
# It can optionally send email alerts about unintended instance conflicts.
#
# Sample Usage:
#
# No alerts, just prevent multiple instances:
# ./singleton_wrapper.sh sleep 10
#
# Default alerts - stderr output, 10 minute instance timeout:
# ALERT_ON_LOCKED=1 ./singleton_wrapper.sh sleep 10
#
# Email alerts on every instance conflict (no timeout):
# ALERT_EMAIL_RCPTS=chrome-troopers@google.com ALERT_ON_LOCKED=1 \
# ALERT_THROTTLE=0 ./singleton_wrapper.sh sleep 10
###
# Trigger an alert when there is an existing program instance.
# If set to 0, alerts will be disabled. Disabling is useful for watchdog
# cronjobs, which expect the program to be running and just want to restart it
# if it dies.
ALERT_ON_LOCKED=${ALERT_ON_LOCKED:-0}
# Number of seconds to allow a program to be locked before printing an alert.
# This can be used to prevent frequently run cronjobs from sending alert emails
# on every run, in case it's OK for previous instances to "spill over".
ALERT_THROTTLE=${ALERT_THROTTLE:-600}
# Use internal email mechanism to send alerts to the given list of recipients.
# If not set, alerts are printed on stderr. Setting this allows nicer formatting
# of alert messages when run from cronjobs, instead of defaulting to cron's
# boilerplate emails.
ALERT_EMAIL_RCPTS=${ALERT_EMAIL_RCPTS:-}
# A unique identifier for the program being run.
# The default is to use the first argument of the wrapped command, but if that's
# not reasonably unique (e.g. 'python foo.py' would use 'python'), then a
# PROGRAM_NAME should be specified such that it's less likely to conflict with
# the lock files of other wrapped programs (e.g. 'foo.py' would be better).
PROGRAM_NAME="${PROGRAM_NAME:-$1}"
if [ -z "$1" ]; then
echo "ERROR: Please specify a program to wrap."
exit 1
fi
LOCKFILE=/var/run/lock/"$LOGNAME.$PROGRAM_NAME"
(
flock -n -x 200
if [ $? != 0 ]; then
if [ "$ALERT_ON_LOCKED" != "0" ]; then
SENDMAIL=$(which sendmail)
NOWTIME=$(date +"%s")
LOCKTIME=$(stat -c %Y "$LOCKFILE")
DIFFTIME=$(($NOWTIME - $LOCKTIME))
if [ "$DIFFTIME" -gt "$ALERT_THROTTLE" ]; then
PID=$(cat "$LOCKFILE")
MSG="WARNING: Cannot execute '$@'\n"
MSG+="Previous instance [$PID] has been "
MSG+="running for $DIFFTIME seconds."
if [ -n "$ALERT_EMAIL_RCPTS" ] && [ -n "$SENDMAIL" ]; then
SENDER=$LOGNAME@$(hostname -f)
(
cat <<!
From: $SENDER
To: $ALERT_EMAIL_RCPTS
Subject: Locked Process on $HOSTNAME: $@ [pid $PID]
$(echo -e "$MSG")
!
) | $SENDMAIL -t -f $SENDER
else
echo -e "$MSG" >&2
fi
exit 2 # previous instance, alert triggered
fi
exit 1 # previous instance, not timed out, no alert
fi
exit 0 # previous instance, no alert (not an error)
fi # no previous isntance
# Run the wrapped program.
"$@" &
# Store the program PID in the lock file for alert message.
echo -n $! > "$LOCKFILE"
# Wait for the program to exit.
wait
# Truncate the lock file to clear the PID for the next run.
>"$LOCKFILE"
# Use >> to prevent changes to the lock file modification time on unsuccessful
# runs, which would otherwise throw off the DIFFTIME calculations.
) 200>>"$LOCKFILE"