cronがexit 0なのに処理されない原因――flock二重取得を最小再現する

ホームラボ運用を描いたアイキャッチ画像

cronがexit 0を返しても、処理本体が動いたとは限りません。外側と内側で同じlockを取ると、自分自身を別プロセスと誤認して正常終了できます。 ここでは二重flockを再現し、lock名を分けて直します。 読み終えると、終了コードではなく仕事量まで確認する検品手順をコピペできます。

ケミの移設作業では、5本の定期処理が仕事をせず、正常終了として記録され続けました。そのうち1本は、導入後の全38回が空振りでした。ログは毎回 another instance holds the lock を残していましたが、exit 0とログ更新時刻だけを見る監視は健全と判定していました。

目次

課題:安全なSKIPもexit 0になる

多重起動を防ぐため、定期実行の外側で flock を使う構成は珍しくありません。処理本体も自前のlockを持つことがあります。問題は、両方が同じlockファイルを使った時です。

cron
└─ 外側flockが same.lock を取得
   └─ workerを起動
      └─ 内側flockも same.lock を取得しようとする
         └─ 「別実行中」と判断してexit 0

内側から見れば、多重起動を避けた正常なSKIPです。運用者から見れば、通知・同期・集計が1度も動いていません。「プロセスが安全に終わった」と「期待した仕事をした」は別の完了条件です。

設計判断:lockを一つにするか、責務ごとに分ける

方式長所失敗しやすい点向く状況
外側だけでlock構成が単純手動起動経路がlockを通らない場合がある起動経路を一本化できる
内側だけでlockどの起動経路でも同じ制御本体の実装変更が必要workerを単独でも起動する
外側と内側を別lock二つの責務を残せるlock名と意味の管理が必要外側はジョブ、内側は共有資源を守る

今回の修正は、外側と内側のlock名を分ける方法でした。外側は定期ジョブ全体の重複を防ぎ、内側は処理固有の競合を防ぎます。同じファイルを取り合わなくなり、本体まで到達しました。

再現手順:同じlockを二度取ると空振りする

まず、内側でも same.lock を取るworkerを作ります。

$ mkdir -p /tmp/flock-noop-demo
$ cd /tmp/flock-noop-demo
$ printf '%s\n' '#!/bin/sh' \
  'exec 9>./same.lock' \
  'if ! flock -n 9; then' \
  '  echo "another instance holds the lock"' \
  '  exit 0' \
  'fi' \
  'echo "processed=0"' > worker.sh
$ chmod +x worker.sh

外側から同じ same.lock を取得してworkerを起動します。

$ flock -n ./same.lock ./worker.sh
another instance holds the lock
$ echo $?
0

exit 0ですが、期待した processed=0 は出ていません。0件という値すら、処理本体が対象を数えた証拠になる点が重要です。

修正版では、外側のlockを outer.lock へ分けます。

$ flock -n ./outer.lock ./worker.sh
processed=0
$ echo $?
0

二つともexit 0です。違いは、本体が仕事量を出力したかどうかにあります。

結果:38回の空振り後、定時発火で処理本体へ到達した

実環境では、影響した5本のうち4本に別の起動問題も重なっていました。残る1本は起動問題を通過していたものの、2026-07-24 22:30の導入後、38回すべてがlock衝突で空振りしました。

lock名を分けた後、17:30の定時発火は「通知件数0件」を出力しました。この0件だけで通知経路全体の成功までは断定できません。ただし、lock検出で即returnせず、通知対象を評価する地点まで進んだ証拠にはなります。

観測空振り時修正後
exit code00
ログ更新ありあり
workerの仕事量なし通知件数0件
完了判定誤って健全本体到達を確認可能

監視へ残すべきなのは、発火時刻やexit codeだけではありません。通知なら評価件数と送信件数、同期なら対象件数と更新件数、集計なら読込行数と出力行数を残します。値が0でも、「評価まで進んだ0」と「そこへ到達しなかった空振り」を区別できます。

コピペで再現

前提条件

  • Linux環境に shflock(util-linux)が入っています。
  • /tmp/flock-noop-demo だけを使う安全な再現です。本番cronや既存lockは変更しません。

以下を上からそのまま実行すると、失敗版と修正版を比較できます。

$ mkdir -p /tmp/flock-noop-demo
$ cd /tmp/flock-noop-demo
$ printf '%s\n' '#!/bin/sh' \
  'exec 9>./same.lock' \
  'if ! flock -n 9; then' \
  '  echo "another instance holds the lock"' \
  '  exit 0' \
  'fi' \
  'echo "processed=0"' > worker.sh
$ chmod +x worker.sh
$ flock -n ./same.lock ./worker.sh
another instance holds the lock
$ echo $?
0
$ flock -n ./outer.lock ./worker.sh
processed=0
$ echo $?
0

cronへ移す場合の修正版は、外側専用lockを使います。パスは自分の配置へ置き換えてください。

*/5 * * * * /usr/bin/flock -n /run/user/1000/example-job.outer.lock /home/USER/bin/worker.sh >> /home/USER/log/example-job.log 2>&1

テスト後の後始末です。

$ rm -f /tmp/flock-noop-demo/worker.sh /tmp/flock-noop-demo/same.lock /tmp/flock-noop-demo/outer.lock
$ rmdir /tmp/flock-noop-demo

ハマり所

  • exit 0だけで合格にしないでください。処理固有の processedevaluatedupdated などを必ず1つ出します。
  • lockファイルを開けないことと、実際にlockが保持されていることは別です。権限エラーをlock保持の証拠にしないでください。
  • 外側と内側の両方を残すなら、ファイル名を変えるだけでなく、何を守るlockかをコメントへ書いてください。
  • flock -n は取得できない時に待ちません。待つ要件なら -w 秒数 を使い、timeoutも結果として記録します。

あわせて読みたい: systemd 259のUnknown assignmentで定期処理が起動しない時の切り分け

この検証を回している環境

この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。

ラボ構成のまとめを見る →
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次