2017年5月31日

[c/c++]lmdbのmstest.cサンプル

lmdbのmtest.cサンプルにコメントを入れてみた。
見えるかしら?

lmdbのmtest.c

コメントを入れた目的は、何やってるか知るため。
ドキュメントを見てもAPIが多いので、全部読むよりサンプルを眺めた方が早いと思ったのだ。

[c/c++]lmdbをマルチスレッドで使いたい

lmdbで、同じDB(といえばよいのか?)をマルチスレッドで使おうと思っている。
まあ、DBというくらいだから、ロックなんかは自動的にやってくれるだろうと何も考えていなかったのだが、なんだかうまくいっていない。


テスト用だからとエラーチェックをしていなかったので、どこでエラーを出しているか確認用のサンプルを作った。
lmdb multithread open fail

スレッド関数でトランザクションまで開いた後スリープし、その間に別スレッドで同じことを行うだけだ。
どこでエラーになったかというと、openだった。

fail: mdb_env_open(2)=16(Device or resource busy)

うーん、mdb_env_open()はEBUSYを返さないのかと思ったが、そうでもないようだ。


mdb.cを見ると、mdb_env_map()でEBUSYを返す箇所があるから、mdb_env_open()の引数でMDB_FIXEDMAPを使っているせいかもしれない。
言い訳になるが、lmdbのサンプルソースがMDB_FIXEDMAPしか使っていないから、それがデフォルトだと思ったのだよ。。。



そもそも、LMDBにとってマルチスレッドはどういう扱いなのだろうか?

LMDB: Lightning Memory-Mapped Database Manager (LMDB)

ロックファイルを使うからどうのこうの、と書かれている。
それ以外の基本的な考え方としては、

  • A thread can only use one transaction at a time, plus any child transactions.

ということで、トランザクションの間は1つしかアクセスできないようだ。
これがlmdb内でやってくれるのか、外側でそうしないといかんのか。。。


ああ、今回のエラーについては、その次に書いてあった。

  • Use an MDB_env* in the process which opened it, without fork()ing.
  • Do not have open an LMDB database twice in the same process at the same time.

まず、mdb_env系のAPIはopenしたプロセスで使用しなさい、とのこと。
それ以外のAPIだったらfork先でも使ってよいということかな?

また、openは2回するな、と。
今回はこっちでエラーになったのかな?

試しに、MDB_FIXEDMAPフラグを外して実行してみると・・・エラーが出なくなった。
トランザクションでエラーになるかと思ったが、そうでもなかった。
これはbeginさせているだけだからかも。


どこかにまとめて書かれていないものか探したら、Getting Startedページにあった。
LMDB: Getting Started

  • LMDBはPOSIX lockを使う
    • このlockは、1つのプロセスで複数回ファイルを開くと問題がある
    • だから、mdb_env_open()は1つのプロセスで複数回実行するな
      • その代わり、スレッド間でLMDB environmentを共有しなさい
      • そうせんかったら、複数openした箇所のどこかがcloseするとロックが削除されてしまう
  • transactionは、デフォルトでTLS(thread local storage)に入っている
    • read-onlyなtransactionでよいなら、MDB_NOTLSオプションを使ってもよいよ


そんなわけで、LMDBとしてはmdb_env_open()を複数回呼んではいかんのだけど、今回発生したエラーはそれが原因ではなかったし、複数回呼んだからといってエラーになるというわけでもないことが分かった。

2017年5月28日

[c/c++]lmdb

以前、Caffeを見ていたときにlmdbのデータを見る方法を調べたことがある。
http://hiro99ma.blogspot.com/2015/10/caffelmdb.html

このときは、mdb_dumpというツールでデータを見るだけだった。


そして、Cで書かれているKey-Value StoreなDBがないか探していると、lightning memory-mapped databaseなるものが出てきた。
OpenLDAPプロジェクトで作ってるようなのだが、これがlmdbだったのだ。

Symas Lightning Memory-mapped Database | Symas Corporation

世の中、狭いねぇ。


github(ぎっとはぶ、と読むのだね。私は、じっとはぶ、と読んでて怪訝な顔をされたよ)はこちら。
https://github.com/LMDB/lmdb


makeすると、ライブラリなどが生成されていた。
単独でビルドできるか、別の場所に「lmdb.h, liblmdb.a, mtest.c」だけをコピーした。

$ gcc -o tst mtest.c -L. -llmdb -pthread

うん、ビルドできた。

$ ./tst

あ、あれ、死んでしまう。。。
なんだ、dbを置くディレクトリは自分で作らんといかんのか。

$ mkdir testdb
$ ./tst

結果は分からないが、動いた。



lmdbを見つけるより前に、FineDBというものを見つけていた。
FineDB

これがlmdbをベースにしている、と書いていて、そういえばlmdbって記事にしたことあるな、という経緯でたどり着いたのだった。


FineDBはまだ動かしていないが、フォルダ構成がserver/cliとなっていて、これはこれで面白そうだ。
ただ、4年前が最後のcommitなので、lmdbを参照にしている部分は古いのかもね。

2017年5月27日

[c/c++]leveldbをcで使いたかったが、libstdc++が必要だった

いつもsqliteを使っていたので、たまには違うものを使っておこうと思い、leveldbを使ってみることにした。

google/leveldb: LevelDB is a fast key-value storage library written at Google that provides an ordered mapping from string keys to string values.


configureなどがないので、makeして、includeディレクトリと、out-staticに入っていたlibleveldb.aを別の場所にコピーして、emnl/leveldb-c-exampleからleveldb_example.cを持ってきた。

$ gcc -I./include -o tst leveldb_example.c -L. -lleveldb -pthread

・・・大量にリンクエラーが出てきた。
deleteがどうのこうのと出ているので、C++のライブラリがいるようだ。
まあ、もとがC++だから、仕方ないか。。。


しかし、snappyとかいうエラーも出ている。
データを圧縮するライブラリらしいが、必須なのか?

Makefileを見ていくと、どうやらbuild_detect_platformというスクリプトで環境をチェックして、設定をbuild_config.mkに作り、それをMakefileが読込んでいるようだ。
どうやら、私が使っている環境ではsnappyをapt-getか何かでインストール済みのようだ(実を言えば、leveldbもインストール済みだった)。
今回は最小構成にしたいので、スクリプトでsnappy検出のところをコメントアウトさせた。


これでもlibstdc++.aみたいなものはいるので、あきらめよう。

$ gcc -I./include -c leveldb_example.c
$ gcc -o tst leveldb_example.o -L. -lleveldb -pthread -lstdc++

動くようだ。


サンプルが、leveldb_put()するときに"value"という文字を書き込んでいるのだが、きっちり5byte分しか書込んでいないので、その後でleveldb_get()したあとにprintf()したときの文字が化けて出力されることがあるようなので、気になる人は\0まで書込むなり、get後に\0を足すなりするとよかろう。

Cだけで書かれたNoSQLというかKVSのライブラリがあると、小さそうでよいのだけどね。

2017年5月26日

[linux]msgsnd()のサイズを計算したい

まだやっている、Linuxのメッセージキューの調査。

前回で終わりの予定だったが、msgsnd()のmsgszを自動で計算したくなったのだ。
mtext[SZ_BUF]みたいにサイズが決まっているならやりやすいけど、メッセージキューってデータ構造を問わないので、せっかくなら使うデータをずらずら並べたいところだ。
そうすると、msgszがよくわからなくなってしまう。


試しに、msgszを8192にしたまま、構造体のサイズを小さくしてみた。
うん、やっぱりcoreを吐いてしまう。
msgrcv()だけが死ぬときもあれば、msgsnd()が死んでしまうこともある。
アラインメントによっては、死なないこともありそうだから、サイズはやはり気をつけておきたい。


あれこれ考えたが、これくらいしか思いつかない。

  • 構造体のsizeofから、mtypeの次のメンバーをoffsetof()した値を引く
  • mtypeの次のメンバーを構造体にしてしまう

後者の方がスマートかな?

2017年5月25日

[linux]msgsnd()のサイズ確認

前々回の記事だが、タイトルは間違っていないものの、検討結果が間違っていた。
修正したので、以前読まれた方は申し訳ないが、一番下だけでも再確認していただきたい。

[linux]メッセージキューのmsgszは8192を超えてはいかん

msgsnd()のサイズ指定を構造体全体のsizeofでやっていたのだけど、指定するのはmtextの部分だけのようだ、ということだ。



が、すっきりしないので検証を行った。
Xubuntu16.04だ。

https://gist.github.com/hirokuma/5916e918f40bbf82dc5723e42789007c

まず、構造体としてはmtextを8192より256くらい多めに確保する。
送信側はmtextをすべて0x77で初期化し、受信側は0xccで初期化しておく。
そしてmsgsnd()では8192を指定し、受信側のmtextが0x77以外になるポイントを探す。


結果としては、8192、と出力された。
0から始まるので、0~8191までの8192バイトは0x77であることが確認できた。

sizeof(構造体) - sizeof(long)としているサイトがあったので、8192がどこからどこまでなのかが気になっていたのだ。
もしsizeof(long)が開始地点だったら、8188バイトまでしかコピーされていなかったはずだ。
一応offsetof()で調べたが、mtextは8バイト目だった。


よし、これでもう間違えないぞ。

[clang]BUFSIZ

マクロにBUFSIZというものがあったのだが、なんだろうか?
stdio.hに入っていることは分かったのだが・・・。



これは、ファイルが使用するI/Oバッファサイズに関係するようだ。
Man page of SETBUF
setbuf()がBUFSIZを使い、setbuffer()はそのサイズ指定可能版ということか。


printfさせると、BUFSIZは8192だった。
環境や時代で変わる値だと思うが、8KBまではバッファリングされることになる。


Man pageの説明を読むと、意識していないことが書かれていた。

  • バッファリングの種類は3種類ある
    • unbuffered
      • 出力をすぐ書込む
    • block buffered
      • 文字の読み書きはブロック単位で行われる
      • 吐き出すにはfflushなどを使う
    • line buffered
      • 新しい行が出力されるか、新しい行が入力されるまでためられる
  • ファイルはすべてblock buffered
    • 初めて入出力するときにmallocが呼ばれる
  • stderrはunbuffered


printf()とfprintf(stderr)を混ぜていると、出力されるタイミングが違うなあ、と思っていたのだ。
出力先が違うからだと思っていたが、バッファリングの種類が違っていたからなのか。


引数がFILE*だから、システムコールレベルではなく、stdioとしてサポートしていることになるのだな。
open()した場合は、OSの動作を見ておかねばなるまい。

[linux]メッセージキューのmsgszは8192を超えてはいかん

※2017/05/25 22:25 修正あり

Linuxのメッセージキューは、まずはサンプルを動かすことにした。
いつものように、LinuxといいつつWindows10のBash on Ubuntu on Windows(以下 BoW)だ。

が・・・コンパイルは通るものの、msgget()でエラーになる。

Function not implemented


嫌な予感は的中した。
System V IPC is missing · Issue #1016 · Microsoft/BashOnWindows

Creators Updateでセマフォと共有メモリは対応したけど、メッセージキューはbacklogと書いてあるから、まだ残ってるという意味なのかな。


まあ、何でもかんでも期待しちゃいかんだろう。


VMに入っているUbuntu環境に持っていくと、進んだ。

が、msgsnd()でInvalid argumentが出てしまう。
Man page of MSGOP
msgsnd()がEINVALを返すのは、以下のどれか。

  1. msqid が不適切な値
  2. mtype が正の値でない
  3. msgsz が不適切な値 (0 以下か、システムで決まる値 MSGMAX よりも大きい値)

msqidはチェックしているし、mtypeも正の値を代入している。
ということは、3番のmsgszだ。

サンプルでは、こういう構造体になっていた。

struct {
    long mtype
    char mtext[BUFSIZ];
};

このBUFSIZは、8192だった。
だから、sizeofすると、たぶん8196・・・違った、8200だった。
8byteアラインなのね。


ではMSGMAXは?
これはマクロ値にはなっていないようだったが、Man pageの下の方に書いてあった。

MSGMAX

メッセージのテキストの最大サイズ: 8192 バイト (Linux では、この制限値は /proc/sys/kernel/msgmax 経由で読み出したり変更したりできる)。

うーん、ここだけ読むとメッセージのテキストの最大サイズだから使ってもよさそうなのだけど、msgszのが超えたらいかんと書いてある方が強いのだろう。


というわけで、うちの環境ではmtextが8184までOKで、8185からInvalid argumentになった。

sizeofはマクロに入れられないので、

struct {
  long mtype;
  char mtext[SZ_BUF];
};

#if 8 + SZ_BUF > 8182
#error !!!
#endif

みたいにして判定させるのが良いかも。


※追記

あれからいくつか見てみたが、msgsnd()に指定するサイズとして、sizeof(構造体)ではなく、sizeof(構造体) - sizeof(long)、としているサイトが見られた。
ま、まさか・・・。

もう一度説明を見直すと、msgsnd()のサイズとして指定するのは、struct msgbufではなく、mtextに相当するサイズ(msgsz)と書いてあるではないか!

私はmsgsnd()で構造体のサイズを指定していたのだが、それではダメだ。
そして、アラインメントもあるので、sizeof(long)を引くのも正しくなさそうな感じがする。
ここは、mtextに相当するサイズをマクロで指定するのがよいのかもしれん。


ふっ、まだまだ私も青いのぅ。。。
タイトルは間違っていないものの、動作検証をしていないから、後日やろう。

2017年5月24日

[c/c++][linux]プロセス間でのメッセージキューに似たものはあるのか?

久々に、Linuxでネットワーク関係のアプリを書こうとしている。

やりたいのは、裏で通信してくれるアプリと、そのアプリに指示を出すアプリだ。
うまく作れば1つのアプリにまとめてもよさそうだが、いかんせん、コンソールから指示を入力させたりする実装をしたくないので、別アプリにして引数で指示を与えようと考えたのだ。
これもRPCの一種になるのかな?


そこまで考えて、悩むことになった。
どうやって指示アプリと通信アプリを接続すればよいのだろうか?
確かローカル環境では、socketpairでファイルみたいなのを作ってやっていたような気がする。。
まあ、今回は指示を出すだけなので、pairは作らなくてよいか。


指示を出すだけなので排他処理はいらないように見えるが、裏で通信していて、受信したメッセージによっては送信を行うことがある。
その送信も、指示するのと同じ経路でやってしまおうと考えている。
つまり、送信処理をキューでためられるようにして、そのキューには指示アプリか受信メッセージが追加を行う。
それぞれプロセスが違うので、ここに排他処理を入れようというわけだ。


なんとなく、共有メモリを作って、セマフォで排他するんじゃなかろうか、と思ったのだが、メッセージキューがあるらしい。
msegrcv, msgsndには排他がいるとか書いていないから、排他は不要なのかな?


しかし、メッセージキューに関して検索しても、あまり新しい記事が出てこない。
実はもっと便利なライブラリがあったりするのでは?と探すが、うまく探し出せない。
関連する語彙がわからん。。。


わからんので、ここら辺を見ながら作ってみよう。
IPC: Message Queues | Shinta's Site

[c/c++]accept数を制限したい

珍しくsocketで作っている。
serverという程のものではないのだが、socketをいくつか受け入れるしくみを作ろうとしている。

Man page of LISTEN

socket()して、bind()して、listen()して、あとはaccept()でやってくるのを待つ。
教科書通りにやると、accept()で処理がブロックされて、clientからのconnect()を待つことになる。

 

今回は、同時にN個まで接続できる、というようなことをやりたい。

socketって、同じポートで接続できないんじゃなかったっけ?と思ったが、connect()する方が適当にポート番号を振って接続しに来るためか、あまり気にしなくてよかった。
まあ、次々accept()したいから、スレッド起動させたりはせんといかんが、そのくらいのようだ。

では、あとは数を制限するだけだ。
・・・・あれ、どうやって?


今回も、Windows10のBash on Ubuntu on Windowsでやっている。

まずは、一番それっぽいように見えた、listen()のbacklogを変更した。
接続キューの上限みたいだから、それでいけると思ったのだが・・・ダメだ。
よく読むと「保留中の接続のキューの最大長」とのことだ。
だから、手でconnectするアプリを次々起動させるようなことでは、保留させる必要がないということだろう。

 

linux - Is it possible to unlisten on a socket? - Stack Overflow
これを読むと、accept()してすぐclose()!らしい。

int delsock = accept(sock, NULL, NULL);
close(delsock);

まあ、確かにやりたいことは満たすのだけど、connect()でエラーが返るわけでは無いので、ちょっとなぁ。

 

しかし、connect()のエラー値を見ても、あまりふさわしいのがないな。
ECONNREFUSEDが近いけど、ちょっと意味が違う。
一時的にリソースが使えない、みたいなのはなさそうだから、あんまりがんばっても仕方ないのか。