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.
@@ -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