0

I have a bash script and sometimes happened that, even my script was scheduled, it was executed two times. So I added few code lines to check if the script is already in execution. Initially I hadn't a problem, but in the last three days I had got it again

PID=`echo $$`
PROCESS=${SL_ROOT_FOLDER}/xxx/xxx/xxx_xxx_$PID.txt     
ps auxww | grep $scriptToVerify | grep -v $$ | grep -v grep > $PROCESS
num_proc=`awk -v RS='\n' 'END{print NR}' $PROCESS`
if [ $num_proc -gt 1 ];
then
  sl_log "---------------------------Warning---------------------------"
  sl_log "$scriptToVerify already executed"
  sl_log "num proc $num_proc"
  sl_log "--------"
  sl_log $PROCESS
  sl_log "--------"
  exit 0;
fi

In this way I check how many rows I've got into my log and if the result is more than one, then I have two process in execution and one will be stopped. This method doesn't work correctly, though. How can I fix my code to check how many scripts in execution I have?

Matteo
  • 316
  • 1
  • 7
  • 21
  • Why do not create temporary file and deal as it is semaphore? – Romeo Ninov Feb 14 '19 at 14:55
  • What is scheduling it? – chepner Feb 14 '19 at 14:56
  • @chepner I have scheduled the time when the script starts. Every day at the same hour my script goes in execution – Matteo Feb 14 '19 at 15:02
  • This should not happen if you configured your scheduler (cron or whatever you use) correctly. I'd suggest you to fix the root cause, rather than add bells and whistles. – Andrejs Cainikovs Feb 14 '19 at 15:14
  • @AndrejsCainikovs unfortunately I'm not the person who manage the scheduler and I can't operate on it. My only option is forecast the potential error and fix it – Matteo Feb 14 '19 at 15:17
  • See [BashFAQ/045 (How can I ensure that only one instance of a script is running at a time (mutual exclusion, locking)?)](https://mywiki.wooledge.org/BashFAQ/045) and [Correct locking in shell scripts?](https://unix.stackexchange.com/q/22044/264812). – pjh Feb 14 '19 at 18:56

2 Answers2

1

Anything that involves:

  1. read some state information
  2. check results
  3. do action based on results
  4. finish

must do all three steps at the same time otherwise there is a "race" condition. For example:

  • (A) reads state
  • (A) checks results (ok)
  • (A) does action (ok)
  • (A) finishes
  • (B) reads state
  • (B) checks results (bad)
  • (B) does action (bad)
  • (B) finishes

but if timing is slightly different:

  • (A) reads state
  • (A) checks results (ok)
    • (B) reads state
    • (B) checks results (ok)
  • (A) does action (ok)
    • (B) does action (ok)
  • (A) finishes
    • (B) finishes

The usual example people give is updating bank balances.

Using your method, you may be able to reduce the frequency of your code running when it shouldn't but you can never avoid it entirely.

A better solution is to use locking. This guarantees that only one process can run at a time. For example, using flock, you can wrap all calls to your script:

flock -x /var/lock/myscript-lockfile myscript

Or, inside your script, you can do something like:

exec 300>/var/lock/myscript-lockfile
flock -x 300

# rest of script

flock -u 300

or:

exec 300>/var/logk/myscript-lockfile
if ! flock -nx 300; then
    # another process is running
    exit 1
fi

# rest of script

flock -u 300
jhnc
  • 11,310
  • 1
  • 9
  • 26
1

flock(1)

#!/bin/bash

# Makes sure we exit if flock fails.
set -e

(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

This ensures that code between "(" and ")" is run only by one process at a time and that the process does wait for a lock too long.

Credit goes to Alex B.

Andrejs Cainikovs
  • 27,428
  • 2
  • 75
  • 95