process_shared 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +25 -2
- data/ext/libpsem/psem_posix.c +34 -4
- data/lib/process_shared/abstract_semaphore.rb +22 -2
- data/lib/process_shared/binary_semaphore.rb +48 -0
- data/lib/process_shared/bounded_semaphore.rb +5 -5
- data/lib/process_shared/condition_variable.rb +30 -4
- data/lib/process_shared/libpsem.so +0 -0
- data/lib/process_shared/mutex.rb +15 -15
- data/lib/process_shared/posix_call.rb +3 -3
- data/lib/process_shared/psem.rb +7 -3
- data/lib/process_shared/semaphore.rb +23 -5
- data/lib/process_shared/shared_array.rb +69 -0
- data/lib/process_shared/shared_memory.rb +80 -12
- data/lib/process_shared/shared_memory_io.rb +321 -0
- data/lib/process_shared/with_self.rb +2 -2
- data/spec/process_shared/binary_semaphore_spec.rb +13 -0
- data/spec/process_shared/condition_variable_spec.rb +67 -0
- data/spec/process_shared/semaphore_spec.rb +41 -0
- data/spec/process_shared/shared_array_spec.rb +26 -0
- data/spec/process_shared/shared_memory_spec.rb +25 -0
- data/spec/spec_helper.rb +11 -2
- metadata +70 -82
@@ -0,0 +1,321 @@
|
|
1
|
+
module ProcessShared
|
2
|
+
# Does some bounds checking for EOF, but assumes underlying memory
|
3
|
+
# object (FFI::Pointer) will do bounds checking, in particular the
|
4
|
+
# {#_putbytes} method relies on this.
|
5
|
+
#
|
6
|
+
# Note: an unbounded FFI::Pointer may be converted into a bounded
|
7
|
+
# pointer using +ptr.slice(0, size)+.
|
8
|
+
class SharedMemoryIO
|
9
|
+
|
10
|
+
attr_accessor :pos
|
11
|
+
attr_reader :mem
|
12
|
+
|
13
|
+
def initialize(mem)
|
14
|
+
@mem = mem
|
15
|
+
@pos = 0
|
16
|
+
@closed = false # TODO: actually pay attention to this
|
17
|
+
end
|
18
|
+
|
19
|
+
def <<(*args)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def advise(*args)
|
24
|
+
# no-op
|
25
|
+
end
|
26
|
+
|
27
|
+
def autoclose=(*args)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def autoclose?
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def binmode
|
36
|
+
# no-op; always in binmode
|
37
|
+
end
|
38
|
+
|
39
|
+
def binmode?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def bytes
|
44
|
+
if block_given?
|
45
|
+
until eof?
|
46
|
+
yield _getbyte
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise NotImplementedError
|
50
|
+
end
|
51
|
+
end
|
52
|
+
alias_method :each_byte, :bytes
|
53
|
+
|
54
|
+
def chars
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
alias_method :each_char, :chars
|
58
|
+
|
59
|
+
def close
|
60
|
+
@closed = true
|
61
|
+
end
|
62
|
+
|
63
|
+
def close_on_exec=(bool)
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
66
|
+
|
67
|
+
def close_on_exec?
|
68
|
+
raise NotImplementedError
|
69
|
+
end
|
70
|
+
|
71
|
+
def close_read
|
72
|
+
raise NotImplementedError
|
73
|
+
end
|
74
|
+
|
75
|
+
def close_write
|
76
|
+
raise NotImplementedError
|
77
|
+
end
|
78
|
+
|
79
|
+
def closed?
|
80
|
+
@closed
|
81
|
+
end
|
82
|
+
|
83
|
+
def codepoints
|
84
|
+
raise NotImplementedError
|
85
|
+
end
|
86
|
+
alias_method :each_codepoint, :codepoints
|
87
|
+
|
88
|
+
def each
|
89
|
+
raise NotImplementedError
|
90
|
+
end
|
91
|
+
alias_method :each_line, :each
|
92
|
+
alias_method :lines, :each
|
93
|
+
|
94
|
+
def eof?
|
95
|
+
pos == mem.size
|
96
|
+
end
|
97
|
+
alias_method :eof, :eof?
|
98
|
+
|
99
|
+
def external_encoding
|
100
|
+
raise NotImplementedError
|
101
|
+
end
|
102
|
+
|
103
|
+
def fcntl
|
104
|
+
raise NotImplementedError
|
105
|
+
end
|
106
|
+
|
107
|
+
def fdatasync
|
108
|
+
raise NotImplementedError
|
109
|
+
end
|
110
|
+
|
111
|
+
def fileno
|
112
|
+
raise NotImplementedError
|
113
|
+
end
|
114
|
+
alias_method :to_i, :fileno
|
115
|
+
|
116
|
+
def flush
|
117
|
+
# no-op
|
118
|
+
end
|
119
|
+
|
120
|
+
def fsync
|
121
|
+
raise NotImplementedError
|
122
|
+
end
|
123
|
+
|
124
|
+
def getbyte
|
125
|
+
return nil if eof?
|
126
|
+
_getbyte
|
127
|
+
end
|
128
|
+
|
129
|
+
# {#getc} in Ruby 1.9 returns String or nil. In 1.8, it returned
|
130
|
+
# Fixnum of nil (identical to getbyte).
|
131
|
+
#
|
132
|
+
# FIXME: should this be encoding/character aware?
|
133
|
+
def getc19
|
134
|
+
if b = getbyte
|
135
|
+
'' << b
|
136
|
+
end
|
137
|
+
end
|
138
|
+
# FIXME: ignores versions prior to 1.8.
|
139
|
+
if RUBY_VERSION =~ /^1.8/
|
140
|
+
alias_method :getc, :getbyte
|
141
|
+
else
|
142
|
+
alias_method :getc, :getc19
|
143
|
+
end
|
144
|
+
|
145
|
+
def gets
|
146
|
+
raise NotImplementedError
|
147
|
+
end
|
148
|
+
|
149
|
+
def internal_encoding
|
150
|
+
raise NotImplementedError
|
151
|
+
end
|
152
|
+
|
153
|
+
def ioctl
|
154
|
+
raise NotImplementedError
|
155
|
+
end
|
156
|
+
|
157
|
+
def tty?
|
158
|
+
false
|
159
|
+
end
|
160
|
+
alias_method :isatty, :tty?
|
161
|
+
|
162
|
+
def lineno
|
163
|
+
raise NotImplementedError
|
164
|
+
end
|
165
|
+
|
166
|
+
def lineno=
|
167
|
+
raise NotImplementedError
|
168
|
+
end
|
169
|
+
|
170
|
+
def lines
|
171
|
+
raise NotImplementedError
|
172
|
+
end
|
173
|
+
|
174
|
+
def pid
|
175
|
+
raise NotImplementedError
|
176
|
+
end
|
177
|
+
|
178
|
+
alias_method :tell, :pos
|
179
|
+
|
180
|
+
def print(*args)
|
181
|
+
raise NotImplementedError
|
182
|
+
end
|
183
|
+
def printf(*args)
|
184
|
+
raise NotImplementedError
|
185
|
+
end
|
186
|
+
|
187
|
+
def putc(arg)
|
188
|
+
raise NotImplementedError
|
189
|
+
end
|
190
|
+
|
191
|
+
def puts(*args)
|
192
|
+
raise NotImplementedError
|
193
|
+
end
|
194
|
+
|
195
|
+
# FIXME: this doesn't match IO#read exactly (corner cases about
|
196
|
+
# EOF and whether length was nil or not), but it's enough for
|
197
|
+
# {Marshal::load}.
|
198
|
+
def read(length = nil, buffer = nil)
|
199
|
+
length ||= (mem.size - pos)
|
200
|
+
buffer ||= ''
|
201
|
+
|
202
|
+
actual_length = [(mem.size - pos), length].min
|
203
|
+
actual_length.times do
|
204
|
+
buffer << _getbyte
|
205
|
+
end
|
206
|
+
buffer
|
207
|
+
end
|
208
|
+
|
209
|
+
def read_nonblock(*args)
|
210
|
+
raise NotImplementedError
|
211
|
+
end
|
212
|
+
|
213
|
+
def readbyte
|
214
|
+
raise EOFError if eof?
|
215
|
+
_getbyte
|
216
|
+
end
|
217
|
+
|
218
|
+
def readchar
|
219
|
+
raise NotImplementedError
|
220
|
+
end
|
221
|
+
|
222
|
+
def readline
|
223
|
+
raise NotImplementedError
|
224
|
+
end
|
225
|
+
|
226
|
+
def readlines
|
227
|
+
raise NotImplementedError
|
228
|
+
end
|
229
|
+
|
230
|
+
def readpartial
|
231
|
+
raise NotImplementedError
|
232
|
+
end
|
233
|
+
|
234
|
+
def reopen
|
235
|
+
raise NotImplementedError
|
236
|
+
end
|
237
|
+
|
238
|
+
def rewind
|
239
|
+
pos = 0
|
240
|
+
end
|
241
|
+
|
242
|
+
def seek(amount, whence = IO::SEEK_SET)
|
243
|
+
case whence
|
244
|
+
when IO::SEEK_CUR
|
245
|
+
self.pos += amount
|
246
|
+
when IO::SEEK_END
|
247
|
+
self.pos = (mem.size + amount)
|
248
|
+
when IO::SEEK_SET
|
249
|
+
self.pos = amount
|
250
|
+
else
|
251
|
+
raise ArgumentError, "bad seek whence #{whence}"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def set_encoding
|
256
|
+
raise NotImplementedError
|
257
|
+
end
|
258
|
+
|
259
|
+
def stat
|
260
|
+
raise NotImplementedError
|
261
|
+
end
|
262
|
+
|
263
|
+
def sync
|
264
|
+
true
|
265
|
+
end
|
266
|
+
|
267
|
+
def sync=
|
268
|
+
raise NotImplementedError
|
269
|
+
end
|
270
|
+
|
271
|
+
def sysread(*args)
|
272
|
+
raise NotImplementedError
|
273
|
+
end
|
274
|
+
|
275
|
+
def sysseek(*args)
|
276
|
+
raise NotImplementedError
|
277
|
+
end
|
278
|
+
|
279
|
+
def syswrite(*args)
|
280
|
+
raise NotImplementedError
|
281
|
+
end
|
282
|
+
|
283
|
+
def to_io
|
284
|
+
raise NotImplementedError
|
285
|
+
end
|
286
|
+
|
287
|
+
def ungetbyte
|
288
|
+
raise IOError if pos == 0
|
289
|
+
pos -= 1
|
290
|
+
end
|
291
|
+
|
292
|
+
def ungetc
|
293
|
+
raise NotImplementedError
|
294
|
+
end
|
295
|
+
|
296
|
+
def write(str)
|
297
|
+
s = str.to_s
|
298
|
+
_putbytes(s)
|
299
|
+
s.size
|
300
|
+
end
|
301
|
+
|
302
|
+
def write_nonblock(str)
|
303
|
+
raise NotImplementedError
|
304
|
+
end
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
# Like {#getbyte} but does not perform eof check.
|
309
|
+
def _getbyte
|
310
|
+
b = mem.get_uchar(pos)
|
311
|
+
self.pos += 1
|
312
|
+
b
|
313
|
+
end
|
314
|
+
|
315
|
+
def _putbytes(str)
|
316
|
+
mem.put_bytes(pos, str, 0, str.size)
|
317
|
+
self.pos += str.size
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
end
|
@@ -2,8 +2,8 @@ module ProcessShared
|
|
2
2
|
module WithSelf
|
3
3
|
# With no associated block, return self. If the optional code
|
4
4
|
# block is given, it will be passed `self` as an argument, and the
|
5
|
-
# self object will automatically be closed (by invoking
|
6
|
-
#
|
5
|
+
# self object will automatically be closed (by invoking +close+ on
|
6
|
+
# +self+) when the block terminates. In this instance, value of
|
7
7
|
# the block is returned.
|
8
8
|
def with_self
|
9
9
|
if block_given?
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'process_shared/binary_semaphore'
|
3
|
+
|
4
|
+
module ProcessShared
|
5
|
+
describe BinarySemaphore do
|
6
|
+
it 'raises exception with double post' do
|
7
|
+
BinarySemaphore.open(0) do |sem|
|
8
|
+
sem.post
|
9
|
+
proc { sem.post }.must_raise(ProcessError)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'process_shared/condition_variable'
|
4
|
+
|
5
|
+
module ProcessShared
|
6
|
+
describe ConditionVariable do
|
7
|
+
it 'runs the example of Ruby Stdlib ConditionVariable' do
|
8
|
+
mutex = Mutex.new
|
9
|
+
resource = ConditionVariable.new
|
10
|
+
|
11
|
+
a = fork {
|
12
|
+
mutex.synchronize {
|
13
|
+
resource.wait(mutex)
|
14
|
+
}
|
15
|
+
Kernel.exit!
|
16
|
+
}
|
17
|
+
|
18
|
+
b = fork {
|
19
|
+
mutex.synchronize {
|
20
|
+
resource.signal
|
21
|
+
}
|
22
|
+
Kernel.exit!
|
23
|
+
}
|
24
|
+
|
25
|
+
::Process.wait(a)
|
26
|
+
::Process.wait(b)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'broadcasts to multiple processes' do
|
30
|
+
mutex = Mutex.new
|
31
|
+
cond = ConditionVariable.new
|
32
|
+
mem = SharedMemory.new(:int)
|
33
|
+
mem.write_int(0)
|
34
|
+
|
35
|
+
pids = []
|
36
|
+
10.times do
|
37
|
+
pids << fork do
|
38
|
+
mutex.synchronize {
|
39
|
+
cond.wait(mutex)
|
40
|
+
mem.write_int(mem.read_int + 1)
|
41
|
+
}
|
42
|
+
Kernel.exit!
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
sleep 0.2 # hopefully they are all waiting...
|
47
|
+
mutex.synchronize {
|
48
|
+
cond.broadcast
|
49
|
+
}
|
50
|
+
|
51
|
+
pids.each { |p| ::Process.wait(p) }
|
52
|
+
|
53
|
+
mem.read_int.must_equal(10)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'stops waiting after timeout' do
|
57
|
+
mutex = Mutex.new
|
58
|
+
cond = ConditionVariable.new
|
59
|
+
|
60
|
+
mutex.synchronize {
|
61
|
+
start = Time.now.to_f
|
62
|
+
cond.wait(mutex, 0.1)
|
63
|
+
(Time.now.to_f - start).must be_gte(0.1)
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -72,5 +72,46 @@ module ProcessShared
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
75
|
+
|
76
|
+
describe '#try_wait' do
|
77
|
+
it 'returns immediately with non-zero semaphore' do
|
78
|
+
Semaphore.open(1) do |sem|
|
79
|
+
start = Time.now.to_f
|
80
|
+
sem.try_wait
|
81
|
+
(Time.now.to_f - start).must be_lt(0.01)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'raises EAGAIN with zero semaphore' do
|
86
|
+
Semaphore.open(0) do |sem|
|
87
|
+
proc { sem.try_wait }.must_raise(Errno::EAGAIN)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'raises ETIMEDOUT after timeout expires' do
|
92
|
+
Semaphore.open(0) do |sem|
|
93
|
+
start = Time.now.to_f
|
94
|
+
proc { sem.try_wait(0.1) }.must_raise(Errno::ETIMEDOUT)
|
95
|
+
(Time.now.to_f - start).must be_gte(0.1)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'returns after waiting if another processes posts' do
|
100
|
+
Semaphore.open(0) do |sem|
|
101
|
+
start = Time.now.to_f
|
102
|
+
|
103
|
+
pid = fork do
|
104
|
+
sleep 0.01
|
105
|
+
sem.post
|
106
|
+
Kernel.exit!
|
107
|
+
end
|
108
|
+
|
109
|
+
sem.try_wait(0.1)
|
110
|
+
(Time.now.to_f - start).must be_lt(0.1)
|
111
|
+
|
112
|
+
::Process.wait(pid)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
75
116
|
end
|
76
117
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'process_shared/shared_array'
|
3
|
+
|
4
|
+
module ProcessShared
|
5
|
+
describe SharedArray do
|
6
|
+
it 'initializes arrays' do
|
7
|
+
mem = SharedArray.new(:int, 10)
|
8
|
+
10.times do |i|
|
9
|
+
mem[i] = i
|
10
|
+
end
|
11
|
+
10.times do |i|
|
12
|
+
mem[i].must_equal i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'responds to Enumerable methods' do
|
17
|
+
mem = SharedArray.new(:int, 4)
|
18
|
+
4.times do |i|
|
19
|
+
mem[i] = i+1
|
20
|
+
end
|
21
|
+
|
22
|
+
mem.map { |i| i * 2 }.must_equal [2, 4, 6, 8]
|
23
|
+
mem.sort.must_equal [1, 2, 3, 4]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -32,5 +32,30 @@ module ProcessShared
|
|
32
32
|
|
33
33
|
mem.get_int(0).must_equal(1234567)
|
34
34
|
end
|
35
|
+
|
36
|
+
describe 'Object dump/load' do
|
37
|
+
it 'writes serialized objects' do
|
38
|
+
mem = SharedMemory.new(1024)
|
39
|
+
pid = fork do
|
40
|
+
mem.write_object(['a', 'b'])
|
41
|
+
Kernel.exit!
|
42
|
+
end
|
43
|
+
::Process.wait(pid)
|
44
|
+
mem.read_object.must_equal ['a', 'b']
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'raises IndexError when insufficient space' do
|
48
|
+
mem = SharedMemory.new(2)
|
49
|
+
proc { mem.write_object(['a', 'b']) }.must_raise(IndexError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'writes with an offset' do
|
53
|
+
mem = SharedMemory.new(1024)
|
54
|
+
mem.put_object(2, 'string')
|
55
|
+
proc { mem.read_object }.must_raise(TypeError)
|
56
|
+
proc { mem.get_object(0) }.must_raise(TypeError)
|
57
|
+
mem.get_object(2).must_equal 'string'
|
58
|
+
end
|
59
|
+
end
|
35
60
|
end
|
36
61
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -17,15 +17,16 @@ class RangeMatcher
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def matches?(subject)
|
20
|
+
@subject = subject
|
20
21
|
subject.send(@operator, @limit)
|
21
22
|
end
|
22
23
|
|
23
24
|
def failure_message_for_should
|
24
|
-
"expected #{operator} #{@limit}"
|
25
|
+
"expected #{@operator} #{@limit}, not #{@subject}"
|
25
26
|
end
|
26
27
|
|
27
28
|
def failure_message_for_should_not
|
28
|
-
"expected not #{operator} #{@limit}"
|
29
|
+
"expected not #{@operator} #{@limit}, not #{@subject}"
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -33,6 +34,14 @@ def be_lt(value)
|
|
33
34
|
RangeMatcher.new('<', value)
|
34
35
|
end
|
35
36
|
|
37
|
+
def be_gt(value)
|
38
|
+
RangeMatcher.new('>', value)
|
39
|
+
end
|
40
|
+
|
36
41
|
def be_lte(value)
|
37
42
|
RangeMatcher.new('<=', value)
|
38
43
|
end
|
44
|
+
|
45
|
+
def be_gte(value)
|
46
|
+
RangeMatcher.new('>=', value)
|
47
|
+
end
|