about summary refs log tree commit
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-11-23 19:45:52 -0800
committerEric Wong <normalperson@yhbt.net>2010-11-23 19:45:52 -0800
commit61120b1268679bb8ffa157736e91e6846fd2a372 (patch)
tree26ca738b3208164e7438599b7f25b3248cf76db8
parent0f3c1c14630fda58363ffd7d3a942041ca2419eb (diff)
downloadmetropolis-61120b1268679bb8ffa157736e91e6846fd2a372.tar.gz
tc/hdb: add exclusive mode, lock disabling
The :exclusive mode behaves like TokyoTyrant and keeps the
database opened in read-write mode, preventing other processes
from accessing the database.  This will be useful on Rubies
without a GVL.

:readonly no longer disables locking by default
instead "rdlock=false" must be passed in the query
parameter.  Write locks may also be disabled with
the "wrlock=false" query parameter.
-rw-r--r--lib/metropolis/common.rb4
-rw-r--r--lib/metropolis/tc/hdb.rb27
-rw-r--r--lib/metropolis/tc/hdb/ex.rb18
-rw-r--r--lib/metropolis/tc/hdb/ro.rb1
-rw-r--r--test/rack_read_write.rb14
-rw-r--r--test/test_tc_hdb.rb26
6 files changed, 84 insertions, 6 deletions
diff --git a/lib/metropolis/common.rb b/lib/metropolis/common.rb
index 13a02d8..2a3b841 100644
--- a/lib/metropolis/common.rb
+++ b/lib/metropolis/common.rb
@@ -9,6 +9,10 @@ module Metropolis::Common
     @headers.merge!(opts[:response_headers] || {})
     @nr_slots = opts[:nr_slots] || 3
     @readonly = !!opts[:readonly]
+    @exclusive = !!opts[:exclusive]
+    if @readonly && @exclusive
+      raise ArgumentError, ":readonly and :exclusive may not be used together"
+    end
   end
 
   def r(code, body = nil)
diff --git a/lib/metropolis/tc/hdb.rb b/lib/metropolis/tc/hdb.rb
index ebd7a17..ce6d13a 100644
--- a/lib/metropolis/tc/hdb.rb
+++ b/lib/metropolis/tc/hdb.rb
@@ -4,6 +4,7 @@
 # local machine so there is never anything that needs yielding to threads.
 module Metropolis::TC::HDB
   autoload :RO, 'metropolis/tc/hdb/ro'
+  autoload :EX, 'metropolis/tc/hdb/ex'
 
   TCHDB = TokyoCabinet::HDB # :nodoc
   include Metropolis::Common
@@ -13,13 +14,34 @@ module Metropolis::TC::HDB
     path_pattern = opts[:path_pattern]
     path_pattern.scan(/%\d*x/).size == 1 or
       raise ArgumentError, "only one '/%\d*x/' may appear in #{path_pattern}"
+
+    @rd_flags = TCHDB::OREADER
+    @wr_flags = TCHDB::OWRITER
+
     @optimize = nil
     if query = opts[:query]
+      case query['rdlock']
+      when 'true', nil
+      when 'false'
+        @rd_flags |= TCHDB::ONOLCK
+      else
+        raise ArgumentError, "'rdlock' must be 'true' or 'false'"
+      end
+
+      case query['wrlock']
+      when 'true', nil
+      when 'false'
+        @wr_flags |= TCHDB::ONOLCK
+      else
+        raise ArgumentError, "'wrlock' must be 'true' or 'false'"
+      end
+
       flags = 0
       @optimize = %w(bnum apow fpow).map do |x|
         v = query[x]
         v ? v.to_i : nil
       end
+
       case large = query['large']
       when 'false', nil
       when 'true'
@@ -27,6 +49,7 @@ module Metropolis::TC::HDB
       else
         raise ArgumentError, "invalid 'large' value: #{large}"
       end
+
       case compress = query['compress']
       when nil
       when 'deflate', 'bzip', 'tcbs'
@@ -48,12 +71,10 @@ module Metropolis::TC::HDB
       end
       [ hdb, path ]
     end
-    @rd_flags = TCHDB::OREADER
-    @wr_flags = TCHDB::OWRITER
     extend(RO) if @readonly
+    extend(EX) if @exclusive
   end
 
-
   def ex!(msg, hdb)
     raise "#{msg}: #{hdb.errmsg(hdb.ecode)}"
   end
diff --git a/lib/metropolis/tc/hdb/ex.rb b/lib/metropolis/tc/hdb/ex.rb
new file mode 100644
index 0000000..5bc7f39
--- /dev/null
+++ b/lib/metropolis/tc/hdb/ex.rb
@@ -0,0 +1,18 @@
+module Metropolis::TC::HDB::EX
+  def self.extended(obj)
+   obj.instance_eval do
+      @wr_flags |= @rd_flags
+      @rd_flags = nil
+      @dbv.each { |(hdb, path)|
+        hdb.open(path, @wr_flags) or ex!(:open, hdb)
+      }
+      @ex_dbv = @dbv.map { |(hdb,_)| hdb }
+    end
+  end
+
+  def reader(key)
+    yield @ex_dbv[key.hash % @nr_slots]
+  end
+
+  alias_method :writer, :reader
+end
diff --git a/lib/metropolis/tc/hdb/ro.rb b/lib/metropolis/tc/hdb/ro.rb
index fddd73c..62ededc 100644
--- a/lib/metropolis/tc/hdb/ro.rb
+++ b/lib/metropolis/tc/hdb/ro.rb
@@ -6,7 +6,6 @@ module Metropolis::TC::HDB::RO
   def self.extended(obj)
    obj.instance_eval do
       @wr_flags = nil
-      @rd_flags |= TokyoCabinet::HDB::ONOLCK
       @dbv.each { |(hdb, path)|
         hdb.open(path, @rd_flags) or ex!(:open, hdb)
       }
diff --git a/test/rack_read_write.rb b/test/rack_read_write.rb
index 8cbeb04..b3a8a1f 100644
--- a/test/rack_read_write.rb
+++ b/test/rack_read_write.rb
@@ -5,8 +5,14 @@ require 'tempfile'
 require 'rack'
 
 module TestRackReadWrite
+  attr_reader :app
+
   def test_rack_read_write
-    app = Metropolis.new(:uri => uri)
+    @app = Metropolis.new(:uri => uri)
+    basic_rest
+  end
+
+  def basic_rest
     o = { :lint => true, :fatal => true }
     req = Rack::MockRequest.new(app)
 
@@ -42,7 +48,11 @@ module TestRackReadWrite
   def test_rack_readonly
     tmp = Metropolis.new(:uri => uri)
     tmp.close!
-    app = Metropolis.new(:uri => uri, :readonly => true)
+    @app = Metropolis.new(:uri => uri, :readonly => true)
+    basic_rest_readonly
+  end
+
+  def basic_rest_readonly
     o = { :lint => true, :fatal => true }
     req = Rack::MockRequest.new(app)
 
diff --git a/test/test_tc_hdb.rb b/test/test_tc_hdb.rb
index 7a9d548..e2229e8 100644
--- a/test/test_tc_hdb.rb
+++ b/test/test_tc_hdb.rb
@@ -196,4 +196,30 @@ class Test_TC_HDB < Test::Unit::TestCase
     assert sum <= nr_bytes, "#{sum} > #{nr_bytes}"
     obj.close!
   end
+
+  def test_exclusive
+    @app = Metropolis.new(:uri => uri, :exclusive => true)
+    assert_equal(app.method(:reader), app.method(:writer))
+    basic_rest
+  end
+
+  def test_no_rdlock
+    @app = Metropolis.new(:uri => "#{uri}?rdlock=false")
+    nolck = ::TokyoCabinet::HDB::ONOLCK
+    flags = @app.instance_variable_get(:@rd_flags)
+    assert((flags & nolck) == nolck)
+    flags = @app.instance_variable_get(:@wr_flags)
+    assert((flags & nolck) == 0)
+    basic_rest
+  end
+
+  def test_no_wrlock
+    @app = Metropolis.new(:uri => "#{uri}?wrlock=false")
+    nolck = ::TokyoCabinet::HDB::ONOLCK
+    flags = @app.instance_variable_get(:@wr_flags)
+    assert((flags & nolck) == nolck)
+    flags = @app.instance_variable_get(:@rd_flags)
+    assert((flags & nolck) == 0)
+    basic_rest
+  end
 end