about summary refs log tree commit
path: root/lib/metropolis/tdb.rb
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-12-01 10:34:32 +0000
committerEric Wong <normalperson@yhbt.net>2010-12-01 10:47:53 +0000
commitf7387cd8d1af627e5919da371cac923bbc59ef6a (patch)
treef18212e18ce20cedbc734bbac05c0c40825bd02c /lib/metropolis/tdb.rb
parent27eb2d7ebd29239a5043a528c97c6dd218d03217 (diff)
downloadmetropolis-f7387cd8d1af627e5919da371cac923bbc59ef6a.tar.gz
add basic support for the Trivial Database (TDB)
TDB supports multiple simultaneous readers and writer
*processes*, not just threads.
Diffstat (limited to 'lib/metropolis/tdb.rb')
-rw-r--r--lib/metropolis/tdb.rb74
1 files changed, 74 insertions, 0 deletions
diff --git a/lib/metropolis/tdb.rb b/lib/metropolis/tdb.rb
new file mode 100644
index 0000000..14aa3aa
--- /dev/null
+++ b/lib/metropolis/tdb.rb
@@ -0,0 +1,74 @@
+# -*- encoding: binary -*-
+
+require 'tdb'
+
+module Metropolis::TDB
+  include Metropolis::Common
+
+  def setup(opts)
+    super
+    path_pattern = opts[:path_pattern]
+    path_pattern.scan(/%\d*x/).size == 1 or
+      raise ArgumentError, "only one '/%\d*x/' may appear in #{path_pattern}"
+    @tdb_opts = { :tdb_flags => 0 }
+    if @readonly
+      @tdb_opts[:open_flags] = IO::RDONLY
+      extend Metropolis::Common::RO
+    end
+    if query = opts[:query]
+      size = query['hash_size'] and @tdb_opts[:hash_size] = size.to_i
+      hash = query['hash'] and @tdb_opts[:hash] = hash.to_sym
+
+      case query['volatile']
+      when 'true'; @tdb_opts[:tdb_flags] |= TDB::VOLATILE
+      when 'false', nil
+      else
+        raise ArgumentError, "'volatile' must be 'true' or 'false'"
+      end
+
+      case query['sync']
+      when 'true', nil
+      when 'false'; @tdb_opts[:tdb_flags] |= TDB::NOSYNC
+      else
+        raise ArgumentError, "'sync' must be 'true' or 'false'"
+      end
+    end
+
+    @dbv = (0...@nr_slots).to_a.map do |slot|
+      path = sprintf(path_pattern, slot)
+      ::TDB.new(path, @tdb_opts)
+    end
+  end
+
+  def db(key, &block)
+    yield @dbv[key.hash % @nr_slots]
+  end
+
+  def put(key, env)
+    value = env["rack.input"].read
+    db(key) do |tdb|
+      case env['HTTP_X_TT_PDMODE']
+      when '1'
+        # TODO: make this atomic
+        return r(409) if tdb.include?(key)
+      when '2'
+        value = (tdb.get(key) || "") << value
+      end
+      tdb.store(key, value)
+    end
+    r(201)
+  end
+
+  def delete(key)
+    r(db(key) { |tdb| tdb.nuke!(key) } ? 200 : 404)
+  end
+
+  def get(key, env)
+    value = db(key) { |tdb| tdb.fetch(key) } or return r(404)
+    [ 200, { 'Content-Length' => value.size.to_s }.merge!(@headers), [ value ] ]
+  end
+
+  def close!
+    @dbv.each { |tdb| tdb.close }
+  end
+end