more_rinda 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|