2019/02/24

[c/c++][lmdb]MDB_RDONLYとMDB_BAD_DBI(未解決)

またしてもLMDBについてだ。



マルチスレッドでLMDBを使っている。
書き込まずに読むだけ、という状況もよくあるため、MDB_RDONLYフラグを使ってread onlyにしていた。



LMDBでは、envとtxnについてMDB_RDONLYが使えるようになっている。
読んだり書いたりなので、envではなくtxnに対してMDB_RDONLYを指定することになる。
通常はそれで問題ないのだけど、ずーーーっと動かしていると、不意にmdb_put()でMDB_BAD_DBIが発生することがあるのだった。

MDB_BAD_DBIの説明は「The specified DBI was changed unexpectedly」。
DBIはDB Indexか、DB Integerか。ともかく、mdb_put()しようとしたDBが既に変更されていたとか、そんな意味だろう。


しかし、だ。

LMDBはマルチスレッドやマルチプロセスに対応していることになっている。
それに、mdb_txn_begin()でトランザクションを開始しないとDBのオープンができない。

だから、マルチスレッドで使う場合はenvを共有しておけばなんとかなるつもりでいた。
それが破られたのだから、私も動揺するというものだ。



エラーコードを文字列にすると「MDB_BAD_DBI: The specified DBI handle was closed/changed unexpectedly」となっているから、closeしたという可能性もあるようだ。
どっかでやってるのか。。。



ネットで検索していたとき、似たような話題を見つけていたのだが、どこかわからん。
RDONLYのあとはcommitなのかabortなのか、という質問だった気がするんだけど、URLが見つけられない。

いろいろ気になるし、まだ解決していないので、なんとかしたいものだ。


まずは、マルチスレッドで2つ動かした場合の動作を見てみよう。

https://gist.github.com/hirokuma/b810e234e6dd4b4ef8baaf1050425d3a

thread_func()を2つ立ち上げる。中でtxn_beginして、sleepして、txn_abortするだけだ。
スレッドは2つ連続して立ち上げているのだが、1番目がtxn_begin()した時点でブロックされて、2番目がtxn_begin()し終わるのは1番目がsleepから起きてtxn_abort()した後だ。
これは期待通りだ。


では、MDB_RDONLYを混ぜるとどうなるだろうか?
これは、1番目を通常、2番目をMDB_RDONLYにした場合だ。

https://gist.github.com/hirokuma/98f8d064f3bd4e049b3026da9190ae76

こうすると、txn_beginでロックされず、両方とも動いてしまう!
ロックされる前提で考えていたので、これが原因だろう。
念のため、caseの1と2を逆にしてみたが、これも両方とも動いた。


おそらくLMDBの期待としては、先にtxn_beginした方のtxnをparentで指定してtxn_beginする、なのだろう。


read onlyとそうでないものが交互になるようにやってみた。

https://gist.github.com/hirokuma/db5921856cc7e1b2409357487237238e

[2] txn_begin: RDONLY
[1] txn_begin
[2] txn_abort
[1] put
[1] txn_commit

この程度ではエラーにならないようだ。

現象が起きたログから、スレッドとmdbの動作をまねしてみた。

https://gist.github.com/hirokuma/b0b5a9a92930f5c9a91dbcab207c382b

が、これでもエラーにならない。。。

1. open RDONLY(tid=10216)
   last txnid=17
   -->db_open(tid=10216)
   -->db_open exit(tid=10216)
2. open NORMAL(tid=10217)
   last txnid=17
   -->db_open(tid=10217)
   -->db_open exit(tid=10217)
3. abort RDONLY(tid=10216)
4. open NORMAL(tid=10216)
   last txnid=17
   -->db_open(tid=10216)
5. put(tid=10217)
  -->db_w(tid=10217)
  -->txn commit(tid=10217)
  -->db_w exit(tid=10217)
   -->db_open exit(tid=10216)
txn abort(tid=10216)


そうだよな、3番でRDONLYをtxn_abortさせ、すぐに4番でNORMALなtxn_beginをするんだけど、この時点でブロックされるのだよ。
5番で別スレッドがtxn_commitすることでブロックが解除されて続きが始まる。

そうならないことがあるからMDB_BAD_DBIが起きるんだろうけど、さっぱりわからんな。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。