Edited at

細かすぎて伝わらない anacron の挙動

More than 3 years have passed since last update.

対象は CentOS 6 に付属の cronie-anacron-1.4.4。


開始時間についての問題

/etc/anacrontab がRHEL 6/CentOS 6系のデフォルトのまま以下のようになっていて、


/etc/anacrontab

# the maximal random delay added to the base delay of the jobs

RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22

#period in days delay in minutes job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly


anacron 自体の起動も、デフォルトのまま /etc/cron.d/0hourly/etc/cron.hourly/0anacron によって毎時1分に起動されているとする。

このとき、上記の cron.weekly ジョブの開始時間の範囲は、何時何分から何時何分までになるか?


誤った回答

まず遅延時間を考えると、


  • デフォルトの最短遅延が6分、最長遅延は上記の RANDOM_DELAY で45分


  • cron.weekly の固定の遅延が25分

  • あわせると、cron.weekly の取り得る遅延は31分〜70分

で、


  • ジョブが実行される時間の範囲は、START_HOURS_RANGE により3時から22時の間

  • anacron 自体は、毎時1分に起動する

ことから、ジョブが実行できる状況なら 3:32〜4:11 の間のどこかで起動する……

というのは誤りである。


正しい回答

anacron のソースで START_HOURS_RANGE のチェックを行っている箇所を見てみると、以下のようになっている。


main.c

time_t jobtime = start_sec + job_array[j]->delay * 60;

t = localtime(&jobtime);
if (range_start != -1 && range_stop != -1 &&
(t->tm_hour < range_start || t->tm_hour >= range_stop))
{
/* ジョブをスキップする処理 */


この if にマッチした場合 START_HOURS_RANGE の範囲外としてジョブがスキップされるが、この基準となる時間は anacron の起動時間(ソース中の start_sec)ではなく、それにジョブごとの遅延時間(ソース中の delay)を加えた時間になっている。定義からすると当然のことで、遅延も考慮に入れた上で、ジョブが実際に動作する時間で判定しているわけだ。

なので、最初の問題の場合、2:01 の anacron の実行時に遅延が59分以上になったジョブは開始時間が 3:00 以降になるため、スキップはされず、そのまま 2:01 の回の anacron によって実行される。この場合は 3:00〜3:11 のどこかで起動することになる。

つまり、この設定での cron.weekly のジョブが起動する時刻は、


  • 3:00〜3:11 (2:01の回の anacron で起動された場合)、または

  • 3:32〜4:11 (3:01の回の anacron で起動された場合)

のどこか、というのが正解になる。


派生する問題

デフォルトの設定の場合、cron.daily ジョブの遅延時間は11分〜50分なので、2:01 の回の anacron で起動されることはなく、必ず 3:01 の回で起動する(3:12〜3:51 の間)。

しかし、anacron の動作として



  1. /var/spool/anacron/ 以下にあるジョブ名と同じ名前のファイルを読み、その中に書かれている前回実行日付から、ジョブを実行対象とするかどうかを判定する。たとえば週次ジョブなら、 /var/spool/anacron/cron.weekly ファイルに記載の前回実行日から7日経過しているかを判定する。

  2. 所定の日数が経過していて実行対象と判定されたジョブについて、この前回実行日ファイルをロックする。

  3. この後で、前述の START_HOURS_RANGE のチェックを行う。実行対象外と判定されたジョブについても、anacron 自体の完了まで前回実行日ファイルはアンロックされない。

という順序になっているため、前述のように 2:01 の回の anacron が 3:01 以降まで cron.weekly の起動待ちになっている場合、実際には起動されない cron.daily の前回実行日ファイルも一緒にロックされたままになっている。

この状態で 3:01 の回の anacron が cron.daily を起動しようとすると、上の 2. のロックに失敗し、ログに

Job 'cron.daily' locked by another anacron - skipping

と出力して起動をスキップしてしまう。結果、cron.daily ジョブは次回の起動タイミング 4:12〜4:51 まで起動が遅れることになる。

anacron で日次ジョブを実行しているが、なぜか特定の曜日だけたまに起動時間が1時間くらい遅くなる……のようなことがあったら、おそらくこういう挙動が原因と思われる。


その他

anacron の起動メカニズムや挙動については、以下がわかりやすい。

http://www.reqtc.com/column/anacron1.html

ただ、


「遅延時間」=共通設定にてランダム決定した遅延時間+

         ジョブ個別設定にてランダム決定した遅延時間


とあるが、ジョブ個別設定の方の遅延時間は、ランダムではなくそのまま加算だと思われる。

あと、Red Hatの公式ドキュメント等には

https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/ch-Automating_System_Tasks.html


RANDOM_DELAY — ジョブごとに指定されている delay in minutes 変数に追加される最大の時間分数。

最短の遅延時間は、デフォルトで 6 分に設定されています。


とあるが、6分に調整するロジックがソースのどこにも見つからない。ソース上は、


readtab.c

int i = random();

double x = 0;
x = (double) i / (double) RAND_MAX * (double) (atoi(value));
random_number = (int)x;
Debug(("Randomized delay set: %d", random_number));

のように 0 から RANDOM_DELAY までの値をとるようになっていて(valueRANDOM_DELAY の設定値)、これ以降も何か調整している形跡がない。最低6分なら、デフォルト設定では cron.daily の遅延は必ず11分以上になるはずだが、手元の cron ログでは

/var/log/cron:Feb  9 03:01:01 myhost anacron[25024]: Will run job `cron.daily' in 9 min.

というのがあるので、マニュアルの方が間違っているんじゃないかと思う。