rubysl-rinda 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/rinda/rinda.rb +283 -0
- data/lib/rinda/ring.rb +271 -0
- data/lib/rinda/tuplespace.rb +589 -0
- data/lib/rubysl/rinda.rb +1 -0
- data/lib/rubysl/rinda/version.rb +5 -0
- data/rubysl-rinda.gemspec +23 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 569d3924674613e8ef815b980e434f1f9083948c
|
4
|
+
data.tar.gz: bd62bc505d9fe5770dd53c548898b07d3f3b7531
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a9e02962e23175532cd263856f609c326ba754321e2922e4f86ae364af4a478c9c85fdd47c4279b8f205893b225d2968f10a8ee16c2d66bb27243ba8ee1f072a
|
7
|
+
data.tar.gz: df2c144c6265a21fc8c64feefd49a08868229f91b4b15361efe9ec953c151517585210c51b14f14d73d7762c64e3c53e9d98b03f76a8029deb074e86d5609b69
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2013, Brian Shirai
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
3. Neither the name of the library nor the names of its contributors may be
|
13
|
+
used to endorse or promote products derived from this software without
|
14
|
+
specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
|
20
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
21
|
+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
22
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
23
|
+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
24
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
25
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Rubysl::Rinda
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'rubysl-rinda'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install rubysl-rinda
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/rinda/rinda.rb
ADDED
@@ -0,0 +1,283 @@
|
|
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
|
+
|
data/lib/rinda/ring.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
#
|
2
|
+
# Note: Rinda::Ring API is unstable.
|
3
|
+
#
|
4
|
+
require 'drb/drb'
|
5
|
+
require 'rinda/rinda'
|
6
|
+
require 'thread'
|
7
|
+
|
8
|
+
module Rinda
|
9
|
+
|
10
|
+
##
|
11
|
+
# The default port Ring discovery will use.
|
12
|
+
|
13
|
+
Ring_PORT = 7647
|
14
|
+
|
15
|
+
##
|
16
|
+
# A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
|
17
|
+
# Service location uses the following steps:
|
18
|
+
#
|
19
|
+
# 1. A RingServer begins listening on the broadcast UDP address.
|
20
|
+
# 2. A RingFinger sends a UDP packet containing the DRb URI where it will
|
21
|
+
# listen for a reply.
|
22
|
+
# 3. The RingServer recieves the UDP packet and connects back to the
|
23
|
+
# provided DRb URI with the DRb service.
|
24
|
+
|
25
|
+
class RingServer
|
26
|
+
|
27
|
+
include DRbUndumped
|
28
|
+
|
29
|
+
##
|
30
|
+
# Advertises +ts+ on the UDP broadcast address at +port+.
|
31
|
+
|
32
|
+
def initialize(ts, port=Ring_PORT)
|
33
|
+
@ts = ts
|
34
|
+
@soc = UDPSocket.open
|
35
|
+
@soc.bind('', port)
|
36
|
+
@w_service = write_service
|
37
|
+
@r_service = reply_service
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Creates a thread that picks up UDP packets and passes them to do_write
|
42
|
+
# for decoding.
|
43
|
+
|
44
|
+
def write_service
|
45
|
+
Thread.new do
|
46
|
+
loop do
|
47
|
+
msg = @soc.recv(1024)
|
48
|
+
do_write(msg)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Extracts the response URI from +msg+ and adds it to TupleSpace where it
|
55
|
+
# will be picked up by +reply_service+ for notification.
|
56
|
+
|
57
|
+
def do_write(msg)
|
58
|
+
Thread.new do
|
59
|
+
begin
|
60
|
+
tuple, sec = Marshal.load(msg)
|
61
|
+
@ts.write(tuple, sec)
|
62
|
+
rescue
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Creates a thread that notifies waiting clients from the TupleSpace.
|
69
|
+
|
70
|
+
def reply_service
|
71
|
+
Thread.new do
|
72
|
+
loop do
|
73
|
+
do_reply
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Pulls lookup tuples out of the TupleSpace and sends their DRb object the
|
80
|
+
# address of the local TupleSpace.
|
81
|
+
|
82
|
+
def do_reply
|
83
|
+
tuple = @ts.take([:lookup_ring, nil])
|
84
|
+
Thread.new { tuple[1].call(@ts) rescue nil}
|
85
|
+
rescue
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# RingFinger is used by RingServer clients to discover the RingServer's
|
92
|
+
# TupleSpace. Typically, all a client needs to do is call
|
93
|
+
# RingFinger.primary to retrieve the remote TupleSpace, which it can then
|
94
|
+
# begin using.
|
95
|
+
|
96
|
+
class RingFinger
|
97
|
+
|
98
|
+
@@broadcast_list = ['<broadcast>', 'localhost']
|
99
|
+
|
100
|
+
@@finger = nil
|
101
|
+
|
102
|
+
##
|
103
|
+
# Creates a singleton RingFinger and looks for a RingServer. Returns the
|
104
|
+
# created RingFinger.
|
105
|
+
|
106
|
+
def self.finger
|
107
|
+
unless @@finger
|
108
|
+
@@finger = self.new
|
109
|
+
@@finger.lookup_ring_any
|
110
|
+
end
|
111
|
+
@@finger
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Returns the first advertised TupleSpace.
|
116
|
+
|
117
|
+
def self.primary
|
118
|
+
finger.primary
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Contains all discoverd TupleSpaces except for the primary.
|
123
|
+
|
124
|
+
def self.to_a
|
125
|
+
finger.to_a
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# The list of addresses where RingFinger will send query packets.
|
130
|
+
|
131
|
+
attr_accessor :broadcast_list
|
132
|
+
|
133
|
+
##
|
134
|
+
# The port that RingFinger will send query packets to.
|
135
|
+
|
136
|
+
attr_accessor :port
|
137
|
+
|
138
|
+
##
|
139
|
+
# Contain the first advertised TupleSpace after lookup_ring_any is called.
|
140
|
+
|
141
|
+
attr_accessor :primary
|
142
|
+
|
143
|
+
##
|
144
|
+
# Creates a new RingFinger that will look for RingServers at +port+ on
|
145
|
+
# the addresses in +broadcast_list+.
|
146
|
+
|
147
|
+
def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
|
148
|
+
@broadcast_list = broadcast_list || ['localhost']
|
149
|
+
@port = port
|
150
|
+
@primary = nil
|
151
|
+
@rings = []
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Contains all discovered TupleSpaces except for the primary.
|
156
|
+
|
157
|
+
def to_a
|
158
|
+
@rings
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Iterates over all discovered TupleSpaces starting with the primary.
|
163
|
+
|
164
|
+
def each
|
165
|
+
lookup_ring_any unless @primary
|
166
|
+
return unless @primary
|
167
|
+
yield(@primary)
|
168
|
+
@rings.each { |x| yield(x) }
|
169
|
+
end
|
170
|
+
|
171
|
+
##
|
172
|
+
# Looks up RingServers waiting +timeout+ seconds. RingServers will be
|
173
|
+
# given +block+ as a callback, which will be called with the remote
|
174
|
+
# TupleSpace.
|
175
|
+
|
176
|
+
def lookup_ring(timeout=5, &block)
|
177
|
+
return lookup_ring_any(timeout) unless block_given?
|
178
|
+
|
179
|
+
msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
|
180
|
+
@broadcast_list.each do |it|
|
181
|
+
soc = UDPSocket.open
|
182
|
+
begin
|
183
|
+
soc.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
184
|
+
soc.send(msg, 0, it, @port)
|
185
|
+
rescue
|
186
|
+
nil
|
187
|
+
ensure
|
188
|
+
soc.close
|
189
|
+
end
|
190
|
+
end
|
191
|
+
sleep(timeout)
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Returns the first found remote TupleSpace. Any further recovered
|
196
|
+
# TupleSpaces can be found by calling +to_a+.
|
197
|
+
|
198
|
+
def lookup_ring_any(timeout=5)
|
199
|
+
queue = Queue.new
|
200
|
+
|
201
|
+
th = Thread.new do
|
202
|
+
self.lookup_ring(timeout) do |ts|
|
203
|
+
queue.push(ts)
|
204
|
+
end
|
205
|
+
queue.push(nil)
|
206
|
+
while it = queue.pop
|
207
|
+
@rings.push(it)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
@primary = queue.pop
|
212
|
+
raise('RingNotFound') if @primary.nil?
|
213
|
+
@primary
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# RingProvider uses a RingServer advertised TupleSpace as a name service.
|
220
|
+
# TupleSpace clients can register themselves with the remote TupleSpace and
|
221
|
+
# look up other provided services via the remote TupleSpace.
|
222
|
+
#
|
223
|
+
# Services are registered with a tuple of the format [:name, klass,
|
224
|
+
# DRbObject, description].
|
225
|
+
|
226
|
+
class RingProvider
|
227
|
+
|
228
|
+
##
|
229
|
+
# Creates a RingProvider that will provide a +klass+ service running on
|
230
|
+
# +front+, with a +description+. +renewer+ is optional.
|
231
|
+
|
232
|
+
def initialize(klass, front, desc, renewer = nil)
|
233
|
+
@tuple = [:name, klass, front, desc]
|
234
|
+
@renewer = renewer || Rinda::SimpleRenewer.new
|
235
|
+
end
|
236
|
+
|
237
|
+
##
|
238
|
+
# Advertises this service on the primary remote TupleSpace.
|
239
|
+
|
240
|
+
def provide
|
241
|
+
ts = Rinda::RingFinger.primary
|
242
|
+
ts.write(@tuple, @renewer)
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
if __FILE__ == $0
|
250
|
+
DRb.start_service
|
251
|
+
case ARGV.shift
|
252
|
+
when 's'
|
253
|
+
require 'rinda/tuplespace'
|
254
|
+
ts = Rinda::TupleSpace.new
|
255
|
+
place = Rinda::RingServer.new(ts)
|
256
|
+
$stdin.gets
|
257
|
+
when 'w'
|
258
|
+
finger = Rinda::RingFinger.new(nil)
|
259
|
+
finger.lookup_ring do |ts|
|
260
|
+
p ts
|
261
|
+
ts.write([:hello, :world])
|
262
|
+
end
|
263
|
+
when 'r'
|
264
|
+
finger = Rinda::RingFinger.new(nil)
|
265
|
+
finger.lookup_ring do |ts|
|
266
|
+
p ts
|
267
|
+
p ts.take([nil, nil])
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
@@ -0,0 +1,589 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'thread'
|
3
|
+
require 'drb/drb'
|
4
|
+
require 'rinda/rinda'
|
5
|
+
|
6
|
+
module Rinda
|
7
|
+
|
8
|
+
##
|
9
|
+
# A TupleEntry is a Tuple (i.e. a possible entry in some Tuplespace)
|
10
|
+
# together with expiry and cancellation data.
|
11
|
+
|
12
|
+
class TupleEntry
|
13
|
+
|
14
|
+
include DRbUndumped
|
15
|
+
|
16
|
+
attr_accessor :expires
|
17
|
+
|
18
|
+
##
|
19
|
+
# Creates a TupleEntry based on +ary+ with an optional renewer or expiry
|
20
|
+
# time +sec+.
|
21
|
+
#
|
22
|
+
# A renewer must implement the +renew+ method which returns a Numeric,
|
23
|
+
# nil, or true to indicate when the tuple has expired.
|
24
|
+
|
25
|
+
def initialize(ary, sec=nil)
|
26
|
+
@cancel = false
|
27
|
+
@expires = nil
|
28
|
+
@tuple = make_tuple(ary)
|
29
|
+
@renewer = nil
|
30
|
+
renew(sec)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Marks this TupleEntry as canceled.
|
35
|
+
|
36
|
+
def cancel
|
37
|
+
@cancel = true
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# A TupleEntry is dead when it is canceled or expired.
|
42
|
+
|
43
|
+
def alive?
|
44
|
+
!canceled? && !expired?
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Return the object which makes up the tuple itself: the Array
|
49
|
+
# or Hash.
|
50
|
+
|
51
|
+
def value; @tuple.value; end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Returns the canceled status.
|
55
|
+
|
56
|
+
def canceled?; @cancel; end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Has this tuple expired? (true/false).
|
60
|
+
#
|
61
|
+
# A tuple has expired when its expiry timer based on the +sec+ argument to
|
62
|
+
# #initialize runs out.
|
63
|
+
|
64
|
+
def expired?
|
65
|
+
return true unless @expires
|
66
|
+
return false if @expires > Time.now
|
67
|
+
return true if @renewer.nil?
|
68
|
+
renew(@renewer)
|
69
|
+
return true unless @expires
|
70
|
+
return @expires < Time.now
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Reset the expiry time according to +sec_or_renewer+.
|
75
|
+
#
|
76
|
+
# +nil+:: it is set to expire in the far future.
|
77
|
+
# +false+:: it has expired.
|
78
|
+
# Numeric:: it will expire in that many seconds.
|
79
|
+
#
|
80
|
+
# Otherwise the argument refers to some kind of renewer object
|
81
|
+
# which will reset its expiry time.
|
82
|
+
|
83
|
+
def renew(sec_or_renewer)
|
84
|
+
sec, @renewer = get_renewer(sec_or_renewer)
|
85
|
+
@expires = make_expires(sec)
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Returns an expiry Time based on +sec+ which can be one of:
|
90
|
+
# Numeric:: +sec+ seconds into the future
|
91
|
+
# +true+:: the expiry time is the start of 1970 (i.e. expired)
|
92
|
+
# +nil+:: it is Tue Jan 19 03:14:07 GMT Standard Time 2038 (i.e. when
|
93
|
+
# UNIX clocks will die)
|
94
|
+
|
95
|
+
def make_expires(sec=nil)
|
96
|
+
case sec
|
97
|
+
when Numeric
|
98
|
+
Time.now + sec
|
99
|
+
when true
|
100
|
+
Time.at(1)
|
101
|
+
when nil
|
102
|
+
Time.at(2**31-1)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Retrieves +key+ from the tuple.
|
108
|
+
|
109
|
+
def [](key)
|
110
|
+
@tuple[key]
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Fetches +key+ from the tuple.
|
115
|
+
|
116
|
+
def fetch(key)
|
117
|
+
@tuple.fetch(key)
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# The size of the tuple.
|
122
|
+
|
123
|
+
def size
|
124
|
+
@tuple.size
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Creates a Rinda::Tuple for +ary+.
|
129
|
+
|
130
|
+
def make_tuple(ary)
|
131
|
+
Rinda::Tuple.new(ary)
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
##
|
137
|
+
# Returns a valid argument to make_expires and the renewer or nil.
|
138
|
+
#
|
139
|
+
# Given +true+, +nil+, or Numeric, returns that value and +nil+ (no actual
|
140
|
+
# renewer). Otherwise it returns an expiry value from calling +it.renew+
|
141
|
+
# and the renewer.
|
142
|
+
|
143
|
+
def get_renewer(it)
|
144
|
+
case it
|
145
|
+
when Numeric, true, nil
|
146
|
+
return it, nil
|
147
|
+
else
|
148
|
+
begin
|
149
|
+
return it.renew, it
|
150
|
+
rescue Exception
|
151
|
+
return it, nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# A TemplateEntry is a Template together with expiry and cancellation data.
|
160
|
+
|
161
|
+
class TemplateEntry < TupleEntry
|
162
|
+
##
|
163
|
+
# Matches this TemplateEntry against +tuple+. See Template#match for
|
164
|
+
# details on how a Template matches a Tuple.
|
165
|
+
|
166
|
+
def match(tuple)
|
167
|
+
@tuple.match(tuple)
|
168
|
+
end
|
169
|
+
|
170
|
+
alias === match
|
171
|
+
|
172
|
+
def make_tuple(ary) # :nodoc:
|
173
|
+
Rinda::Template.new(ary)
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# <i>Documentation?</i>
|
180
|
+
|
181
|
+
class WaitTemplateEntry < TemplateEntry
|
182
|
+
|
183
|
+
attr_reader :found
|
184
|
+
|
185
|
+
def initialize(place, ary, expires=nil)
|
186
|
+
super(ary, expires)
|
187
|
+
@place = place
|
188
|
+
@cond = place.new_cond
|
189
|
+
@found = nil
|
190
|
+
end
|
191
|
+
|
192
|
+
def cancel
|
193
|
+
super
|
194
|
+
signal
|
195
|
+
end
|
196
|
+
|
197
|
+
def wait
|
198
|
+
@cond.wait
|
199
|
+
end
|
200
|
+
|
201
|
+
def read(tuple)
|
202
|
+
@found = tuple
|
203
|
+
signal
|
204
|
+
end
|
205
|
+
|
206
|
+
def signal
|
207
|
+
@place.synchronize do
|
208
|
+
@cond.signal
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# A NotifyTemplateEntry is returned by TupleSpace#notify and is notified of
|
216
|
+
# TupleSpace changes. You may receive either your subscribed event or the
|
217
|
+
# 'close' event when iterating over notifications.
|
218
|
+
#
|
219
|
+
# See TupleSpace#notify_event for valid notification types.
|
220
|
+
#
|
221
|
+
# == Example
|
222
|
+
#
|
223
|
+
# ts = Rinda::TupleSpace.new
|
224
|
+
# observer = ts.notify 'write', [nil]
|
225
|
+
#
|
226
|
+
# Thread.start do
|
227
|
+
# observer.each { |t| p t }
|
228
|
+
# end
|
229
|
+
#
|
230
|
+
# 3.times { |i| ts.write [i] }
|
231
|
+
#
|
232
|
+
# Outputs:
|
233
|
+
#
|
234
|
+
# ['write', [0]]
|
235
|
+
# ['write', [1]]
|
236
|
+
# ['write', [2]]
|
237
|
+
|
238
|
+
class NotifyTemplateEntry < TemplateEntry
|
239
|
+
|
240
|
+
##
|
241
|
+
# Creates a new NotifyTemplateEntry that watches +place+ for +event+s that
|
242
|
+
# match +tuple+.
|
243
|
+
|
244
|
+
def initialize(place, event, tuple, expires=nil)
|
245
|
+
ary = [event, Rinda::Template.new(tuple)]
|
246
|
+
super(ary, expires)
|
247
|
+
@queue = Queue.new
|
248
|
+
@done = false
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Called by TupleSpace to notify this NotifyTemplateEntry of a new event.
|
253
|
+
|
254
|
+
def notify(ev)
|
255
|
+
@queue.push(ev)
|
256
|
+
end
|
257
|
+
|
258
|
+
##
|
259
|
+
# Retrieves a notification. Raises RequestExpiredError when this
|
260
|
+
# NotifyTemplateEntry expires.
|
261
|
+
|
262
|
+
def pop
|
263
|
+
raise RequestExpiredError if @done
|
264
|
+
it = @queue.pop
|
265
|
+
@done = true if it[0] == 'close'
|
266
|
+
return it
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# Yields event/tuple pairs until this NotifyTemplateEntry expires.
|
271
|
+
|
272
|
+
def each # :yields: event, tuple
|
273
|
+
while !@done
|
274
|
+
it = pop
|
275
|
+
yield(it)
|
276
|
+
end
|
277
|
+
rescue
|
278
|
+
ensure
|
279
|
+
cancel
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# TupleBag is an unordered collection of tuples. It is the basis
|
286
|
+
# of Tuplespace.
|
287
|
+
|
288
|
+
class TupleBag
|
289
|
+
|
290
|
+
def initialize # :nodoc:
|
291
|
+
@hash = {}
|
292
|
+
end
|
293
|
+
|
294
|
+
##
|
295
|
+
# +true+ if the TupleBag to see if it has any expired entries.
|
296
|
+
|
297
|
+
def has_expires?
|
298
|
+
@hash.each do |k, v|
|
299
|
+
v.each do |tuple|
|
300
|
+
return true if tuple.expires
|
301
|
+
end
|
302
|
+
end
|
303
|
+
false
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Add +ary+ to the TupleBag.
|
308
|
+
|
309
|
+
def push(ary)
|
310
|
+
size = ary.size
|
311
|
+
@hash[size] ||= []
|
312
|
+
@hash[size].push(ary)
|
313
|
+
end
|
314
|
+
|
315
|
+
##
|
316
|
+
# Removes +ary+ from the TupleBag.
|
317
|
+
|
318
|
+
def delete(ary)
|
319
|
+
size = ary.size
|
320
|
+
@hash.fetch(size, []).delete(ary)
|
321
|
+
end
|
322
|
+
|
323
|
+
##
|
324
|
+
# Finds all live tuples that match +template+.
|
325
|
+
|
326
|
+
def find_all(template)
|
327
|
+
@hash.fetch(template.size, []).find_all do |tuple|
|
328
|
+
tuple.alive? && template.match(tuple)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
##
|
333
|
+
# Finds a live tuple that matches +template+.
|
334
|
+
|
335
|
+
def find(template)
|
336
|
+
@hash.fetch(template.size, []).find do |tuple|
|
337
|
+
tuple.alive? && template.match(tuple)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
##
|
342
|
+
# Finds all tuples in the TupleBag which when treated as templates, match
|
343
|
+
# +tuple+ and are alive.
|
344
|
+
|
345
|
+
def find_all_template(tuple)
|
346
|
+
@hash.fetch(tuple.size, []).find_all do |template|
|
347
|
+
template.alive? && template.match(tuple)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
##
|
352
|
+
# Delete tuples which dead tuples from the TupleBag, returning the deleted
|
353
|
+
# tuples.
|
354
|
+
|
355
|
+
def delete_unless_alive
|
356
|
+
deleted = []
|
357
|
+
@hash.keys.each do |size|
|
358
|
+
ary = []
|
359
|
+
@hash[size].each do |tuple|
|
360
|
+
if tuple.alive?
|
361
|
+
ary.push(tuple)
|
362
|
+
else
|
363
|
+
deleted.push(tuple)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
@hash[size] = ary
|
367
|
+
end
|
368
|
+
deleted
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
|
373
|
+
##
|
374
|
+
# The Tuplespace manages access to the tuples it contains,
|
375
|
+
# ensuring mutual exclusion requirements are met.
|
376
|
+
#
|
377
|
+
# The +sec+ option for the write, take, move, read and notify methods may
|
378
|
+
# either be a number of seconds or a Renewer object.
|
379
|
+
|
380
|
+
class TupleSpace
|
381
|
+
|
382
|
+
include DRbUndumped
|
383
|
+
include MonitorMixin
|
384
|
+
|
385
|
+
##
|
386
|
+
# Creates a new TupleSpace. +period+ is used to control how often to look
|
387
|
+
# for dead tuples after modifications to the TupleSpace.
|
388
|
+
#
|
389
|
+
# If no dead tuples are found +period+ seconds after the last
|
390
|
+
# modification, the TupleSpace will stop looking for dead tuples.
|
391
|
+
|
392
|
+
def initialize(period=60)
|
393
|
+
super()
|
394
|
+
@bag = TupleBag.new
|
395
|
+
@read_waiter = TupleBag.new
|
396
|
+
@take_waiter = TupleBag.new
|
397
|
+
@notify_waiter = TupleBag.new
|
398
|
+
@period = period
|
399
|
+
@keeper = nil
|
400
|
+
end
|
401
|
+
|
402
|
+
##
|
403
|
+
# Adds +tuple+
|
404
|
+
|
405
|
+
def write(tuple, sec=nil)
|
406
|
+
entry = TupleEntry.new(tuple, sec)
|
407
|
+
start_keeper
|
408
|
+
synchronize do
|
409
|
+
if entry.expired?
|
410
|
+
@read_waiter.find_all_template(entry).each do |template|
|
411
|
+
template.read(tuple)
|
412
|
+
end
|
413
|
+
notify_event('write', entry.value)
|
414
|
+
notify_event('delete', entry.value)
|
415
|
+
else
|
416
|
+
@bag.push(entry)
|
417
|
+
@read_waiter.find_all_template(entry).each do |template|
|
418
|
+
template.read(tuple)
|
419
|
+
end
|
420
|
+
@take_waiter.find_all_template(entry).each do |template|
|
421
|
+
template.signal
|
422
|
+
end
|
423
|
+
notify_event('write', entry.value)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
entry
|
427
|
+
end
|
428
|
+
|
429
|
+
##
|
430
|
+
# Removes +tuple+
|
431
|
+
|
432
|
+
def take(tuple, sec=nil, &block)
|
433
|
+
move(nil, tuple, sec, &block)
|
434
|
+
end
|
435
|
+
|
436
|
+
##
|
437
|
+
# Moves +tuple+ to +port+.
|
438
|
+
|
439
|
+
def move(port, tuple, sec=nil)
|
440
|
+
template = WaitTemplateEntry.new(self, tuple, sec)
|
441
|
+
yield(template) if block_given?
|
442
|
+
start_keeper
|
443
|
+
synchronize do
|
444
|
+
entry = @bag.find(template)
|
445
|
+
if entry
|
446
|
+
port.push(entry.value) if port
|
447
|
+
@bag.delete(entry)
|
448
|
+
notify_event('take', entry.value)
|
449
|
+
return entry.value
|
450
|
+
end
|
451
|
+
raise RequestExpiredError if template.expired?
|
452
|
+
|
453
|
+
begin
|
454
|
+
@take_waiter.push(template)
|
455
|
+
while true
|
456
|
+
raise RequestCanceledError if template.canceled?
|
457
|
+
raise RequestExpiredError if template.expired?
|
458
|
+
entry = @bag.find(template)
|
459
|
+
if entry
|
460
|
+
port.push(entry.value) if port
|
461
|
+
@bag.delete(entry)
|
462
|
+
notify_event('take', entry.value)
|
463
|
+
return entry.value
|
464
|
+
end
|
465
|
+
template.wait
|
466
|
+
end
|
467
|
+
ensure
|
468
|
+
@take_waiter.delete(template)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
##
|
474
|
+
# Reads +tuple+, but does not remove it.
|
475
|
+
|
476
|
+
def read(tuple, sec=nil)
|
477
|
+
template = WaitTemplateEntry.new(self, tuple, sec)
|
478
|
+
yield(template) if block_given?
|
479
|
+
start_keeper
|
480
|
+
synchronize do
|
481
|
+
entry = @bag.find(template)
|
482
|
+
return entry.value if entry
|
483
|
+
raise RequestExpiredError if template.expired?
|
484
|
+
|
485
|
+
begin
|
486
|
+
@read_waiter.push(template)
|
487
|
+
template.wait
|
488
|
+
raise RequestCanceledError if template.canceled?
|
489
|
+
raise RequestExpiredError if template.expired?
|
490
|
+
return template.found
|
491
|
+
ensure
|
492
|
+
@read_waiter.delete(template)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
##
|
498
|
+
# Returns all tuples matching +tuple+. Does not remove the found tuples.
|
499
|
+
|
500
|
+
def read_all(tuple)
|
501
|
+
template = WaitTemplateEntry.new(self, tuple, nil)
|
502
|
+
synchronize do
|
503
|
+
entry = @bag.find_all(template)
|
504
|
+
entry.collect do |e|
|
505
|
+
e.value
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
##
|
511
|
+
# Registers for notifications of +event+. Returns a NotifyTemplateEntry.
|
512
|
+
# See NotifyTemplateEntry for examples of how to listen for notifications.
|
513
|
+
#
|
514
|
+
# +event+ can be:
|
515
|
+
# 'write':: A tuple was added
|
516
|
+
# 'take':: A tuple was taken or moved
|
517
|
+
# 'delete':: A tuple was lost after being overwritten or expiring
|
518
|
+
#
|
519
|
+
# The TupleSpace will also notify you of the 'close' event when the
|
520
|
+
# NotifyTemplateEntry has expired.
|
521
|
+
|
522
|
+
def notify(event, tuple, sec=nil)
|
523
|
+
template = NotifyTemplateEntry.new(self, event, tuple, sec)
|
524
|
+
synchronize do
|
525
|
+
@notify_waiter.push(template)
|
526
|
+
end
|
527
|
+
template
|
528
|
+
end
|
529
|
+
|
530
|
+
private
|
531
|
+
|
532
|
+
##
|
533
|
+
# Removes dead tuples.
|
534
|
+
|
535
|
+
def keep_clean
|
536
|
+
synchronize do
|
537
|
+
@read_waiter.delete_unless_alive.each do |e|
|
538
|
+
e.signal
|
539
|
+
end
|
540
|
+
@take_waiter.delete_unless_alive.each do |e|
|
541
|
+
e.signal
|
542
|
+
end
|
543
|
+
@notify_waiter.delete_unless_alive.each do |e|
|
544
|
+
e.notify(['close'])
|
545
|
+
end
|
546
|
+
@bag.delete_unless_alive.each do |e|
|
547
|
+
notify_event('delete', e.value)
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
##
|
553
|
+
# Notifies all registered listeners for +event+ of a status change of
|
554
|
+
# +tuple+.
|
555
|
+
|
556
|
+
def notify_event(event, tuple)
|
557
|
+
ev = [event, tuple]
|
558
|
+
@notify_waiter.find_all_template(ev).each do |template|
|
559
|
+
template.notify(ev)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
##
|
564
|
+
# Creates a thread that scans the tuplespace for expired tuples.
|
565
|
+
|
566
|
+
def start_keeper
|
567
|
+
return if @keeper && @keeper.alive?
|
568
|
+
@keeper = Thread.new do
|
569
|
+
while need_keeper?
|
570
|
+
keep_clean
|
571
|
+
sleep(@period)
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
##
|
577
|
+
# Checks the tuplespace to see if it needs cleaning.
|
578
|
+
|
579
|
+
def need_keeper?
|
580
|
+
return true if @bag.has_expires?
|
581
|
+
return true if @read_waiter.has_expires?
|
582
|
+
return true if @take_waiter.has_expires?
|
583
|
+
return true if @notify_waiter.has_expires?
|
584
|
+
end
|
585
|
+
|
586
|
+
end
|
587
|
+
|
588
|
+
end
|
589
|
+
|
data/lib/rubysl/rinda.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "rubysl/rinda/version"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require './lib/rubysl/rinda/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "rubysl-rinda"
|
6
|
+
spec.version = RubySL::Rinda::VERSION
|
7
|
+
spec.authors = ["Brian Shirai"]
|
8
|
+
spec.email = ["brixen@gmail.com"]
|
9
|
+
spec.description = %q{Ruby standard library rinda.}
|
10
|
+
spec.summary = %q{Ruby standard library rinda.}
|
11
|
+
spec.homepage = "https://github.com/rubysl/rubysl-rinda"
|
12
|
+
spec.license = "BSD"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($/)
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
20
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
21
|
+
spec.add_development_dependency "mspec", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rubysl-prettyprint", "~> 1.0"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubysl-rinda
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Shirai
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubysl-prettyprint
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
description: Ruby standard library rinda.
|
70
|
+
email:
|
71
|
+
- brixen@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .travis.yml
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lib/rinda/rinda.rb
|
83
|
+
- lib/rinda/ring.rb
|
84
|
+
- lib/rinda/tuplespace.rb
|
85
|
+
- lib/rubysl/rinda.rb
|
86
|
+
- lib/rubysl/rinda/version.rb
|
87
|
+
- rubysl-rinda.gemspec
|
88
|
+
homepage: https://github.com/rubysl/rubysl-rinda
|
89
|
+
licenses:
|
90
|
+
- BSD
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.0.7
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Ruby standard library rinda.
|
112
|
+
test_files: []
|