🐧
Linuxでsleep(1)に大きな値を指定すると24日ずつnanosleep(3)する
GNU CoreUtils の sleep は infinity が指定できる を読んで、その後の処理が気になったのでLinux環境での動作を調べてみた。
ソース読解
GNU CoreUtils の sleep は infinity が指定できるで説明されているように、xstrtod() が HUGE_VAL を返すので、xnanosleep() にはdoubleの値HUGE_VALが渡される
double seconds = 0.0;
...
if (! (xstrtod (argv[i], &p, &s, cl_strtod) || errno == ERANGE)
...
seconds += s;
...
if (xnanosleep (seconds))
xnanosleep()はdtotimespec()でdoubleをstruct timespecに変換して、nanosleep() に渡す
struct timespec ts_sleep = dtotimespec (seconds);
...
if (nanosleep (&ts_sleep, &ts_sleep) == 0)
dtotimespec()はとても大きな値に対してtimespecの最大値を返す
return make_timespec (TYPE_MAXIMUM (time_t), TIMESPEC_HZ - 1);
動作確認
nsnosleep(2)システムコールに実際に渡されている値を確認するため、strace でシステムコールの引数を見てみると
$ strace sleep infinity
execve("/bin/sleep", ["sleep", "infinity"], [/* 30 vars */]) = 0
...
nanosleep({tv_sec=2073600, tv_nsec=999999999},
tv_nsecの値は予想通りだが、tv_secの値2073600はあまりにも小さく、予想と違う。
読み直し
でnanosleepを置き換えていて、24日(24 * 24 * 60 * 60 = 2073600秒)ずつ分けて nsnosleep を呼び出していた。tv_nsecは最初の呼び出しで吸収される。
24日ずつに分けている理由は、コード中のコメントに書かれている。nanosleepで長時間待てない環境のための対処らしい。
nanosleep mishandles large sleeps due to internal overflow problems.
The worst known case of this is Linux 2.6.9 with glibc 2.3.4, which
can't sleep more than 24.85 days (2^31 milliseconds). Similarly,
cygwin 1.5.x, which can't sleep more than 49.7 days (2^32 milliseconds).
Solve this by breaking the sleep up into smaller chunks.
まとめ
というわけで、Linuxでsleep(1)に大きな値を指定すると24日ずつnanosleep(3)することがわかった。
strace の出力は24日後に以下のようになっているはず。(後で確認する)
nanosleep({tv_sec=2073600, tv_nsec=999999999}, NULL) = 0
nanosleep({tv_sec=2073600, tv_nsec=0},
(2021.12.09 追記) screen 上で strace sleep infinity して放置していたもの、期待通りの結果になっていました:
Discussion