process_shared 0.0.3 → 0.0.4
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/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
|