いくつかの操作は例外があがってくれた方が気が楽なのでそういうサブクラスを用意してみた。
require 'tokyocabinet' class TokyoBay class BDBError < RuntimeError def initialize(bdb) super(bdb.errmsg(bdb.ecode)) end end class BDB < TokyoCabinet::BDB def exception BDBError.new(self) end def cursor TokyoCabinet::BDBCUR.new(self) end def self.call_or_die(*ary) file, lineno = __FILE__, __LINE__ if /^(.+?):(\d+)(?::in `(.*)')?/ =~ caller(1)[0] file = $1 lineno = $2.to_i end ary.each do |sym| module_eval("def #{sym}(*arg); super || raise(self); end", file, lineno) end end call_or_die :open, :close call_or_die :tranbegin, :tranabort, :trancommit call_or_die :vanish end end
BDBを一つ開いたままにしておいて、ただ一つのスレッドだけが借りれるようにしてみる。
require 'monitor' class TokyoBay include MonitorMixin def initialize(fname) super() @fname = fname @bdb = BDB.new @bdb.open(@fname, TokyoCabinet::BDB::OWRITER | TokyoCabinet::BDB::OCREAT) @in_transaction = nil end def close synchronize do @bdb.close @bdb = nil end end def execute synchronize do return yield(@bdb) end end def transaction synchronize do return yield(@bdb) if @in_transaction begin @in_transaction = Thread.current @bdb.tranbegin succ = true begin return yield(@bdb) rescue Exception => e succ = false raise(e) ensure @in_transaction = nil succ ? @bdb.trancommit : @bdb.tranabort end end end end end
ふうむ。すでに107行くらい。
もうtransactionを取得しているスレッドがさらにtransactionを要求するとき、新たなtransactionをはじめない。ネストしたtransactionとならないようにしとく。
このtransactionのイディオムは20世紀から使ってる。
すぐに使い道が思いつかないからBDBのメソッドをいろいろ呼んでみる。
bay = TokyoBay.new('bay.tc') bay.execute do |bdb| bdb.vanish if (bdb.getlist('putdup') || []).size >= 10 bdb.addint('addint', 1) bdb.adddouble('addint', 1.0) bdb.put('put', 'put') bdb.putcat('putcat', 'cat') bdb.out('putcat') if bdb['putcat'].size >= 3 bdb.putdup('putdup', 'putdup') bdb.putlist('putlist', ['1', '2']) end bay.transaction do |bdb| p [:fsiz, bdb.fsiz] p [:fwmkeys, bdb.fwmkeys('p', -1)] cursor = bdb.cursor cursor.first while cursor.key p [cursor.key, cursor.val] cursor.next end end class Foo def initialize(bay) @bay = bay @bay.transaction do |bdb| bdb.putkeep('foo', 0) end end def foo @bay.transaction do |bdb| return bdb['foo'] end end def foo=(v) @bay.transaction do |bdb| bdb['foo'] = v return bdb['foo'] end end def up @bay.transaction do self.foo = foo.to_i + 1 end end end foo = Foo.new(bay) p foo.foo foo.up p foo.foo
ああそうだ。単語の数でも数えよう。putdupではなくaddintを使う方が簡単だけど、そうするとおもしろいところなく終わってしまうのであえてputdupで。
ph1 = TokyoBay.new('ph1.tc') ph1.execute do |bdb| while s = gets s.scan(/(\w+)/) do |w| bdb.putdup(w[0], '1') end end end ph2 = TokyoBay.new('ph2.tc') ph1.execute do |src| ph2.execute do |dst| cursor = src.cursor cursor.first break unless cursor.key last = cursor.key count = cursor.val.to_i while cursor.key if cursor.key == last count += cursor.val.to_i else dst.putdup(last, count) last = cursor.key count = cursor.val.to_i end cursor.next end dst.putdup(last, count) end end ph2.execute do |bdb| cursor = bdb.cursor cursor.first while cursor.key p [cursor.key, cursor.val] cursor.next end end