Skip to content

Instantly share code, notes, and snippets.

@ujihisa
Created August 3, 2014 03:55
Show Gist options
  • Save ujihisa/53429203cec28d9396c6 to your computer and use it in GitHub Desktop.
Save ujihisa/53429203cec28d9396c6 to your computer and use it in GitHub Desktop.

PM1を使ったコードの改善、そしてPM2への移植

これが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をブロックしている

まずは最初の問題のみを解決しよう。

Step 1 プロンプトの設定

プロンプトの設定方法は各外部コマンドごとに違うから自力で調べよう。 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をブロックしている

Step 2 バッファの導入

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.vimrunner/process_managerで、これを作るのは本当に難しかった。 ようするに、どこまで進んだか自力で情報をどこかに保持して、その状態に応じて次の処理を選択する、というものだ。 バッファを適宜初期化しないといけない。おっと、途中でプロセスが死んだときは、ちゃんと再起動させて処理を最初からやり直すように。 また、このスクリプト自体は何度も実行できるように (= 前のプロセスが生きたままの状態でも使えるように)。

ここまでのまとめ: 状態管理ヤバい

Step 3 PM2に移植

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()がある!

(まだまだつづくよ)

Step 4 PM2の新機能

  • 最後にプロセス殺してたけど、これ生きたままなら次回も再利用できて便利。生かしておこう
  • う、そうすると最初のprompt再設定いらないなあ
    • そこでPM2のis_new()ですよ (ステマ
  • あーあと途中で急に死んだときはどうしよう
    • 単に殺してリトライしましょう。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は一回まで)

(もうちょっとだけつづくよ!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment