lmdbのmtest.cサンプルにコメントを入れてみた。
見えるかしら?
コメントを入れた目的は、何やってるか知るため。
ドキュメントを見てもAPIが多いので、全部読むよりサンプルを眺めた方が早いと思ったのだ。
lmdbのmtest.cサンプルにコメントを入れてみた。
見えるかしら?
コメントを入れた目的は、何やってるか知るため。
ドキュメントを見てもAPIが多いので、全部読むよりサンプルを眺めた方が早いと思ったのだ。
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)
ロックファイルを使うからどうのこうの、と書かれている。
それ以外の基本的な考え方としては、
ということで、トランザクションの間は1つしかアクセスできないようだ。
これがlmdb内でやってくれるのか、外側でそうしないといかんのか。。。
ああ、今回のエラーについては、その次に書いてあった。
まず、mdb_env系のAPIはopenしたプロセスで使用しなさい、とのこと。
それ以外のAPIだったらfork先でも使ってよいということかな?
また、openは2回するな、と。
今回はこっちでエラーになったのかな?
試しに、MDB_FIXEDMAPフラグを外して実行してみると・・・エラーが出なくなった。
トランザクションでエラーになるかと思ったが、そうでもなかった。
これはbeginさせているだけだからかも。
どこかにまとめて書かれていないものか探したら、Getting Startedページにあった。
LMDB: Getting Started
そんなわけで、LMDBとしてはmdb_env_open()を複数回呼んではいかんのだけど、今回発生したエラーはそれが原因ではなかったし、複数回呼んだからといってエラーになるというわけでもないことが分かった。
以前、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を参照にしている部分は古いのかもね。
いつもsqliteを使っていたので、たまには違うものを使っておこうと思い、leveldbを使ってみることにした。
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のライブラリがあると、小さそうでよいのだけどね。
まだやっている、Linuxのメッセージキューの調査。
前回で終わりの予定だったが、msgsnd()のmsgszを自動で計算したくなったのだ。
mtext[SZ_BUF]みたいにサイズが決まっているならやりやすいけど、メッセージキューってデータ構造を問わないので、せっかくなら使うデータをずらずら並べたいところだ。
そうすると、msgszがよくわからなくなってしまう。
試しに、msgszを8192にしたまま、構造体のサイズを小さくしてみた。
うん、やっぱりcoreを吐いてしまう。
msgrcv()だけが死ぬときもあれば、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バイト目だった。
よし、これでもう間違えないぞ。
マクロにBUFSIZというものがあったのだが、なんだろうか?
stdio.hに入っていることは分かったのだが・・・。
これは、ファイルが使用するI/Oバッファサイズに関係するようだ。
Man page of SETBUF
setbuf()がBUFSIZを使い、setbuffer()はそのサイズ指定可能版ということか。
printfさせると、BUFSIZは8192だった。
環境や時代で変わる値だと思うが、8KBまではバッファリングされることになる。
Man pageの説明を読むと、意識していないことが書かれていた。
printf()とfprintf(stderr)を混ぜていると、出力されるタイミングが違うなあ、と思っていたのだ。
出力先が違うからだと思っていたが、バッファリングの種類が違っていたからなのか。
引数がFILE*だから、システムコールレベルではなく、stdioとしてサポートしていることになるのだな。
open()した場合は、OSの動作を見ておかねばなるまい。
※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を返すのは、以下のどれか。
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に相当するサイズをマクロで指定するのがよいのかもしれん。
ふっ、まだまだ私も青いのぅ。。。
タイトルは間違っていないものの、動作検証をしていないから、後日やろう。
久々に、Linuxでネットワーク関係のアプリを書こうとしている。
やりたいのは、裏で通信してくれるアプリと、そのアプリに指示を出すアプリだ。
うまく作れば1つのアプリにまとめてもよさそうだが、いかんせん、コンソールから指示を入力させたりする実装をしたくないので、別アプリにして引数で指示を与えようと考えたのだ。
これもRPCの一種になるのかな?
そこまで考えて、悩むことになった。
どうやって指示アプリと通信アプリを接続すればよいのだろうか?
確かローカル環境では、socketpairでファイルみたいなのを作ってやっていたような気がする。。
まあ、今回は指示を出すだけなので、pairは作らなくてよいか。
指示を出すだけなので排他処理はいらないように見えるが、裏で通信していて、受信したメッセージによっては送信を行うことがある。
その送信も、指示するのと同じ経路でやってしまおうと考えている。
つまり、送信処理をキューでためられるようにして、そのキューには指示アプリか受信メッセージが追加を行う。
それぞれプロセスが違うので、ここに排他処理を入れようというわけだ。
なんとなく、共有メモリを作って、セマフォで排他するんじゃなかろうか、と思ったのだが、メッセージキューがあるらしい。
msegrcv, msgsndには排他がいるとか書いていないから、排他は不要なのかな?
しかし、メッセージキューに関して検索しても、あまり新しい記事が出てこない。
実はもっと便利なライブラリがあったりするのでは?と探すが、うまく探し出せない。
関連する語彙がわからん。。。
わからんので、ここら辺を見ながら作ってみよう。
IPC: Message Queues | Shinta's Site
珍しくsocketで作っている。
serverという程のものではないのだが、socketをいくつか受け入れるしくみを作ろうとしている。
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が近いけど、ちょっと意味が違う。
一時的にリソースが使えない、みたいなのはなさそうだから、あんまりがんばっても仕方ないのか。