ライブラリで時間経過に依存した処理を実装したい場合
単純に実時間を使ってしまうと、テストが書き難いという問題がある。 例えば「10秒でタイムアウトする」という挙動をテストしたければ、そのテストで実際に10秒以上待つ必要が出てくる。
また、こういったテストは実行タイミングに依存し、確率的に失敗するものになりやすい。
そのため最近では、(アプリケーションではなく)ライブラリでは、実時間を直接扱わないことが多くなった。
例えば、何かしらの分散アルゴリズムのノードを実装している場合には、そのノードの起動時点からの経過時刻を保持するための時計インスタンス的なものを用意するようにするのだが、その時計は実時間の経過に応じて勝手には進まないようにしておく。 代わりに「時計を指定秒数分だけ進める」的なメソッドをノードに生やして、それを呼んだ時に指定分だけノードの時計が進むようにする。
このメソッドを、典型的には定期的(e.g., 一秒毎)に呼び出すようにすれば、ほぼ実時間を反映した時計が得られるが、テストの際等には、メソッドに指定をする値を調整することで、タイムアウト処理の発生タイミング等が完全に制御可能になる。 メリット
上述の通り、テストの記述が楽になる
実時刻取得関数(典型的にはシステムコール)を呼び出す必要が無くなる(減らせる)ので、高速化が見込める タイマーの設定頻度も減らせる可能性がある
例えば、秒間数千リクエストがあるようなアルゴリズムで、かつ、その各リクエストの処理にタイムアウトが設定されているとしても、実際にはその全てに律儀にタイマーを設定する必要はない
各リクエストのタイムアウトの有効期限(論理時刻)だけを覚えておき、ノードの論理時刻を進めるための定期実行のタイミングで、有効期限が過ぎたものをまとめて処理するようにすれば、タイマーの設定頻度がリクエスト数に依存しなくなる(その分、タイムアウトの指定タイミングと実際に処理されたタイミングにズレは生じる) 極端に処理速度が低下しているノードでも、問題が発生しにくい
例えばRaftのノードの場合には、followerノードは、リーダからのハートビートを一定時間受け取らなければ(タイムアウトすれば)、死んだと見做して新しいリーダを決定する選挙を開始しようとする この際の問題は、仮にリーダが生きていたとしても、そのfollowerノードを動かしているマシン自体が何らかの要因で極端に重いと、そもそもfollowerノードに割り当てられるCPUリソースがごくわずかで、リーダは適切にハートビートを送っているにも関わらず、followerがそれを時間内に処理できずに、タイムアウト扱いされてしまう可能性があることである ただ、このタイムアウト判定に実時間ではなく、ここで取り上げたような論理時刻(という呼称は不正確かもしれないが)を用いるようにすれば、マシン自体がスローダウンしていたとしても「ノードの時刻を進める関数を呼び出し際には、常に100ms分だけ進める」と決めておけば、望まぬタイムアウトの発生を防げる可能性は高くなるかもしれない つまり、そのノードがマシンのリソースを100%使える状態ではなくても、他のリソース消費者からの影響を最小限に出来るかもしれない
実際には、いろいろと関連する問題もあって、これだけでマシンスローダウンの根本対処となることはないが、緩和策の一つとなる可能性はある
デメリット
実時間からは若干のズレが生じる (ちゃんと補正すれば、ズレも無くせるとは思うが面倒)
定期的に、メソッドを呼び出して時刻を進める必要があるので、タイムアウトの発生がまれな場合には、その定期実行のコストが高くなる可能性がある
ただし「次のタイムアウト発生時刻」を検出して、メソッド呼び出しタイミングを調整することは可能
時計を進めるためのメソッド呼び出し間隔が長いと、タイムアウト処理の実行タイミングの粒度が粗くなる
採用されなかった代替案
一時期、RustのDurationではなく、単なるカウンタ(tickというメソッドを呼ぶ度に値が一ずつ増加する論理時計)を使用して、似たようなことを実現しようとしていたことがあった。 機能的には、これでも上記の目的はほぼ達成できていたが、以下のような問題があったため、結局は採用されなかった。
パラメータの指定方法が直観的ではなくなる:
timeout_ticks: u32よりもtimeout_duration: Durationの方が直観的であり、指定や説明が楽
前者の場合には、利用者がtickメソッドを呼び出す間隔を把握している必要がある
後者の場合には、単に利用者が期待する値を設定すれば良い
Durationを増加される間隔によっては、指定された値よりも粗い粒度でタイムアウト処理が実行されることはあるかもしれないが、それは時計の分解能の問題(また timeout_duration と時計の分解能は、それぞれ独立して設定・変更が可能)
Duration指定の方が、粒度が柔軟:
途中で変更(e.g., リクエストが少ない時にはメソッド実行間隔を伸ばす)といったことが行いやすい
ただし、実際にこういったことを行いたくなったことは今のところはない(2018-08-01)