1 | require 'drb/drb'
|
---|
2 | require 'thread'
|
---|
3 |
|
---|
4 | ##
|
---|
5 | # A module to implement the Linda distributed computing paradigm in Ruby.
|
---|
6 | #
|
---|
7 | # Rinda is part of DRb (dRuby).
|
---|
8 | #
|
---|
9 | # == Example(s)
|
---|
10 | #
|
---|
11 | # See the sample/drb/ directory in the Ruby distribution, from 1.8.2 onwards.
|
---|
12 | #
|
---|
13 | #--
|
---|
14 | # TODO
|
---|
15 | # == Introduction to Linda/rinda?
|
---|
16 | #
|
---|
17 | # == Why is this library separate from DRb?
|
---|
18 |
|
---|
19 | module Rinda
|
---|
20 |
|
---|
21 | ##
|
---|
22 | # Rinda error base class
|
---|
23 |
|
---|
24 | class RindaError < RuntimeError; end
|
---|
25 |
|
---|
26 | ##
|
---|
27 | # Raised when a hash-based tuple has an invalid key.
|
---|
28 |
|
---|
29 | class InvalidHashTupleKey < RindaError; end
|
---|
30 |
|
---|
31 | ##
|
---|
32 | # Raised when trying to use a canceled tuple.
|
---|
33 |
|
---|
34 | class RequestCanceledError < ThreadError; end
|
---|
35 |
|
---|
36 | ##
|
---|
37 | # Raised when trying to use an expired tuple.
|
---|
38 |
|
---|
39 | class RequestExpiredError < ThreadError; end
|
---|
40 |
|
---|
41 | ##
|
---|
42 | # A tuple is the elementary object in Rinda programming.
|
---|
43 | # Tuples may be matched against templates if the tuple and
|
---|
44 | # the template are the same size.
|
---|
45 |
|
---|
46 | class Tuple
|
---|
47 |
|
---|
48 | ##
|
---|
49 | # Creates a new Tuple from +ary_or_hash+ which must be an Array or Hash.
|
---|
50 |
|
---|
51 | def initialize(ary_or_hash)
|
---|
52 | if hash?(ary_or_hash)
|
---|
53 | init_with_hash(ary_or_hash)
|
---|
54 | else
|
---|
55 | init_with_ary(ary_or_hash)
|
---|
56 | end
|
---|
57 | end
|
---|
58 |
|
---|
59 | ##
|
---|
60 | # The number of elements in the tuple.
|
---|
61 |
|
---|
62 | def size
|
---|
63 | @tuple.size
|
---|
64 | end
|
---|
65 |
|
---|
66 | ##
|
---|
67 | # Accessor method for elements of the tuple.
|
---|
68 |
|
---|
69 | def [](k)
|
---|
70 | @tuple[k]
|
---|
71 | end
|
---|
72 |
|
---|
73 | ##
|
---|
74 | # Fetches item +k+ from the tuple.
|
---|
75 |
|
---|
76 | def fetch(k)
|
---|
77 | @tuple.fetch(k)
|
---|
78 | end
|
---|
79 |
|
---|
80 | ##
|
---|
81 | # Iterate through the tuple, yielding the index or key, and the
|
---|
82 | # value, thus ensuring arrays are iterated similarly to hashes.
|
---|
83 |
|
---|
84 | def each # FIXME
|
---|
85 | if Hash === @tuple
|
---|
86 | @tuple.each { |k, v| yield(k, v) }
|
---|
87 | else
|
---|
88 | @tuple.each_with_index { |v, k| yield(k, v) }
|
---|
89 | end
|
---|
90 | end
|
---|
91 |
|
---|
92 | ##
|
---|
93 | # Return the tuple itself
|
---|
94 | def value
|
---|
95 | @tuple
|
---|
96 | end
|
---|
97 |
|
---|
98 | private
|
---|
99 |
|
---|
100 | def hash?(ary_or_hash)
|
---|
101 | ary_or_hash.respond_to?(:keys)
|
---|
102 | end
|
---|
103 |
|
---|
104 | ##
|
---|
105 | # Munges +ary+ into a valid Tuple.
|
---|
106 |
|
---|
107 | def init_with_ary(ary)
|
---|
108 | @tuple = Array.new(ary.size)
|
---|
109 | @tuple.size.times do |i|
|
---|
110 | @tuple[i] = ary[i]
|
---|
111 | end
|
---|
112 | end
|
---|
113 |
|
---|
114 | ##
|
---|
115 | # Ensures +hash+ is a valid Tuple.
|
---|
116 |
|
---|
117 | def init_with_hash(hash)
|
---|
118 | @tuple = Hash.new
|
---|
119 | hash.each do |k, v|
|
---|
120 | raise InvalidHashTupleKey unless String === k
|
---|
121 | @tuple[k] = v
|
---|
122 | end
|
---|
123 | end
|
---|
124 |
|
---|
125 | end
|
---|
126 |
|
---|
127 | ##
|
---|
128 | # Templates are used to match tuples in Rinda.
|
---|
129 |
|
---|
130 | class Template < Tuple
|
---|
131 |
|
---|
132 | ##
|
---|
133 | # Matches this template against +tuple+. The +tuple+ must be the same
|
---|
134 | # size as the template. An element with a +nil+ value in a template acts
|
---|
135 | # as a wildcard, matching any value in the corresponding position in the
|
---|
136 | # tuple. Elements of the template match the +tuple+ if the are #== or
|
---|
137 | # #===.
|
---|
138 | #
|
---|
139 | # Template.new([:foo, 5]).match Tuple.new([:foo, 5]) # => true
|
---|
140 | # Template.new([:foo, nil]).match Tuple.new([:foo, 5]) # => true
|
---|
141 | # Template.new([String]).match Tuple.new(['hello']) # => true
|
---|
142 | #
|
---|
143 | # Template.new([:foo]).match Tuple.new([:foo, 5]) # => false
|
---|
144 | # Template.new([:foo, 6]).match Tuple.new([:foo, 5]) # => false
|
---|
145 | # Template.new([:foo, nil]).match Tuple.new([:foo]) # => false
|
---|
146 | # Template.new([:foo, 6]).match Tuple.new([:foo]) # => false
|
---|
147 |
|
---|
148 | def match(tuple)
|
---|
149 | return false unless tuple.respond_to?(:size)
|
---|
150 | return false unless tuple.respond_to?(:fetch)
|
---|
151 | return false unless self.size == tuple.size
|
---|
152 | each do |k, v|
|
---|
153 | begin
|
---|
154 | it = tuple.fetch(k)
|
---|
155 | rescue
|
---|
156 | return false
|
---|
157 | end
|
---|
158 | next if v.nil?
|
---|
159 | next if v == it
|
---|
160 | next if v === it
|
---|
161 | return false
|
---|
162 | end
|
---|
163 | return true
|
---|
164 | end
|
---|
165 |
|
---|
166 | ##
|
---|
167 | # Alias for #match.
|
---|
168 |
|
---|
169 | def ===(tuple)
|
---|
170 | match(tuple)
|
---|
171 | end
|
---|
172 |
|
---|
173 | end
|
---|
174 |
|
---|
175 | ##
|
---|
176 | # <i>Documentation?</i>
|
---|
177 |
|
---|
178 | class DRbObjectTemplate
|
---|
179 |
|
---|
180 | ##
|
---|
181 | # Creates a new DRbObjectTemplate that will match against +uri+ and +ref+.
|
---|
182 |
|
---|
183 | def initialize(uri=nil, ref=nil)
|
---|
184 | @drb_uri = uri
|
---|
185 | @drb_ref = ref
|
---|
186 | end
|
---|
187 |
|
---|
188 | ##
|
---|
189 | # This DRbObjectTemplate matches +ro+ if the remote object's drburi and
|
---|
190 | # drbref are the same. +nil+ is used as a wildcard.
|
---|
191 |
|
---|
192 | def ===(ro)
|
---|
193 | return true if super(ro)
|
---|
194 | unless @drb_uri.nil?
|
---|
195 | return false unless (@drb_uri === ro.__drburi rescue false)
|
---|
196 | end
|
---|
197 | unless @drb_ref.nil?
|
---|
198 | return false unless (@drb_ref === ro.__drbref rescue false)
|
---|
199 | end
|
---|
200 | true
|
---|
201 | end
|
---|
202 |
|
---|
203 | end
|
---|
204 |
|
---|
205 | ##
|
---|
206 | # TupleSpaceProxy allows a remote Tuplespace to appear as local.
|
---|
207 |
|
---|
208 | class TupleSpaceProxy
|
---|
209 |
|
---|
210 | ##
|
---|
211 | # Creates a new TupleSpaceProxy to wrap +ts+.
|
---|
212 |
|
---|
213 | def initialize(ts)
|
---|
214 | @ts = ts
|
---|
215 | end
|
---|
216 |
|
---|
217 | ##
|
---|
218 | # Adds +tuple+ to the proxied TupleSpace. See TupleSpace#write.
|
---|
219 |
|
---|
220 | def write(tuple, sec=nil)
|
---|
221 | @ts.write(tuple, sec)
|
---|
222 | end
|
---|
223 |
|
---|
224 | ##
|
---|
225 | # Takes +tuple+ from the proxied TupleSpace. See TupleSpace#take.
|
---|
226 |
|
---|
227 | def take(tuple, sec=nil, &block)
|
---|
228 | port = []
|
---|
229 | @ts.move(DRbObject.new(port), tuple, sec, &block)
|
---|
230 | port[0]
|
---|
231 | end
|
---|
232 |
|
---|
233 | ##
|
---|
234 | # Reads +tuple+ from the proxied TupleSpace. See TupleSpace#read.
|
---|
235 |
|
---|
236 | def read(tuple, sec=nil, &block)
|
---|
237 | @ts.read(tuple, sec, &block)
|
---|
238 | end
|
---|
239 |
|
---|
240 | ##
|
---|
241 | # Reads all tuples matching +tuple+ from the proxied TupleSpace. See
|
---|
242 | # TupleSpace#read_all.
|
---|
243 |
|
---|
244 | def read_all(tuple)
|
---|
245 | @ts.read_all(tuple)
|
---|
246 | end
|
---|
247 |
|
---|
248 | ##
|
---|
249 | # Registers for notifications of event +ev+ on the proxied TupleSpace.
|
---|
250 | # See TupleSpace#notify
|
---|
251 |
|
---|
252 | def notify(ev, tuple, sec=nil)
|
---|
253 | @ts.notify(ev, tuple, sec)
|
---|
254 | end
|
---|
255 |
|
---|
256 | end
|
---|
257 |
|
---|
258 | ##
|
---|
259 | # An SimpleRenewer allows a TupleSpace to check if a TupleEntry is still
|
---|
260 | # alive.
|
---|
261 |
|
---|
262 | class SimpleRenewer
|
---|
263 |
|
---|
264 | include DRbUndumped
|
---|
265 |
|
---|
266 | ##
|
---|
267 | # Creates a new SimpleRenewer that keeps an object alive for another +sec+
|
---|
268 | # seconds.
|
---|
269 |
|
---|
270 | def initialize(sec=180)
|
---|
271 | @sec = sec
|
---|
272 | end
|
---|
273 |
|
---|
274 | ##
|
---|
275 | # Called by the TupleSpace to check if the object is still alive.
|
---|
276 |
|
---|
277 | def renew
|
---|
278 | @sec
|
---|
279 | end
|
---|
280 | end
|
---|
281 |
|
---|
282 | end
|
---|
283 |
|
---|