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.
@@ -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 `close` on
6
- # `self`) when the block terminates. In this instance, value of
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