これがPM1用の元コード。sqlite3を使ってデータを追加するというもの。
let endpatterns= ['']
let P= vital#of('vital').import('ProcessManager')
call P.touch('ane', 'sqlite3 hoge.db')
call P.writeln('ane', 'drop table if exists abc;')
call P.writeln('ane', 'create table abc (' . join(map(range(1, 1000), '"ol" . v:val'), ', ') . ');')
echo P.read('ane', endpatterns)
sleep 1m
echo P.read('ane', endpatterns)
sleep 1m
echo P.read('ane', endpatterns)
sleep 1m
call P.writeln('ane', 'pragma table_info(abc);')
echo P.read('ane', endpatterns)
sleep 1m
echo P.read('ane', endpatterns)
sleep 1m
echo P.read('ane', endpatterns)
sleep 1m
call P.writeln('ane', '.q')
call P.term('ane')
これの問題点
endpatterns
がない、つまり各処理完了タイミングがつかめない。- 処理中Vimをブロックしている
まずは最初の問題のみを解決しよう。
プロンプトの設定方法は各外部コマンドごとに違うから自力で調べよう。
sqlite3の場合は-interactive
をつけて起動してから.prompt
で自分の好きなのを設定できる。
安全のため、なるべく長い文字列にするべき。
let s:P = vital#of('vital').import('ProcessManager')
function! s:blocking_wait()
let t = ''
while t !=# 'matched'
let [out, err, t] = s:P.read('ane', ['kamichidu-PM2>'])
sleep 1m
endwhile
return [out, err]
endfunction
" call s:P.term('ane')
call s:P.touch('ane', 'sqlite3 hoge.db -interactive')
call s:P.writeln('ane', '.prompt "\nkamichidu-PM2>"')
call s:blocking_wait()
call s:P.writeln('ane', 'drop table if exists abc;')
call s:blocking_wait()
call s:P.writeln('ane', 'create table abc (' . join(map(range(1, 1000), '"ol" . v:val'), ', ') . ');')
echo s:blocking_wait()
call s:P.writeln('ane', 'pragma table_info(abc);')
echo s:blocking_wait()
call s:P.writeln('ane', '.q')
call s:P.term('ane')
これを実行すると、echo s:blocking_wait()
が適切にstdout (と一応stderr)を出力してくれる。
まだ問題点が2つ。
- (NEW!) 処理に時間がかかった場合、出力結果が分断され、そのうちの一部しか
blocking_wait()
がreturnしないため、すべてがechoされるとは限らない - 処理中Vimをブロックしている
let s:P = vital#of('vital').import('ProcessManager')
function! s:blocking_wait()
let t = ''
let buffer_out = ''
let buffer_err = ''
while t !=# 'matched'
let [out, err, t] = s:P.read('ane', ['kamichidu-PM2>'])
let buffer_out .= out
let buffer_err .= err
sleep 1m
endwhile
return [buffer_out, buffer_err]
endfunction
" call s:P.term('ane')
call s:P.touch('ane', 'sqlite3 hoge.db -interactive')
call s:P.writeln('ane', '.prompt "\nkamichidu-PM2>"')
call s:blocking_wait()
call s:P.writeln('ane', 'drop table if exists abc;')
call s:blocking_wait()
call s:P.writeln('ane', 'create table abc (' . join(map(range(1, 1000), '"ol" . v:val'), ', ') . ');')
echo s:blocking_wait()
call s:P.writeln('ane', 'pragma table_info(abc);')
echo s:blocking_wait()
call s:P.writeln('ane', '.q')
call s:P.term('ane')
PMのread()
の結果はすべて一旦バッファに保存。これでmatched
のときにまとめて全部をreturnできる。
あと残る問題はVimをブロックしている点のみ。やったね! が、これを解決するのは非常に難しい。
実際に解決しているプラギンはquickrun.vim
のrunner/process_manager
で、これを作るのは本当に難しかった。
ようするに、どこまで進んだか自力で情報をどこかに保持して、その状態に応じて次の処理を選択する、というものだ。
バッファを適宜初期化しないといけない。おっと、途中でプロセスが死んだときは、ちゃんと再起動させて処理を最初からやり直すように。
また、このスクリプト自体は何度も実行できるように (= 前のプロセスが生きたままの状態でも使えるように)。
ここまでのまとめ: 状態管理ヤバい
PM2はなんと状態管理不要。な、なんだってー(ry
let s:P = vital#of('vital').import('ProcessManager')
function! s:blocking_wait(p)
call a:p.reserve_read(['kamichidu-PM2>'])
while 1
let result = a:p.go_bulk()
sleep 1m
if result.done
return [result.out, result.err]
endif
endwhile
endfunction
" call s:P.term('ane')
let p = s:P.of('ane', 'sqlite3 hoge.db -interactive')
call p.reserve_wait(['.*>'])
call p.reserve_writeln('.prompt "\nkamichidu-PM2>"')
call s:blocking_wait(p)
call p.reserve_writeln('drop table if exists abc;')
call s:blocking_wait(p)
call p.reserve_writeln('create table abc (' . join(map(range(1, 1000), '"ol" . v:val'), ', ') . ');')
echo s:blocking_wait(p)
call p.reserve_writeln('pragma table_info(abc);')
echo s:blocking_wait(p)
call p.reserve_writeln('.q')
call p.shutdown()
先ほどと同じ構造のまま、単純にPM2のAPIを用いた
s:P.touch()
のかわりにs:P.of()
の使用、そしてその返り値の使い回しs:P.writeln()
やs:P.read()
のかわりにp.reserve_writeln()
やp.reserve_read()
の使用- reserve系は何もしない
s.p.go_bulk()
で、それまでにreserverされたことをまとめてやる
ところでcall s:blocking_wait(p)
とecho s:blocking_wait(p)
の2種類の呼び出しがある。前者はたんにプロセスがプロンプトにいくまでの結果を読み捨てたいというだけだ。PM2にはなんとそれ専用のreserve_wait()
がある!
(まだまだつづくよ)
- 最後にプロセス殺してたけど、これ生きたままなら次回も再利用できて便利。生かしておこう
- う、そうすると最初のprompt再設定いらないなあ
- そこでPM2の
is_new()
ですよ (ステマ
- そこでPM2の
- あーあと途中で急に死んだときはどうしよう
- 単に殺してリトライしましょう。PM2ならできます (ステマ
let s:P = vital#of('vital').import('ProcessManager')
function! s:blocking_wait(p)
call a:p.reserve_read(['kamichidu-PM2>'])
while 1
let result = a:p.go_bulk()
sleep 1m
if result.done
return [result.out, result.err]
elseif result.fail
call p.shutdown()
return s:three_steps() " リトライ!
endif
endwhile
endfunction
function! s:three_steps()
let p = s:P.of('ane', 'sqlite3 hoge.db -interactive')
if p.is_new()
call p.reserve_wait(['.*>'])
\.reserve_writeln('.prompt "\nkamichidu-PM2>"')
\.reserve_wait(['kamichidu-PM2>'])
endif
call p.reserve_writeln('drop table if exists abc;')
\.reserve_wait(['kamichidu-PM2>'])
\.reserve_writeln('create table abc (' . join(map(range(1, 1000), '"ol" . v:val'), ', ') . ');')
echo s:blocking_wait(p)
call p.reserve_writeln('pragma table_info(abc);')
echo s:blocking_wait(p)
call p.reserve_writeln('.q')
endfunction
call s:three_steps()
reserve_*
系関数はselfを返すのでチェーン可能 (ただしreserve_read
は一回まで)
(もうちょっとだけつづくよ!)