Shell-Tips

【Shell-Tips】多重起動の禁止処理を実装しよう。

特定用途のスクリプト実装後、クーロンにより毎日決まった時間に処理が実行されることを期待していたが「いつの間にかプロセスが落ちていた・・」

しかも「何時?プロセスが落ちたのか分からない」など、異常に気づいたころには「実は数ヶ月前から処理が止まっていた。」などはよくある話です。

大抵の場合、原因は多重起動処理の禁止ロジックを実装していないがために起こる悲劇です。

この問題を回避するべく「多重起動処理」を禁止にする仕組みを実装します。

Shellの基礎知識

🟢 Shell の基礎知識(基礎編)
📌 条件分岐やループなど、実務で通用する基本操作を網羅。
└─【Shellの基礎知識】構文体系を理解して実務に使える基礎力を身につける
        ├─【Shellの基礎知識】Shellスクリプト入門|初心者が押さえる基本
        ├─【Shellの基礎知識】変数と特殊変数の使い方|初心者向け解説
        ├─【Shellの基礎知識】Shell演算子の完全ガイド|基礎から応用まで
        ├─【Shellの基礎知識】条件分岐『if』『case』の使い方を解説
        ├─【Shellの基礎知識】ループ処理の基本|効率化と応用例を解説
        ├─【Shellの基礎知識】文字列置換の基本と応用|初心者向け解説
        ├─【Shellの基礎知識】複数行テキスト出力を簡単に!ヒアドキュメント活用法
        ├─【Shellの基礎知識】関数の基本と応用|書式と戻り値を解説
        ├─【Shellの基礎知識】組み込みコマンドの活用法|最適化テクニック
        ├─【Shellの基礎知識】クォートとコマンド置換の違いと使い分け
        ├─【Shellの基礎知識】リダイレクトの基本|標準入出力とエラー出力
        ├─【Shellの基礎知識】中級者向けShellスクリプト|実用的な書き方とコツ
        └─【Shellの基礎知識】中級者向けShell活用術|10の応用Tipsを紹介

多重起動禁止処理の作成

定期的に実行するスクリプトが何らかの原因により、正常に実行できずに止まってしまう(キューイング)場合、(翌日の定時などに)後から起動された同一スクリプトのプロセスは次々にOSにキューイングされて行きます。

当然キューイングされたプロセス数分のリソースが消費され、やがてリソース不足に陥りシステムがダウンしてしまいます。

多重起動処理禁止に必要な機能

  • ロック機能
  • アンロック機能
  • 処理の中断機能

上記3つの機能を実装して、多重起動処理検知時に当該プロセスを停止します。

前提となる実行環境

Beエンジニアでシェルスクリプトを実行する環境は下記の通りとします。

実行環境

BASE_DIR(任意のディレクトリ)

  • scripts
    • bin(実行スクリプト格納領域)
      • <<各種実行スクリプト>>.sh (実行ファイル)
    • com(共通スクリプト格納領域)
      • logger.shrc(共通ログ出力ファイル)
      • utils.shrc(共通関数定義ファイル)
    • etc(設定ファイル等の格納領域)
      • infraMessage.conf(メッセージ定義ファイル)
    • log(スクリプト実行ログの格納領域)
      • スクリプト名.log 
    • tmp(テンポラリ領域)
    • rep(レポート出力領域)

本処理の仕組みは、共通で使用することを前提に共通関数定義ファイル「utils.shrc」へ実装します。

ロック機能

スクリプトが起動した時点で「/tmp」ディレクトリ配下へ「スクリプト名.lock」ディレクトリを作成し、当該スクリプトが実行中であることを後続の同一スクリプトへ知らせるためのファイル「pid」ファイルを作成します。

[<BASE_DIR>/scripts/bin/utils.shrc ]

# --------------------------------------------------
# Acquire process lock using a lock file
# --------------------------------------------------
# Purpose:
#   Prevent multiple instances of the script from running simultaneously.
# Arguments:
#   None
# Returns:
#   0: Success (lock acquired)
#   !0: Failure (another instance is already running)
# Notes:
#   - Uses a lock file instead of a directory.
#   - Relies on `flock` for safe locking.
#   - Writes the process ID to the lock file.
# --------------------------------------------------
acquireLock() {
  LOCK_FILE="${TMP_PATH}/${SCRIPT_NAME%.*}.lock"
  exec 200>"$LOCK_FILE"  # File descriptor 200 をロックファイルに関連付け
  
  # flock コマンドを使用してロックを試みる
  if flock -n 200; then
    echo $$ > "$LOCK_FILE"  # プロセスIDを書き込む
    logOut "INFO" "Lock acquired on [$LOCK_FILE]."
    export LOCK_FILE
    return 0
  else
    logOut "ERROR" "Another instance is already running. Lock file [$LOCK_FILE] exists."
    abort
  fi
}

アンロック機能

当該スクリプトが終了した時点で「/tmp」ディレクトリ配下の「スクリプト名.lock」ディレクトリを削除します。

[<BASE_DIR>/scripts/bin/utils.shrc ]

# --------------------------------------------------
# Release the acquired lock
# --------------------------------------------------
# Purpose: # Removes the lock file to allow other instances to run.
# Arguments: # None # Returns: # None # Notes:
# - This function should be called before exiting the script. # - I
t verifies the lock file exists before removing.
# --------------------------------------------------
releaseLock() {
  if [ -n "$LOCK_FILE" ]; then
    locker=$(cat "$LOCK_FILE" 2>/dev/null)
    
    if [ "$locker" = "$$" ]; then
      rm -f "$LOCK_FILE"
      logOut "INFO" "Lock released: [$LOCK_FILE]"
    else
      logOut "WARN" "Lock file exists, but owned by PID [$locker], not [$$]."
      logOut "WARN" "Lock file [$LOCK_FILE] is left untouched."
      abort
    fi
  fi
}

処理の中断機能

既に、同一スクリプトが実行中である場合、後発の同一スクリプト処理を中断します。

[<BASE_DIR>/scripts/bin/utils.shrc ]

# --------------------------------------------------
# Abort script execution with an error message.
# --------------------------------------------------
# Purpose:
#   Logs an abort message and terminates the script with exit code 1.
# Arguments:
#   param1-: Error message to log.
#      Example: "Critical failure in configuration loading."
# Returns:
#   None (terminates script with exit code 1)
# Notes:
#   - All output is sent to standard error (stderr).
#   - Includes stack trace information for easier debugging.
# --------------------------------------------------
abort() {
  local caller_info log_msg
  caller_info=$(caller 0 2>/dev/null)  # どこで呼ばれたか取得
  
  log_msg="ABORT [$caller_info]: $*"  # `formatLog` の代わりに直接メッセージ生成
  echo "$log_msg" 1>&2  # stderr にログ出力
  
  sync  # ログを確実にディスクに書き込む
  exit 1  # スクリプトを異常終了
}

1呼び出し側のシェルスクリプト作成

実行ファイルへ多重起動禁止処理を実装し、実行後30秒間のスリープ処理を記述します。同一スクリプトを時間差で実行します。つまり後発スクリプトが先発スクリプト追い越すことを禁止します。

なお、ログの出力には下記の共通ログ出力クラスを使用しています。

[<BASE_DIR>/scripts/bin/func.sh ]

多重起動禁止処理の実行結果

別々のコンソールから「utils.sh」をそれぞれ実行します。

2行目:一発目のスクリプトがPID:17492で実行されました。
3行目:ロック機能により、ロックディレクトリが作成されました。
4行目:一発目のスクリプト(PID:17492)の成功ログが出力されています。
5行目:2発目の同一スクリプトがPID:17502で実行されました。
6行目:既にロックディレクトリが存在するため、新たなディレクトリの作成に失敗。
7行目:2発目の同一スクリプト処理が中断されました。
8行目:一発目のスクリプトの処理が実行されています。
9行目:一発目のスクリプトが正常に終了しました。

実践環境を整える

ここまで学んだ知識を実際に試すには、Linuxを動かす環境が必要です。手軽に始めるならVPSを利用するのがおすすめです。
VPS徹底比較!ConoHa・さくら・Xserverの選び方



VPSを利用してLinux環境を準備したら、実際の設定は下記の記事が参考になります。
VPSに開発環境を自動構築する方法|Apache+Tomcat+PostgreSQL

よく読まれている記事

1

「私たちが日々利用しているスマートフォンやインターネット、そしてスーパーコンピュータやクラウドサービス――これらの多くがLinuxの力で動いていることをご存じですか? 無料で使えるだけでなく、高い柔軟 ...

2

Linux環境でよく目にする「Vim」という名前。サーバーにログインしたら突然Vimが開いてしまい、「どうやって入力するの?」「保存や終了ができない!」と困った経験をした人も多いのではないでしょうか。 ...

3

ネットワーク技術は現代のITインフラにおいて不可欠な要素となっています。しかし、ネットワークを深く理解するためには、その基本となる「プロトコル」と「レイヤ」の概念をしっかり把握することが重要です。 こ ...

4

この記事は、Linuxについて勉強している初心者の方向けに「Shellスクリプト」について解説します。最後まで読んで頂けましたら、Shellスクリプトはどのような役割を担っているのか?を理解出来るよう ...

5

Javaは世界中で広く使われているプログラミング言語であり、特に業務システムやWebアプリケーションの開発において欠かせない存在です。本記事では、初心者向けにJavaの基礎知識を網羅し、環境構築から基 ...

-Shell-Tips