more_rinda 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/README.md +20 -0
- data/Rakefile +2 -0
- data/example/eval0.rb +15 -0
- data/example/nqueen.rb +78 -0
- data/example/phil.rb +62 -0
- data/example/rk09/agent.rb +53 -0
- data/example/rk09/card.rb +10 -0
- data/example/rk09/card_holder.rb +18 -0
- data/example/rk09/place.rb +17 -0
- data/example/sleeping_barber_actor.rb +85 -0
- data/example/sleeping_barber_rinda.rb +102 -0
- data/install.rb +11 -0
- data/lib/rinda/eval.rb +19 -0
- data/lib/rinda/inspect.rb +63 -0
- data/lib/rinda/njet.rb +11 -0
- data/lib/rinda/ptuplespace.rb +178 -0
- data/lib/rinda/tokyotuplestore.rb +122 -0
- data/lib/rinda/tuplestore.rb +139 -0
- data/lib/rinda/version.rb +3 -0
- data/more_rinda.gemspec +20 -0
- data/test/ptuplespace_sample.rb +25 -0
- data/test/test_njet.rb +34 -0
- metadata +78 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# MoreRinda
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
|
5
|
+
Extension to rinda tuplespace
|
6
|
+
|
7
|
+
- rinda_eval = simple parallel programming utility. runs a computation inside child process and returns the result via tuple.
|
8
|
+
|
9
|
+
- ptuplespace = persistency later
|
10
|
+
|
11
|
+
- njet = rinda's pattern matcher to match any changes.
|
12
|
+
|
13
|
+
## Install
|
14
|
+
|
15
|
+
gem install more_rinda
|
16
|
+
|
17
|
+
|
18
|
+
## Example Usage.
|
19
|
+
|
20
|
+
Please see "test" and "sample" directories for the usage.
|
data/Rakefile
ADDED
data/example/eval0.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/eval'
|
3
|
+
|
4
|
+
place = Rinda::TupleSpace.new
|
5
|
+
DRb.start_service
|
6
|
+
|
7
|
+
10.times do |n|
|
8
|
+
Rinda::rinda_eval(place) do |ts|
|
9
|
+
[:sqrt, n, Math.sqrt(n)]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
10.times do |n|
|
14
|
+
p place.read([:sqrt, n, nil])
|
15
|
+
end
|
data/example/nqueen.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/eval'
|
3
|
+
|
4
|
+
module NQueen
|
5
|
+
module_function
|
6
|
+
def concat(board, row)
|
7
|
+
board.each_with_index do |v, col|
|
8
|
+
check = (v - row).abs
|
9
|
+
return nil if check == 0
|
10
|
+
return nil if check == board.size - col
|
11
|
+
end
|
12
|
+
board + [row]
|
13
|
+
end
|
14
|
+
|
15
|
+
def nq(size, board=[])
|
16
|
+
found = 0
|
17
|
+
size.times do |row|
|
18
|
+
fwd = concat(board, row)
|
19
|
+
next unless fwd
|
20
|
+
return 1 if fwd.size == size
|
21
|
+
found += nq(size, fwd)
|
22
|
+
end
|
23
|
+
found
|
24
|
+
end
|
25
|
+
|
26
|
+
def nq2(size, r1, r2)
|
27
|
+
board = concat([r1], r2)
|
28
|
+
return 0 unless board
|
29
|
+
nq(size, board)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def invoke_engine(rinda, num)
|
34
|
+
num.times do
|
35
|
+
Rinda::rinda_eval(rinda) do |ts|
|
36
|
+
begin
|
37
|
+
while true
|
38
|
+
sym, size, r1, r2 = ts.take([:nq, Integer, Integer, Integer])
|
39
|
+
ts.write([:nq_ans, size, r1, r2, NQueen.nq2(size, r1, r2)])
|
40
|
+
end
|
41
|
+
rescue
|
42
|
+
end
|
43
|
+
[:nq_engine]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_q(rinda, size)
|
49
|
+
size.times do |r1|
|
50
|
+
size.times do |r2|
|
51
|
+
rinda.write([:nq, size, r1, r2])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def take_a(rinda, size)
|
57
|
+
found = 0
|
58
|
+
size.times do |r1|
|
59
|
+
size.times do |r2|
|
60
|
+
tuple = rinda.take([:nq_ans, size, r1, r2, nil])
|
61
|
+
found += tuple[4]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
found
|
65
|
+
end
|
66
|
+
|
67
|
+
def resolve(rinda, size)
|
68
|
+
write_q(rinda, size)
|
69
|
+
take_a(rinda, size)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
DRb.start_service
|
74
|
+
rinda = Rinda::TupleSpace.new
|
75
|
+
size = (ARGV.shift || '5').to_i
|
76
|
+
|
77
|
+
invoke_engine(rinda, 4)
|
78
|
+
puts resolve(rinda, size)
|
data/example/phil.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/eval'
|
3
|
+
|
4
|
+
class Phil
|
5
|
+
def initialize(ts, n, num)
|
6
|
+
@ts = ts
|
7
|
+
@right = n
|
8
|
+
@left = (n + 1) % num
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
while running?
|
13
|
+
do_it(:think)
|
14
|
+
@ts.take([:room_ticket])
|
15
|
+
@ts.take([:chopstick, @right])
|
16
|
+
@ts.take([:chopstick, @left])
|
17
|
+
do_it(:eat)
|
18
|
+
@ts.write([:chopstick, @right])
|
19
|
+
@ts.write([:chopstick, @left])
|
20
|
+
@ts.write([:room_ticket])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def running?
|
25
|
+
@ts.read_all([:done]).size == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def do_it(symbol)
|
29
|
+
sleep(rand)
|
30
|
+
@ts.write(p [symbol, @right])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def main
|
35
|
+
place = Rinda::TupleSpace.new
|
36
|
+
DRb.start_service
|
37
|
+
|
38
|
+
num = 10
|
39
|
+
num.times do |n|
|
40
|
+
place.write([:chopstick, n])
|
41
|
+
Rinda::rinda_eval(place) do |ts|
|
42
|
+
phil = Phil.new(ts, n, num)
|
43
|
+
phil.run
|
44
|
+
[:phil, n]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
(num - 1).times do |n|
|
49
|
+
place.write([:room_ticket])
|
50
|
+
end
|
51
|
+
|
52
|
+
sleep(10)
|
53
|
+
|
54
|
+
place.write([:done])
|
55
|
+
|
56
|
+
num.times do |n|
|
57
|
+
place.take([:phil, n])
|
58
|
+
p [n, place.read_all([:think, n]).size]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
main
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'drb/drb'
|
3
|
+
require 'rinda/rinda'
|
4
|
+
|
5
|
+
class Agent
|
6
|
+
def initialize(name, url, desc)
|
7
|
+
@tuple = [name, url, desc]
|
8
|
+
@inbox = Queue.new
|
9
|
+
@renewer = Rinda::SimpleRenewer.new(15)
|
10
|
+
end
|
11
|
+
|
12
|
+
def pop
|
13
|
+
@inbox.pop
|
14
|
+
end
|
15
|
+
|
16
|
+
def hello(name, url, desc)
|
17
|
+
@inbox.push([name, url, desc])
|
18
|
+
return @tuple
|
19
|
+
end
|
20
|
+
|
21
|
+
def meet(who)
|
22
|
+
tuple = who.hello(*@tuple)
|
23
|
+
@inbox.push(tuple)
|
24
|
+
end
|
25
|
+
|
26
|
+
def enter(place)
|
27
|
+
place.write([:agent, @tuple[0], self], @renewer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def broadcast(place)
|
31
|
+
place.read_all([:agent, String, DRbObject]).each do |k, name, ro|
|
32
|
+
next if name == @tuple[0]
|
33
|
+
ro.meet(self) rescue nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
DRb.start_service
|
39
|
+
place = DRbObject.new_with_uri(ARGV.shift)
|
40
|
+
|
41
|
+
name = ARGV.shift || 'your_nick'
|
42
|
+
url = ARGV.shift || 'http://www.druby.org'
|
43
|
+
desc = ARGV.shift || 'no comment is good comment'
|
44
|
+
|
45
|
+
agent = Agent.new(name, url, desc)
|
46
|
+
agent.enter(place)
|
47
|
+
agent.broadcast(place)
|
48
|
+
|
49
|
+
while true
|
50
|
+
p agent.pop
|
51
|
+
end
|
52
|
+
|
53
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/njet'
|
3
|
+
|
4
|
+
class CardHolder
|
5
|
+
def initialize
|
6
|
+
@ts = Rinda::TupleSpace.new
|
7
|
+
@ts.write(['m_seki', 'http://d.hatena.ne.jp/m_seki', 'Hello, Again'])
|
8
|
+
end
|
9
|
+
|
10
|
+
def exchange(name, url, desc)
|
11
|
+
@ts.write([name, url, desc])
|
12
|
+
@ts.take([Rinda::Njet.new(name), nil, nil])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
DRb.start_service('druby://:56789', CardHolder.new)
|
17
|
+
puts DRb.uri
|
18
|
+
DRb.thread.join
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/inspect'
|
3
|
+
require 'rinda/njet'
|
4
|
+
|
5
|
+
ts = Rinda::TupleSpace.new(15)
|
6
|
+
DRb.start_service('druby://:56788', ts)
|
7
|
+
|
8
|
+
while true
|
9
|
+
puts
|
10
|
+
puts DRb.uri
|
11
|
+
ts.report.tuple.each do |t|
|
12
|
+
p [t[0], t[1], t[2].__drburi]
|
13
|
+
end
|
14
|
+
sleep 10
|
15
|
+
end
|
16
|
+
|
17
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class ActorsOffice
|
4
|
+
def initialize(actor)
|
5
|
+
@queue = Queue.new
|
6
|
+
@thread = Thread.new(actor) do
|
7
|
+
catch(actor) do
|
8
|
+
while true
|
9
|
+
msg, arg, blk = @queue.pop
|
10
|
+
actor.__send__(msg, *arg, &blk)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def __thread__; @thread; end
|
16
|
+
|
17
|
+
def method_missing(m, *a, &b)
|
18
|
+
@queue.push([m, a, b])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class BarberShop
|
23
|
+
def initialize
|
24
|
+
@chairs = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def barber=(barber)
|
28
|
+
@barber = barber
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_customer(name)
|
32
|
+
puts "<< arriving #{name}"
|
33
|
+
if (@chairs.size >= 3)
|
34
|
+
puts "** sorry, no room for #{name} **"
|
35
|
+
else
|
36
|
+
@chairs << name
|
37
|
+
@barber.wake_up
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def customer_leaves
|
42
|
+
barber_check
|
43
|
+
end
|
44
|
+
|
45
|
+
def barber_check
|
46
|
+
@barber.customer(@chairs.shift) unless @chairs.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
def quit
|
50
|
+
puts 'quit'
|
51
|
+
throw(self)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Barber
|
56
|
+
def initialize(shop)
|
57
|
+
@shop = shop
|
58
|
+
end
|
59
|
+
|
60
|
+
def cut(name)
|
61
|
+
puts "cutting #{name}."
|
62
|
+
sleep(rand(5))
|
63
|
+
puts "finishing cutting #{name}.>>"
|
64
|
+
@shop.customer_leaves
|
65
|
+
end
|
66
|
+
|
67
|
+
def wake_up
|
68
|
+
@shop.barber_check
|
69
|
+
end
|
70
|
+
|
71
|
+
def customer(name)
|
72
|
+
cut(name)
|
73
|
+
@shop.barber_check
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
shop = ActorsOffice.new(BarberShop.new)
|
78
|
+
shop.barber = ActorsOffice.new(Barber.new(shop))
|
79
|
+
|
80
|
+
%w(jack john henry tom bob m_seki makoto).each do |name|
|
81
|
+
shop.new_customer(name)
|
82
|
+
sleep(1)
|
83
|
+
end
|
84
|
+
|
85
|
+
gets
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/eval'
|
3
|
+
|
4
|
+
class BarberShop
|
5
|
+
def initialize(ts)
|
6
|
+
@ts = ts
|
7
|
+
@waiting = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
@ts.write([:shop, @waiting])
|
12
|
+
while true
|
13
|
+
addr, msg, arg = @ts.take([:shop, nil, nil])
|
14
|
+
case msg
|
15
|
+
when :new_customer
|
16
|
+
name = arg
|
17
|
+
puts "<< arriving #{name}"
|
18
|
+
if (@waiting.size >= 3)
|
19
|
+
puts "** sorry, no room for #{name} **"
|
20
|
+
else
|
21
|
+
@waiting << name
|
22
|
+
@ts.write([:barber, :wake_up, nil])
|
23
|
+
end
|
24
|
+
when :customer_leaves
|
25
|
+
puts "leaving #{@waiting.shift}>>"
|
26
|
+
@ts.write([:barber, :customer, @waiting[0]])
|
27
|
+
when :barber_check
|
28
|
+
@ts.write([:barber, :customer, @waiting[0]])
|
29
|
+
when :quit
|
30
|
+
return
|
31
|
+
end
|
32
|
+
@ts.take([:shop, nil])
|
33
|
+
@ts.write([:shop, @waiting])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Barber
|
39
|
+
def initialize(ts)
|
40
|
+
@ts = ts
|
41
|
+
end
|
42
|
+
|
43
|
+
def start
|
44
|
+
@ts.write([:barber, 'sleep'])
|
45
|
+
while true
|
46
|
+
addr, msg, arg = @ts.take([:barber, nil, nil])
|
47
|
+
case msg
|
48
|
+
when :wake_up
|
49
|
+
@ts.take([:barber, nil])
|
50
|
+
@ts.write([:barber, 'wake up'])
|
51
|
+
@ts.write([:shop, :barber_check, nil])
|
52
|
+
when :customer
|
53
|
+
if arg
|
54
|
+
cut(arg)
|
55
|
+
@ts.write([:shop, :customer_leaves, nil])
|
56
|
+
else
|
57
|
+
puts " sleeping"
|
58
|
+
@ts.take([:barber, nil])
|
59
|
+
@ts.write([:barber, 'sleep'])
|
60
|
+
end
|
61
|
+
when :quit
|
62
|
+
return
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def cut(name)
|
68
|
+
puts " cutting #{name}."
|
69
|
+
rand(5).times do
|
70
|
+
puts " cutting..."
|
71
|
+
sleep(1)
|
72
|
+
end
|
73
|
+
puts " finishing cutting #{name}."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
place = Rinda::TupleSpace.new
|
78
|
+
DRb.start_service('druby://localhost:0')
|
79
|
+
|
80
|
+
Rinda::rinda_eval(place) do |ts|
|
81
|
+
BarberShop.new(ts).start
|
82
|
+
[:shop]
|
83
|
+
end
|
84
|
+
|
85
|
+
Rinda::rinda_eval(place) do |ts|
|
86
|
+
Barber.new(ts).start
|
87
|
+
[:barber]
|
88
|
+
end
|
89
|
+
|
90
|
+
%w(jack john henry tom bob m_seki makoto).each do |name|
|
91
|
+
place.write([:shop, :new_customer, name])
|
92
|
+
sleep(2)
|
93
|
+
end
|
94
|
+
|
95
|
+
p place.read([:shop, []])
|
96
|
+
p place.read([:barber, 'sleep'])
|
97
|
+
|
98
|
+
place.write([:shop, :quit, nil])
|
99
|
+
place.write([:barber, :quit, nil])
|
100
|
+
|
101
|
+
p place.take([:shop])
|
102
|
+
p place.take([:barber])
|
data/install.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
dest = RbConfig::CONFIG['sitelibdir'] + '/rinda'
|
5
|
+
src = ['lib/rinda/eval.rb']
|
6
|
+
|
7
|
+
FileUtils.mkdir_p(dest, {:verbose => true})
|
8
|
+
src.each do |s|
|
9
|
+
FileUtils.install(s, dest, {:verbose => true, :mode => 0644})
|
10
|
+
end
|
11
|
+
|
data/lib/rinda/eval.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'rinda/rinda'
|
3
|
+
|
4
|
+
module Rinda
|
5
|
+
module_function
|
6
|
+
def rinda_eval(ts)
|
7
|
+
Thread.pass # FIXME
|
8
|
+
ts = DRbObject.new(ts) unless DRbObject === ts
|
9
|
+
pid = fork do
|
10
|
+
Thread.current['DRb'] = nil
|
11
|
+
DRb.stop_service
|
12
|
+
DRb.start_service
|
13
|
+
place = TupleSpaceProxy.new(ts)
|
14
|
+
tuple = yield(place)
|
15
|
+
place.write(tuple) rescue nil
|
16
|
+
end
|
17
|
+
Process.detach(pid)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
require 'rinda/tuplespace'
|
3
|
+
|
4
|
+
module Rinda
|
5
|
+
class TupleSpace
|
6
|
+
attr_reader :bag, :read_waiter, :take_waiter, :notify_waiter
|
7
|
+
|
8
|
+
def report
|
9
|
+
synchronize do
|
10
|
+
TupleSpaceReport.new(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class TupleBag
|
16
|
+
include Enumerable
|
17
|
+
def each(&blk)
|
18
|
+
each_entry(&blk)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class WaitTemplateEntry
|
23
|
+
attr_reader :cond
|
24
|
+
end
|
25
|
+
|
26
|
+
class TupleSpaceReport
|
27
|
+
def initialize(ts)
|
28
|
+
@tuple = ts.bag.collect {|t| t.value}
|
29
|
+
@reader = ts.read_waiter.collect {|t| [t.value, waiters(t)]}
|
30
|
+
@taker = ts.take_waiter.collect {|t| [t.value, waiters(t)]}
|
31
|
+
end
|
32
|
+
attr_reader :tuple, :reader, :taker
|
33
|
+
|
34
|
+
def waiters(tuple)
|
35
|
+
tuple.cond.instance_variable_get(:@waiters).collect do |th|
|
36
|
+
[th, (th[:DRb]['client'].stream.peeraddr rescue nil)]
|
37
|
+
end[0]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if __FILE__ == $0
|
43
|
+
require 'pp'
|
44
|
+
|
45
|
+
def report(ts)
|
46
|
+
pp ts.report
|
47
|
+
end
|
48
|
+
|
49
|
+
ts = Rinda::TupleSpace.new
|
50
|
+
DRb.start_service(nil, ts)
|
51
|
+
p DRb.uri
|
52
|
+
|
53
|
+
t1 = Thread.new { p ts.take([:hello, :world]) }
|
54
|
+
t2 = Thread.new { p ts.read([:hello, :nil]) }
|
55
|
+
sleep 1
|
56
|
+
report(ts)
|
57
|
+
ts.write([:hello, :world])
|
58
|
+
report(ts)
|
59
|
+
sleep 1
|
60
|
+
report(ts)
|
61
|
+
gets
|
62
|
+
report(ts)
|
63
|
+
end
|
data/lib/rinda/njet.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/tuplestore'
|
3
|
+
require 'singleton'
|
4
|
+
require 'weakref'
|
5
|
+
|
6
|
+
module Rinda
|
7
|
+
class PTupleEntryCache
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@cache = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](serial)
|
15
|
+
ref = @cache[serial]
|
16
|
+
return nil unless ref
|
17
|
+
it = ref.__getobj__
|
18
|
+
@cache.delete(serial) unless it
|
19
|
+
return it
|
20
|
+
end
|
21
|
+
|
22
|
+
def register(tuple)
|
23
|
+
@cache[tuple.serial] = WeakRef.new(tuple)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class PTupleEntry < TupleEntry
|
28
|
+
def self.new_with_desc(key, desc)
|
29
|
+
store = Rinda.tuple_store
|
30
|
+
it = allocate
|
31
|
+
it.instance_variable_set('@store', store)
|
32
|
+
it.instance_variable_set('@serial', key)
|
33
|
+
it.instance_variable_set('@tuple', desc[:tuple])
|
34
|
+
it.instance_variable_set('@expires', desc[:expires])
|
35
|
+
it.instance_variable_set('@cancel', desc[:cancel])
|
36
|
+
it.instance_variable_set('@renewer', desc[:renewer])
|
37
|
+
PTupleEntryCache.instance.register(it)
|
38
|
+
it
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_store
|
42
|
+
{ :tuple => @tuple,
|
43
|
+
:expires => @expires,
|
44
|
+
:cancel => @cancel,
|
45
|
+
:renewer => @renewer }
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(*arg)
|
49
|
+
super(*arg)
|
50
|
+
@store = Rinda.tuple_store
|
51
|
+
@serial = @store.add(to_store)
|
52
|
+
PTupleEntryCache.instance.register(self)
|
53
|
+
end
|
54
|
+
attr_reader :serial
|
55
|
+
|
56
|
+
def cancel
|
57
|
+
super
|
58
|
+
@store.set_cancel(@serial)
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete
|
62
|
+
@store.delete(@serial)
|
63
|
+
end
|
64
|
+
|
65
|
+
def expires=(it)
|
66
|
+
super(it)
|
67
|
+
@store.set_expires(@serial, it)
|
68
|
+
end
|
69
|
+
|
70
|
+
def renew(it)
|
71
|
+
old = @expires
|
72
|
+
super(it)
|
73
|
+
return unless @store
|
74
|
+
@store.set_expires(@serail, @expires) unless old == @expires
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class PTupleSpace < TupleSpace
|
79
|
+
def initialize(*arg)
|
80
|
+
super(*arg)
|
81
|
+
@bag = PTupleBag.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_entry(tuple, sec)
|
85
|
+
PTupleEntry.new(tuple, sec)
|
86
|
+
end
|
87
|
+
|
88
|
+
def restore
|
89
|
+
@bag.restore
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class PTupleBag < TupleBag
|
94
|
+
def initialize(*var)
|
95
|
+
super(*var)
|
96
|
+
end
|
97
|
+
attr_reader :store
|
98
|
+
|
99
|
+
def delete(tuple)
|
100
|
+
it = super(tuple)
|
101
|
+
return nil unless it
|
102
|
+
tuple.delete
|
103
|
+
it
|
104
|
+
end
|
105
|
+
|
106
|
+
def restore
|
107
|
+
Rinda.tuple_store.each do |k, v|
|
108
|
+
tuple = PTupleEntry.new_with_desc(k, v)
|
109
|
+
push(tuple)
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class TupleStoreIdConv < DRb::DRbIdConv
|
116
|
+
def tuple_store?(ref)
|
117
|
+
return false unless ref.kind_of?(Array)
|
118
|
+
return false unless ref[0] == :TupleStoreId
|
119
|
+
return true
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_obj(ref)
|
123
|
+
if tuple_store?(ref)
|
124
|
+
PTupleEntryCache.instance[ref[1]]
|
125
|
+
else
|
126
|
+
super(ref)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_id(obj)
|
131
|
+
if obj.class == PTupleEntry
|
132
|
+
[:TupleStoreId, obj.serial]
|
133
|
+
else
|
134
|
+
super(obj)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def setup_tuple_store(store)
|
140
|
+
raise "tuple_store was assinged" if @tuple_store
|
141
|
+
@tuple_store = store
|
142
|
+
end
|
143
|
+
module_function :setup_tuple_store
|
144
|
+
|
145
|
+
def tuple_store
|
146
|
+
@tuple_store
|
147
|
+
end
|
148
|
+
module_function :tuple_store
|
149
|
+
end
|
150
|
+
|
151
|
+
if __FILE__ == $0
|
152
|
+
# store = DRbObject.new_with_uri('druby://localhost:12345')
|
153
|
+
store = Rinda::TupleStoreLog.new('ts_log')
|
154
|
+
Rinda::setup_tuple_store(store)
|
155
|
+
|
156
|
+
DRb.install_id_conv(Rinda::TupleStoreIdConv.new)
|
157
|
+
ts = Rinda::PTupleSpace.new
|
158
|
+
DRb.start_service('druby://localhost:23456', ts)
|
159
|
+
ts.restore
|
160
|
+
# sleep
|
161
|
+
|
162
|
+
ts.write(['Hello', 'World'])
|
163
|
+
p ts.read_all(['Hello', nil])
|
164
|
+
p ts.take(['Hello', nil])
|
165
|
+
|
166
|
+
x = ts.write(['Hello', 'cancel'], 2)
|
167
|
+
p ts.read_all(['Hello', nil])
|
168
|
+
ref = DRbObject.new(x)
|
169
|
+
ref.cancel
|
170
|
+
p ts.read_all(['Hello', nil])
|
171
|
+
x = ts.write(['Hello', 'World'])
|
172
|
+
|
173
|
+
p DRbObject.new(x)
|
174
|
+
|
175
|
+
File.open('test.dat', 'wb') do |x|
|
176
|
+
Marshal.dump(store, x)
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'rinda/tuplestore'
|
2
|
+
require 'tokyocabinet'
|
3
|
+
|
4
|
+
module Rinda
|
5
|
+
class TokyoStore < TupleStore
|
6
|
+
class BDBError < RuntimeError
|
7
|
+
def initialize(bdb)
|
8
|
+
super(bdb.errmsg(bdb.ecode))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class BDB < TokyoCabinet::BDB
|
13
|
+
def exception
|
14
|
+
BDBError.new(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cursor
|
18
|
+
TokyoCabinet::BDBCUR.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.call_or_die(*ary)
|
22
|
+
file, lineno = __FILE__, __LINE__
|
23
|
+
if /^(.+?):(\d+)(?::in `(.*)')?/ =~ caller(1)[0]
|
24
|
+
file = $1
|
25
|
+
lineno = $2.to_i
|
26
|
+
end
|
27
|
+
ary.each do |sym|
|
28
|
+
module_eval("def #{sym}(*arg); super || raise(self); end",
|
29
|
+
file, lineno)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
call_or_die :open, :close
|
34
|
+
call_or_die :tranbegin, :tranabort, :trancommit
|
35
|
+
call_or_die :vanish
|
36
|
+
end
|
37
|
+
|
38
|
+
include MonitorMixin
|
39
|
+
def initialize(name)
|
40
|
+
super()
|
41
|
+
@name = name
|
42
|
+
@bdb = BDB.new
|
43
|
+
writer {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def transaction(mode)
|
47
|
+
synchronize do
|
48
|
+
begin
|
49
|
+
@bdb.open(@name, mode)
|
50
|
+
return yield
|
51
|
+
ensure
|
52
|
+
@bdb.close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def reader(&block)
|
58
|
+
transaction(BDB::OREADER, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def writer(&block)
|
62
|
+
transaction(BDB::OWRITER | BDB::OCREAT, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def load(val)
|
66
|
+
val ? Marshal.load(val) : nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def each
|
70
|
+
reader do
|
71
|
+
cursor = @bdb.cursor
|
72
|
+
cursor.jump('t.')
|
73
|
+
while cursor.key
|
74
|
+
break unless /^t\.(\w+)/ =~ cursor.key
|
75
|
+
key = $1
|
76
|
+
desc = Hash.new
|
77
|
+
desc[:tuple] = load(cursor.val)
|
78
|
+
cursor.next
|
79
|
+
next unless desc[:tuple]
|
80
|
+
next if @bdb["c.#{key}"]
|
81
|
+
desc[:renewer] = load(@bdb["r.#{key}"])
|
82
|
+
desc[:expires] = load(@bdb["e.#{key}"])
|
83
|
+
yield(key, desc)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def add(desc)
|
89
|
+
writer do
|
90
|
+
ser = (@bdb['ser'] || 0).to_i + 1
|
91
|
+
@bdb['ser'] = ser
|
92
|
+
key = ser.to_s(36)
|
93
|
+
@bdb["t.#{key}"] = Marshal.dump(desc[:tuple])
|
94
|
+
@bdb["r.#{key}"] = Marshal.dump(desc[:renewer])
|
95
|
+
@bdb["e.#{key}"] = Marshal.dump(desc[:expires])
|
96
|
+
@bdb["c.#{key}"] = 't' if desc[:cancel]
|
97
|
+
return key
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def delete(key)
|
102
|
+
writer do
|
103
|
+
@bdb.out("t.#{key}")
|
104
|
+
@bdb.out("r.#{key}")
|
105
|
+
@bdb.out("e.#{key}")
|
106
|
+
@bdb.out("c.#{key}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_cancel(key)
|
111
|
+
writer do
|
112
|
+
@bdb["c.#{key}"] = 't'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_expires(key, value)
|
117
|
+
writer do
|
118
|
+
@bdb["e.#{key}"] = Marshal.dump(value)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'monitor'
|
3
|
+
|
4
|
+
module Rinda
|
5
|
+
class TupleStore
|
6
|
+
def each; end
|
7
|
+
def add(desc); end
|
8
|
+
def delete(key); end
|
9
|
+
def set_cancel(key); end
|
10
|
+
def set_expires(key, value); end
|
11
|
+
end
|
12
|
+
|
13
|
+
class TupleStoreSimple < TupleStore
|
14
|
+
include MonitorMixin
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
super()
|
18
|
+
@ser = 0
|
19
|
+
@hash = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
@hash.each do |k, v|
|
24
|
+
next unless v
|
25
|
+
next if v[:cancel]
|
26
|
+
yield(k, v) if v
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(desc)
|
31
|
+
synchronize do
|
32
|
+
@ser += 1
|
33
|
+
@hash[@ser] = desc
|
34
|
+
return @ser
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(key)
|
39
|
+
@hash.delete(key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_cancel(key)
|
43
|
+
return unless @hash[key]
|
44
|
+
@hash[key][:cancel] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_expires(key, value)
|
48
|
+
return unless @hash[key]
|
49
|
+
@hash[key][:expires] = value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class TupleStoreLog < TupleStore
|
54
|
+
include MonitorMixin
|
55
|
+
|
56
|
+
def initialize(log_dir)
|
57
|
+
super()
|
58
|
+
@log_dir = log_dir
|
59
|
+
Dir::mkdir(log_dir) rescue nil
|
60
|
+
@ser = 0
|
61
|
+
@hash = {}
|
62
|
+
restore
|
63
|
+
end
|
64
|
+
|
65
|
+
def each
|
66
|
+
@hash.each do |k, v|
|
67
|
+
next unless v
|
68
|
+
next if v[:cancel]
|
69
|
+
yield(k, v) if v
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add(desc)
|
74
|
+
synchronize do
|
75
|
+
@ser += 1
|
76
|
+
@hash[@ser] = desc
|
77
|
+
File.open(File.join(@log_dir, @ser.to_s), 'wb') do |f|
|
78
|
+
Marshal.dump(desc, f)
|
79
|
+
end
|
80
|
+
return @ser
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete(key)
|
85
|
+
synchronize do
|
86
|
+
@hash.delete(key)
|
87
|
+
fname = File.join(@log_dir, key.to_s)
|
88
|
+
File.rename(fname, fname + '_del')
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_attr(key, name, value)
|
94
|
+
synchronize do
|
95
|
+
return unless @hash[key]
|
96
|
+
@hash[key][name] = value
|
97
|
+
fname = File.join(@log_dir, key.to_s)
|
98
|
+
File.open(fname + 'tmp', 'wb') do |f|
|
99
|
+
Marshal.dump(@hash[key], f)
|
100
|
+
end
|
101
|
+
File.rename(fname + 'tmp', fname)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_cancel(key)
|
106
|
+
set_attr(key, :cancel, true)
|
107
|
+
end
|
108
|
+
|
109
|
+
def set_expires(key, value)
|
110
|
+
set_attr(key, :expires, value)
|
111
|
+
end
|
112
|
+
|
113
|
+
def restore
|
114
|
+
Dir.foreach(@log_dir) do |file|
|
115
|
+
num = desc = nil
|
116
|
+
if /([0-9]+)(_del)?/ =~ file
|
117
|
+
num = $1.to_i
|
118
|
+
@ser = num if @ser < num
|
119
|
+
next if $2
|
120
|
+
begin
|
121
|
+
File.open(File.join(@log_dir, file), 'rb') do |f|
|
122
|
+
desc = Marshal.load(f)
|
123
|
+
@hash[num] = desc
|
124
|
+
end
|
125
|
+
rescue
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
if __FILE__ == $0
|
134
|
+
uri = ARGV.shift || 'druby://localhost:12345'
|
135
|
+
store = Rinda::TupleStoreLog.new('ts_log')
|
136
|
+
DRb.start_service(uri, store)
|
137
|
+
DRb.thread.join
|
138
|
+
end
|
139
|
+
|
data/more_rinda.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rinda/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "more_rinda"
|
7
|
+
s.version = MoreRinda::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Masatoshi Seki"]
|
10
|
+
s.homepage = "https://github.com/seki/MoreRinda"
|
11
|
+
s.summary = %q{Various extensions for Rinda::TupleSpace lovers.}
|
12
|
+
s.description = ""
|
13
|
+
|
14
|
+
s.rubyforge_project = "drip"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rinda/ptuplespace'
|
2
|
+
|
3
|
+
# store = DRbObject.new_with_uri('druby://localhost:12345')
|
4
|
+
store = Rinda::TupleStoreLog.new('ts_log')
|
5
|
+
Rinda::setup_tuple_store(store)
|
6
|
+
|
7
|
+
DRb.install_id_conv(Rinda::TupleStoreIdConv.new)
|
8
|
+
ts = Rinda::PTupleSpace.new
|
9
|
+
DRb.start_service('druby://localhost:23456', ts)
|
10
|
+
ts.restore
|
11
|
+
# sleep
|
12
|
+
|
13
|
+
ts.write(['Hello', 'World'])
|
14
|
+
p ts.read_all(['Hello', nil])
|
15
|
+
p ts.take(['Hello', nil])
|
16
|
+
|
17
|
+
x = ts.write(['Hello', 'cancel'], 2)
|
18
|
+
p ts.read_all(['Hello', nil])
|
19
|
+
ref = DRbObject.new(x)
|
20
|
+
ref.cancel
|
21
|
+
p ts.read_all(['Hello', nil])
|
22
|
+
x = ts.write(['Hello', 'World'])
|
23
|
+
|
24
|
+
p DRbObject.new(x)
|
25
|
+
|
data/test/test_njet.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rinda/tuplespace'
|
2
|
+
require 'rinda/njet'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
class NjetTest < Test::Unit::TestCase
|
6
|
+
include Rinda
|
7
|
+
def test_match
|
8
|
+
ts = Rinda::TupleSpace.new
|
9
|
+
assert_equal([], ts.read_all([Njet.new(10)]))
|
10
|
+
ts.write([10])
|
11
|
+
assert_equal([], ts.read_all([Njet.new(10)]))
|
12
|
+
ts.write([11])
|
13
|
+
assert_equal([[11]], ts.read_all([Njet.new(10)]))
|
14
|
+
ts.write([12])
|
15
|
+
assert_equal([[11], [12]], ts.read_all([Njet.new(10)]).sort)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_wait_for_change
|
19
|
+
ts = Rinda::TupleSpace.new
|
20
|
+
|
21
|
+
ts.write([:state, 1])
|
22
|
+
_, last_state = ts.read([:state, nil])
|
23
|
+
|
24
|
+
assert_raise(Rinda::RequestExpiredError) do
|
25
|
+
ts.read([:state, Njet.new(last_state)], 0)
|
26
|
+
end
|
27
|
+
|
28
|
+
tuple = ts.take([:state, nil])
|
29
|
+
tuple[1] += 1
|
30
|
+
ts.write(tuple)
|
31
|
+
|
32
|
+
assert_equal([:state, 2], ts.read([:state, Njet.new(last_state)], 0))
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: more_rinda
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Masatoshi Seki
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-09 00:00:00 Z
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email:
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- Gemfile
|
27
|
+
- README.md
|
28
|
+
- Rakefile
|
29
|
+
- example/eval0.rb
|
30
|
+
- example/nqueen.rb
|
31
|
+
- example/phil.rb
|
32
|
+
- example/rk09/agent.rb
|
33
|
+
- example/rk09/card.rb
|
34
|
+
- example/rk09/card_holder.rb
|
35
|
+
- example/rk09/place.rb
|
36
|
+
- example/sleeping_barber_actor.rb
|
37
|
+
- example/sleeping_barber_rinda.rb
|
38
|
+
- install.rb
|
39
|
+
- lib/rinda/eval.rb
|
40
|
+
- lib/rinda/inspect.rb
|
41
|
+
- lib/rinda/njet.rb
|
42
|
+
- lib/rinda/ptuplespace.rb
|
43
|
+
- lib/rinda/tokyotuplestore.rb
|
44
|
+
- lib/rinda/tuplestore.rb
|
45
|
+
- lib/rinda/version.rb
|
46
|
+
- more_rinda.gemspec
|
47
|
+
- test/ptuplespace_sample.rb
|
48
|
+
- test/test_njet.rb
|
49
|
+
homepage: https://github.com/seki/MoreRinda
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project: drip
|
72
|
+
rubygems_version: 1.8.6.1
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Various extensions for Rinda::TupleSpace lovers.
|
76
|
+
test_files:
|
77
|
+
- test/ptuplespace_sample.rb
|
78
|
+
- test/test_njet.rb
|