vim-jp / issues

有志で既知のバグや要望を検討・管理し、オフィシャルへの還元をしていきます。
https://vim-jp.org/
342 stars 11 forks source link

ch_status/getchar を利用した同期処理が statusline 内で無限ループする #1185

Open lambdalisue opened 6 years ago

lambdalisue commented 6 years ago

質問・報告の内容

ch_status/getchar を利用して job を同期的に処理する方法が statusline から呼ばれる関数内でのみ BusyLoop になります。 ~なお call getchar(1) の部分を sleep 1m にすると治ります。~ (チェックする際に操作ミスってただけ?再度やったら治りませんでした)

以下の vimrc にて Vim を起動後に set laststatus=2 とするとフリーズします(Ctrl-C でキャンセル可能)。

" vim -u ~/.vim/vimrc.min
if has('vim_starting')
  set nocompatible
endif

function! HelloWorld() abort
  let results = []
  let job = job_start(['echo', 'Hello', 'World'], {
       \ 'out_cb': { _, m -> add(results, m) },
       \})
  let ch = job_getchannel(job)
  while ch_status(ch) =~# '^\(open\|buffered\)$'
    call getchar(1)
  endwhile
  return join(results, ' ')
endfunction

" it shows 'Hello World'
echomsg HelloWorld()

set laststatus=0
set statusline=%{HelloWorld()}

echomsg 'Loaded'

Vimのバージョン

8.1.388

OSの種類/ディストリ/バージョン

その他

https://vim-jp.slack.com/archives/C03C4RC9F/p1536720963000100 の現象が statusline では治っていないってことだと思っています。 ちなみに上記は 8.1.0367 を指しています。

ichizok commented 6 years ago

8.1.0342 の変更が原因です。

修正:

--- a/src/misc2.c
+++ b/src/misc2.c
@@ -6366,11 +6366,6 @@ parse_queued_messages(void)
 {
     win_T *old_curwin = curwin;

-    // Do not handle messages while redrawing, because it may cause buffers to
-    // change or be wiped while they are being redrawn.
-    if (updating_screen)
-       return;
-
     // For Win32 mch_breakcheck() does not check for input, do it here.
 # if defined(WIN32) && defined(FEAT_JOB_CHANNEL)
     channel_handle_events(FALSE);
ichizok commented 6 years ago

なお call getchar(1) の部分を sleep 1m にすると治ります。

私の環境 (Vim 8.1.392 macOS 10.13.6/Ubuntu 18.04) では sleep でもフリーズしてます。 実際、仕組み的に sleep にしても回避できないと思うんですが...

lambdalisue commented 6 years ago

私の環境 (Vim 8.1.392 macOS 10.13.6/Ubuntu 18.04) では sleep でもフリーズしてます。 実際、仕組み的に sleep にしても回避できないと思うんですが...

おっしゃる通りで、こちらのテストミスだったようです。 取り消しておきました。:bow:

ichizok commented 6 years ago

System.Job を修正されてますが、この件が直れば元のコードのままでも問題ないと思います。

ichizok commented 6 years ago

Sent. https://github.com/vim/vim/pull/3440

lambdalisue commented 6 years ago

Let's not support that

mjk...

Shougo commented 6 years ago

Oh...

tyru commented 6 years ago

すみません、スレ違いかもしれませんが似たような問題なので質問させてください。

具体的には Async.Promise の以下のコードが問題となるのかが知りたいです (test を実行してみた限りでは無限ループになる事なく終了してるようですが…)。

https://github.com/vim-jp/vital.vim/blob/bc0bd9ae0d48bb12f3909056b311cb489bd9c494/autoload/vital/__vital__/Async/Promise.vim#L252-L257

ch_status() ではなく timer で登録したタスク内で a:promise._state の値を変えていますが、これが無限ループになる可能性はあるのかが知りたいです。

ichizok commented 6 years ago

8.1.0342 特定の処理中において job/channel callback が呼ばれなくなった。 (e.g. 'statusline' の関数内) ただし job-exit_cb は job_status() からの呼び出しは有効。

8.1.0349 getchar(1) で job/channel callback が呼ばれなくなった。 (厳密に言うと「入力をpeekする処理の内部で job/channel callback が呼ばれなくなった」のだが、これを意識してスクリプトを書くことはないと思う) GUI 版には関係ない (動作は変わらない) 。

8.1.0367 getchar(1) で job/channel callback が呼ばれるようになった。

job/channel の状態変化を待機する場合、getchar(1) からは timer callback が呼ばれないので、普通は sleep を使うと思います。 したがって、特に問題になるのは 8.1.0342 の変更です。 'statusline' 以外にも影響する箇所があるかは未確認です。

@tyru 「a:promise._state を操作する timer を job/channel callback 内で作成しており、それ (callback) が 'statusline' の関数内で実行されることを期待している」場合は危険そうです。

tyru commented 6 years ago

なるほど!詳細にありがとうございます。 しかも Async.Promise のコードまで読んでアドバイス頂いて、感謝しかないです。ありがとうございます。

lambdalisue commented 5 years ago

statusline の中で sleep を呼ぶと timer_start や job/channle の callback が呼ばれるため "Not allowe here" とかが 他のプラグインのバグっぽく 呼ばれるという面倒くさい挙動にぶち当たりました。 解決策としては「statusline の中で sleep を呼ばない」くらいしかありませんでした。

念のためメモ