テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(writeboost report) キューされたflush jobが死んだらどうなるのか

stop the subsequent flush jobs if a task is accidentally terminated · Issue #123 · akiradeveloper/dm-writeboost · GitHub

実際どうなるかもよくわからないので, どうするかはっきりと決まってはいないが, 問題としては存在するように思う.

ライトブーストは, rambufがいっぱいになった時, flush_jobをflush_wq(:: singlethread_workqueue)に突っ込む. このキューはordered_workqueueだから, キューされた順番で実行されることは保証される. これでログが順序どおり書かれることを狙う.

flush_jobの使うリソースはキューされた時点ではすべて獲得されており, そのタスク実行中にクラッシュしたりはしない. はずではあるが, 思わぬところから思わぬことが起こるのがカーネルというものであり, 一旦恐れてみる必要がある.

もし, あるflush_jobが単に破棄されて, flush_wqが後続のflush_jobを実行し続けた場合, 本当はflushされていないブロックにアクセスしてしまう可能性があるし, 後続のflush_jobがdeferred flush requestを持っていた場合, そこでackが返ってしまい, それより前のデータはすべて永続化されていることになってしまう. これは明らかにやばい.

従って少なくとも, あるflush_jobを実行する時に, もしそのidが, 最後にflushされたid + 1でないならば, 破棄するというセーフガードがあるべきである. これは2.2.2で入れようと思う.

さらにやるとすれば, なんらかの原因で破棄されたflush_jobを再生成してrequeueする仕組みを作ることになると思うが, シンプルな方法は見つからない. というのも, flush_jobが思わぬ理由で殺された場合に, それをエラーとして検知出来る可能性は低いからである. flush_job自体に成功したか?というフラグをもたせ, もし失敗していたらrequeueするというのが良いかも知れない. しかし, 本来前進しかしないはずのidを巻き戻すことになるから, 現状の実装では素直には実装出来ないし, 原理的に許容出来ない可能性が高い.

ふと思いついた良さそうな案としては, wqにキューしたflush_jobを他のFIFOキューにも繋いでおき, flush_jobが完走した場合のみこいつを自分をそのキューから取り除く. それと上記セーフガードを組み合わせると, そのキューには, 失敗したタスクからrambuf枚数分のタスクが繋がれているはずである. 同じrambufを使うsegmentを獲得しようとしたとき, そのキューに入ってるflush_jobをflush_wqにqueueする. 実装を少し変更する必要があると思うが, ミスったら回帰テストが検知出来ると思う.

static void acquire_new_rambuffer(struct wb_device *wb, u64 id)
{
    struct rambuffer *next_rambuf;
    u32 tmp32;

    // ここらへん
    wait_for_flushing(wb, SUB_ID(id, NR_RAMBUF_POOL));