2017/08/21


Windows では子プロセスが出力した標準出力を読み取る際にバッファリングが行われる。これは親プロセスからは無効に出来ない。子プロセスが setbuf を呼び出してバッファを無効にするか、fflush で強制的にバッファをフラッシュする必要がある。

#include <windows.h>
#include <stdio.h>

int
main(int argc, char* argv[]) {
  int n = 0;
  while (1) {
    printf("%d", n++);
    Sleep(1000);
  }
  return 0;
}

Vim に job/channel 機能があるが、これらもこの入出力バッファのせいで改行されるまでの出力を読み取る事が出来ない。:terminal が取り込まれてインタラクティブなプロセスを制御できる様になったけど、Windows の :terminal は winpty を使って画面の要素を読み取って実行されている。なので標準出力そのものをリアルタイム読み取るという事は出来ない。そこで CreateRemoteThread を使ってプロセスに DLL インジェクションし、そこで setbuf を呼び出す物を作った。

GitHub - mattn/vim-iobuf

README.md vim-iobuf On Windows, stdout has a buffer. As you can see, this can be controllable with s...

https://github.com/mattn/vim-iobuf

これを使って以下の様に呼び出すと、子プロセスのバッファリングが無効にされ改行コードを出力しなくても channel を使って読み取る事が出来る。

let job = job_start(['.\\tick.exe'], {
\  'out_mode''raw'
\  'out_cb':{ch,msg->execute('echo msg'1)},
\  'exit_cb':{jo,st->execute('echo "exited"'1)
\}})
call iobuf#nobuffer(job)
iobuf

プラグインから使うプラグインとして、お使い下さい。

setbuf はライブラリ依存なので実行するランタイムに依存します。


2017/08/17


https://play.golang.org/p/CHGYhtzRsK Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS=...

Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS="windows"』だと途中でエラーになるプログラム。


osやsyscallパッケージを使っている時、こういう事があって当然なのかパッケージの実装に問題があるのか判断する規準となりそうな記述って公式になにかありますでしょうか。

https://plus.google.com/u/0/103737163485109218200/posts/gfNS2Bz4QrH?cfem=1

UNIX だと以下のコードはパスするのに Windows だとエラーになるという話。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

UNIX の場合、ファイルを開いている最中にファイルを削除するとファイルシステム上からは unlink されるけど、実際は存在していてファイルの読み込みが出来ます。そして close(2) されたタイミングで削除されます。Windows の FILE_SHARE_DELETE は一見、UNIX のそれっぽく見えるのですが動作が異なります。

例えば test.txt というファイルを作り以下のコードを実行します。

#include <windows.h>
#include <stdio.h>

int
main(int argc, char* argv[]) {
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  getchar();
  char buf[256] = {0};
  DWORD nread;
  printf("%d\n", ReadFile(h, buf, 256, &nread, NULL));
  printf("%s,%d\n", buf, nread);
  return 0;
}

getchar() で処理が停止している間に別のコマンドプロンプトでファイルを消してみて下さい。まずは実行

delete1

そして削除。ファイルが消えると思いきや残っています。そしてアクセスするとエラーになります。

delete2

Windows の FILE_SHARE_DELETE の動作は UNIX のそれとは異なるのです。例えばファイルを消した後で、ファイルが存在しないつもりで処理を続行してしまうと別のエラーが発生する事になります。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

この様な OS 間の動作の違いを吸収する事は言語レベルでは出来ません。マルチプラットフォームで動作するコードを書きたいと思われるのであれば、os.Rename や os.Remove の前に Close する事をおすすめします。

ちなみに「みんなのGo言語」にも解説が書いてあります。

みんなのGo言語[現場で使える実践テクニック] みんなのGo言語[現場で使える実践テクニック]
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧大輔, 鈴木健太
技術評論社 / (2016-09-09)
 
発送可能時間:


2017/07/24


ひさびさ Vim のエントリを書く気がします。

今から4年ほど前、Vim にスレッドセーフなメッセージキューが欲しいというメールが vim-dev 届きます。

[PATCH] Proof of concept: thread-safe message queue
https://groups.google.com/forum/#!searchin/vim_dev/tarruda%7Csort:relevance/vim_dev/65jjGqS1_VQ/fFiFrrIBwNAJ

その時はまだ、vim-dev の中にも「Vim はエディタだし必要ない」といった空気があったと思います。

[PATCH] Non-blocking job control for vimscript
https://groups.google.com/forum/#!searchin/vim_dev/tarruda%7Csort:relevance/vim_dev/QF7Bzh1YABU/02-YGr7_sCwJ

そしてその数か月後、もう一つのメールが届きます。ノンブロッキングでプロセスを制御する為のパッチです。

このパッチに関して vim-dev は特に反対意見を持っていませんでした。しかし Bram から反応が得られませんでした。昔から vim-dev を知っている人であれば分かると思うのですが、Bram はまずこういった機能追加の要望があった際にはまず皆の反応を確かめます。盛り上がったら取り込むし、盛り上がらなかったらボツという事です。僕も、大昔にソケット通信のパッチを送ってボツにされた事もありますし、mruby を組み込むパッチを送って誰からも相手されなかった事があります。

その反応が気に入らないという人もいました。この2つのメールを送ってきた人、その人もそうでした。そう、この2つのメールを送ってきた人こそが後に Neovim を作る事になった人です。Neovim は Vim のコードをベースに色々な機能を盛り込み、Vim と差別化する事で資金を調達しました。その中でも特徴的だったのが端末機能です。Vim のウィンドウの一つに端末を割り当てられる事で Vim の中だけで色々なシェル操作を完結できるという、とてもキャッチ―な物でした。非同期通信やタイマーといった API は現在の Vim にも備わっています。Bram は元々 fork を参考にしないという頑固な人なので、同じ様な機能を実装するのに時間は掛かってしまったかもしれませんが、channel/job に関しては Neovim 以上の物が実装されています。しかしながらこの端末機能についてはサポートされてきませんでした。ところがつい先日の 7/3 突然 Bram が口を開きました。

Adding terminal emulator support in Vim
https://groups.google.com/forum/#!topic/vim_dev/Q9gUWGCeTXM

長い間、私は Vim の中で端末エミュレータを追加する事がはたして良いアイデアであるのか疑問を持ち続けてきました。 それは危険なことですし、規模も大きくなり、そしてメンテナンスの悪夢にもなり得ます。 しかしそれと同時に、これは非常に、非常に便利になり得ます。

私が今回これを望んだ理由は、ssh 接続で Vim をデバッグする為です。 私はローカルで程よい設定をしていますが、家にいない時はいつも苦しんでいます。 これらの多くの制限は、いつも私が家に帰りデバッグする時間を延期してしまいます。 これを提供するプラグインがありますが、Vim をデバッグするには端末で実行する必要があるのです。

そういって突然 terminal への興味を示しました。僕は以前、Qiita にこんな記事を書いた事がありました。

Vim のソースのいじり方(:terminal を作るまで) - Qiita

この記事は [Vim Advent Calendar 2016](http://qiita.com/advent-calendar/2016/vim)、18日目の記事です。 # はじめに ここ数年で、...

http://qiita.com/mattn/items/ee438479b09055a2f305

そこで作った物を Bram に見せたところ、面白いと興味を持ってくれました。しばらくして Vim のマスターブランチに :terminal の実装が入り始めます。しかし標準入出力を操作する dumb 端末でしかありませんでした。

これはいっちょやってやるかと、winpty を使って Windows で terminal を動かすパッチを送りつけました。

support :terminal on Windows by mattn · Pull Request #1854 · vim/vim · GitHub

diff --git a/src/terminal.c b/src/terminal.c index df40866ba..bc632e7cb 100644 --- a/src/terminal.c ...

https://github.com/vim/vim/pull/1854

7/19 日の事ですから、わずか3日前の事です。Bram もとても興味を持ってくれた様でした。それから Bram と僕でパッチを書き続け、先ほど入ったパッチでようやく一段落した様な気がします。

terminal

まさか4年前に、Vim が数年後にこんな事が出来る様になるなんって思っていませんでした。自分でパッチを書いておきながら少し感動してしまいました。右上のウィンドウでこのブログ記事を書き、右下のウィンドウで ssh ログインして UNIX 版の動作検証、左でツイッターのストリームをダラダラと流し続けている光景です。

ちなみに Windows での terminal は Neovim も実現できていません。

元々、コマンドプロンプトで生活しており、Vim を常駐しない派でしたので今後この端末機能を使うかどうかは正直分かりませんが、何か Vim の新しい時代が来た様な気がしており、久しぶりに書いて良かったなと思えるパッチでした。

まだもう少しバグがあるかもしれませんし、安定しないかもしれませんが、引き続き精力的に開発していく予定です。もちろん「vim-jp が Vim の新しい機能を作っていくんだ」という気持ちに変わりはありません。ぜひ新しく追加された端末機能を使ってみて下さい。