非同期プロセスと sleep-forに関するメモ
emacs - Elisp: sleep-for doesn't block when running a test in ert - Stack Overflow
でやり取りを行ったことについてのメモ。
問題
通常であれば非同期プロセスを使う際、sleepする必要なんてないと
思うんですが、テストを書く場合は完了を待ちたいという場合がある
かと思います。で、start-processでプロセスを起動、その後すぐに
sleep-forをすると指定した時間 sleepしてくれないという問題が
ありました。ドキュメントには "guarantee a delay"と書いており、
なんで、っていうのがスタート地点でした。
(ert-deftest timetest () (let ((now (cadr (current-time)))) (start-process "echo" "*echo*" "echo" "hello world") (sleep-for 5) (should (< now (- (cadr (current-time)) 3)))))
このようなテストを行うと、ほとんどの場合テストが失敗します。
原因
原因はプロセスが終了するとき、sleep-forが中断してしまうためです。
ほとんどの場合というのは、sleep-forの前にプロセスが終了すると、
sleepが中断されないためです。
なぜこのような問題が起こるのかは不明なのです。
sleep-forの実装を見たところ複雑で、すぐにわかるという感じでは
ありませんでした。プロセス終了時に何かしらの eventが飛んできて、
叩き起こされるというイメージでしょうか。
解決
上記のようなテストケースでは以下のように対応することが
できます。叩き起こされていもいいように dummyの sleepを追加しました。
(ert-deftest timetest () (let ((now (cadr (current-time)))) (start-process "echo" "*echo*" "echo" "hello world") (sleep-for 1) ;; この sleepはおそらく叩き起こされる (sleep-for 5) ;; もう叩き起こされることはないので 5秒 sleepできる (should (< now (- (cadr (current-time)) 3)))))
ただこれは脆いです。n個の非同期プロセスの終了が発生してしまうと
対応出来ません。そのため現状は以下のようにしておくのが無難なのでは
ないかという結論にいたりました。
(ert-deftest timetest () (let ((now (float-time)) ;; current-timeより float-timeの方がベター (process-connection-type nil)) (start-process "tmp" "*tmp*" "bash" "-c" "sleep 1; echo hi") (while (< (- (float-time) now) 5) ;; 定期的に時間を確認 (sleep-for 1)) (should (< now (- (float-time) 3)))))
定期的に時間を確認し、期待する時間に達していなかったら再度 sleep
し、また時間を確認ということを繰り返します。これだと何度叩き起こされても
期待する時間まで眠れます。
process-connection-typeについてはよくわかっていないのですが、
このようなケースでは nilにしておくのがよさそうな感じです。
擬似端末使うか、PIPE使うかの選択みたいなんですが、擬似端末だと
一定量書くと、プロセス終了と同様の問題が起きるっぽいです。
(こちらについては詳しく見ていません。)
おわりに
正式な解決方法をご存知の方がいたら、教えていただければと思います。