On Thu, 8 Feb 2007, Eric H. wrote:
I’ve heard a few people talk of it (mainly, TupleSpace with a persistence
(database)), but to my knowledge nobody’s done it.
i have. if people like it, i’ll release next week.
-
it’s a hack
-
it’s persistent
jib:~ > cat a.rb
require ‘sqlitespace’
rss = Rinda::SQLiteSpace.new
any_pair = [nil, nil]
rss.read_all(any_pair).sort.each{|t| p t} # note this prints FIRST!
rss.write [Time.now, rand]
jib:~ > ruby a.rb
jib:~ > ruby a.rb
[Fri Feb 09 16:53:37 MST 2007, 0.388129945611581]
jib:~ > ruby a.rb
[Fri Feb 09 16:53:37 MST 2007, 0.388129945611581]
[Fri Feb 09 16:53:52 MST 2007, 0.250816953601316]
jib:~ > ruby a.rb
[Fri Feb 09 16:53:37 MST 2007, 0.388129945611581]
[Fri Feb 09 16:53:52 MST 2007, 0.250816953601316]
[Fri Feb 09 16:53:54 MST 2007, 0.400566845666617]
-
it’s pretty fast (and uses little cpu)
jib:~ > cat b.rb
require ‘sqlitespace’
rss = Rinda::SQLiteSpace.new
rss.transaction{ 42424.times{ write [Time.now, rand] } }
jib:~ > time ruby b.rb
real 0m19.063s
user 0m18.110s
sys 0m0.560s
-
it’s a hack
-
obviously it requires sqlite to be installed, but that’s it
-
it’s a hack
here’s the code:
harp:~ > cat sqlitespace.rb
require ‘monitor’
require ‘thread’
require ‘drb/drb’
require ‘rinda/rinda’
require ‘rinda/tuplespace’
require ‘fileutils’
require ‘base64’
begin
require ‘rubygems’
rescue LoadError
42
ensure
require ‘sqlite’
end
module Rinda
class SQLiteSpace < ::Rinda::TupleSpace
def initialize(timeout=60, prefix=home_dir)
super()
ensure
setup prefix
end
%w[ prefix dir bag read_waiter take_waiter notify_waiter ].each{|a|
attr a}
def setup(prefix=home_dir)
@prefix = prefix
FileUtils.mkdir_p @prefix
@dir = File.join @prefix, 'sqlitespace'
FileUtils.mkdir_p @dir
@bag = SQLiteBag.new File.join(@dir, 'bag')
@read_waiter = SQLiteBag.new File.join(@dir, 'read_waiter')
@take_waiter = SQLiteBag.new File.join(@dir, 'take_watier')
@notify_waiter = SQLiteBag.new File.join(@dir, 'notify_watier')
end
def transaction *a, &b
@bag.transaction{
@read_waiter.transaction{
@take_waiter.transaction{
@notify_waiter.transaction{
instance_eval &b
}
}
}
}
end
def home_dir
ENV['HOME'] || ENV['USERPROFILE'] # *nix || winblows
end
end
class SQLiteBag
class DB
SCHEMA = <<-SQL
create table bag(
id integer primary key,
size integer,
blobsize integer,
blob blob
)
SQL
%w[ path db in_transaction ].each{|a| attr a}
def initialize path
@path = path
@db = ::SQLite::Database.new(path) rescue
::SQLite::Database.new(path,0)
@in_transaction = false
setup
end
def setup
begin
@db.execute(SCHEMA){}
rescue ::SQLite::SQLException => e
42
end
end
#
# for older rubys
#
begin
Base64
def encode64 blob
Base64.encode64 blob
end
def decode64 blob
Base64.decode64 blob
end
rescue NameError
end
#
# handles DRbUndumped feature of Rinda::TupleEntry class
#
def encode obj
data = [obj.class]
obj.instance_variables.each do |ivar|
value = obj.instance_variable_get ivar
data << [ivar, value]
end
blob = encode64(Marshal.dump(data))
end
#
# handles DRbUndumped feature of Rinda::TupleEntry class
#
def decode blob
data = Marshal.load(decode64(blob))
klass = data.shift
obj = klass.allocate
data.each{|ivar, value| obj.instance_variable_set ivar, value}
obj
end
def execute *a, &b
@db.execute *a, &b
end
def transaction *a, &b
raise 'nested transaction' if @in_transaction
begin
@db.execute 'begin transaction'
@in_transaction = true
yield
ensure
@db.execute 'end transaction'
@in_transaction = false
end
end
def template_entry(ary)
if ::Rinda::TemplateEntry === ary
ary
else
::Rinda::TemplateEntry.new ary
end
end
def push(ary)
size = ary.size
blob = encode ary
blobsize = blob.size
@db.execute "insert into bag values(null, #{ size }, #{
blob.size }, ‘#{ blob }’)"
end
def delete(ary)
size = ary.size
blob = encode ary
blobsize = blob.size
@db.execute "delete from bag where size=#{ size } and
blobsize=#{ blobsize } and blob=’#{ blob }’"
end
def find_all(template, limit=nil, &block)
template= Rinda::TemplateEntry.new(Array.new(template)) if
Numeric === template
size = template.size
all = []
@db.execute “select blob from bag where size=#{ size } #{ limit
? (‘limit=%d’ % limit) : ‘’ }” do |tuple|
blob = tuple[‘blob’]
t = decode blob
if t.alive? && template.match(t)
block ? block[t] : (all << t)
end
end
block ? nil : all
end
def find(template, limit=1, &block)
find_all(template, limit).first
end
def find_all_template(tuple, limit=nil, &block)
size = tuple.size
all = []
@db.execute "select blob from bag where size=#{ size } #{ limit
? (‘limit=%d’ % limit) : ‘’ }" do |tuple|
blob = tuple[‘blob’]
t = decode blob
if t.alive? && t.match(tuple)
block ? block[t] : (all << t)
end
end
block ? nil : all
end
def delete_unless_alive
deleted = []
@db.transaction do
find_all do |tuple|
unless tuple.alive?
delete tuple
deleted << tuple
end
end
end
deleted
end
end
%w[ path db ].each{|a| attr a}
def initialize path
@path = path
@db = DB.new path
@hash = {}
end
def transaction *a, &b
@db.transaction *a, &b
end
%w( push delete find_all find find_all_template delete ).each do
|m|
module_eval <<-code
def #{ m } *a, &b
@db.#{ m } *a, &b
end
code
end
end
end
enjoy.
-a
we can deny everything, except that we have the possibility of being
better.
simply reflect on that.
-a