process_shared 0.0.1

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.
Files changed (46) hide show
  1. data/COPYING +19 -0
  2. data/ChangeLog +0 -0
  3. data/README.rdoc +69 -0
  4. data/ext/libpsem/bsem.c +188 -0
  5. data/ext/libpsem/bsem.h +32 -0
  6. data/ext/libpsem/constants.c +22 -0
  7. data/ext/libpsem/constants.h +18 -0
  8. data/ext/libpsem/extconf.rb +36 -0
  9. data/ext/libpsem/mempcpy.c +7 -0
  10. data/ext/libpsem/mempcpy.h +13 -0
  11. data/ext/libpsem/mutex.c +15 -0
  12. data/ext/libpsem/mutex.h +14 -0
  13. data/ext/libpsem/psem.c +15 -0
  14. data/ext/libpsem/psem.h +43 -0
  15. data/ext/libpsem/psem_error.c +46 -0
  16. data/ext/libpsem/psem_error.h +11 -0
  17. data/ext/libpsem/psem_posix.c +130 -0
  18. data/ext/libpsem/psem_posix.h +10 -0
  19. data/ext/pthread_sync_helper/extconf.rb +9 -0
  20. data/ext/pthread_sync_helper/pthread_sync_helper.c +43 -0
  21. data/ext/semaphore.c +623 -0
  22. data/lib/process_shared.rb +6 -0
  23. data/lib/process_shared/abstract_semaphore.rb +50 -0
  24. data/lib/process_shared/bounded_semaphore.rb +43 -0
  25. data/lib/process_shared/condition_variable.rb +27 -0
  26. data/lib/process_shared/libc.rb +36 -0
  27. data/lib/process_shared/libpsem.bundle +0 -0
  28. data/lib/process_shared/libpsem.so +0 -0
  29. data/lib/process_shared/mutex.rb +103 -0
  30. data/lib/process_shared/posix_call.rb +29 -0
  31. data/lib/process_shared/process_error.rb +3 -0
  32. data/lib/process_shared/psem.rb +109 -0
  33. data/lib/process_shared/rt.rb +21 -0
  34. data/lib/process_shared/semaphore.rb +60 -0
  35. data/lib/process_shared/shared_memory.rb +45 -0
  36. data/lib/process_shared/thread.rb +30 -0
  37. data/lib/process_shared/with_self.rb +20 -0
  38. data/lib/scratch.rb +300 -0
  39. data/spec/process_shared/bounded_semaphore_spec.rb +48 -0
  40. data/spec/process_shared/libc_spec.rb +9 -0
  41. data/spec/process_shared/mutex_spec.rb +74 -0
  42. data/spec/process_shared/psem_spec.rb +136 -0
  43. data/spec/process_shared/semaphore_spec.rb +76 -0
  44. data/spec/process_shared/shared_memory_spec.rb +36 -0
  45. data/spec/spec_helper.rb +35 -0
  46. metadata +139 -0
@@ -0,0 +1,45 @@
1
+ require 'process_shared/rt'
2
+ require 'process_shared/libc'
3
+ require 'process_shared/with_self'
4
+
5
+ module ProcessShared
6
+ # Memory block shared across processes. TODO: finalizer that closes...
7
+ class SharedMemory < FFI::Pointer
8
+ include WithSelf
9
+
10
+ attr_reader :size, :fd
11
+
12
+ def self.open(size, &block)
13
+ new(size).with_self(&block)
14
+ end
15
+
16
+ def initialize(size)
17
+ @size = case size
18
+ when Symbol
19
+ FFI.type_size(size)
20
+ else
21
+ size
22
+ end
23
+
24
+ name = "/ps-shm#{rand(10000)}"
25
+ @fd = RT.shm_open(name,
26
+ LibC::O_CREAT | LibC::O_RDWR | LibC::O_EXCL,
27
+ 0777)
28
+ RT.shm_unlink(name)
29
+
30
+ LibC.ftruncate(@fd, @size)
31
+ @pointer = LibC.mmap(nil,
32
+ @size,
33
+ LibC::PROT_READ | LibC::PROT_WRITE,
34
+ LibC::MAP_SHARED,
35
+ @fd,
36
+ 0)
37
+ super(@pointer)
38
+ end
39
+
40
+ def close
41
+ LibC.munmap(@pointer, @size)
42
+ LibC.close(@fd)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ module ProcessShared
2
+ # API-compatible with Ruby Thread class but using ProcessShared
3
+ # primitives instead (i.e. each Thread will be a separate OS
4
+ # process).
5
+ class Process
6
+ class << self
7
+ # How the heck will I implement this...
8
+ def abort_on_exception
9
+ end
10
+
11
+ def abort_on_exception=(val)
12
+ end
13
+
14
+ # This can't really work since each thread is a separate process..
15
+ def current
16
+ end
17
+
18
+ def kill(process)
19
+ ::Process.kill(process.pid)
20
+ end
21
+ end
22
+
23
+ def join(limit = nil)
24
+ if limit
25
+ else
26
+ ::Process.wait(pid)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ module ProcessShared
2
+ module WithSelf
3
+ # With no associated block, return self. If the optional code
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
7
+ # the block is returned.
8
+ def with_self
9
+ if block_given?
10
+ begin
11
+ yield self
12
+ ensure
13
+ self.close
14
+ end
15
+ else
16
+ self
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,300 @@
1
+ require 'ffi'
2
+
3
+ module LibC
4
+ extend FFI::Library
5
+
6
+ ffi_lib FFI::Library::LIBC
7
+
8
+ attach_variable :errno, :int
9
+
10
+ attach_function :mmap, [:pointer, :size_t, :int, :int, :int, :off_t], :pointer
11
+ attach_function :munmap, [:pointer, :size_t], :int
12
+
13
+ attach_function :ftruncate, [:int, :off_t], :int
14
+
15
+ class << self
16
+ def call(err_msg = 'error in system call', &block)
17
+ ret = yield
18
+ err = LibC.errno
19
+
20
+ if ret.kind_of?(Fixnum) and ret < 0
21
+ raise SystemCallError.new(err_msg, err)
22
+ end
23
+
24
+ ret
25
+ end
26
+ end
27
+ end
28
+
29
+ module RT
30
+ extend FFI::Library
31
+
32
+ ffi_lib 'rt'
33
+
34
+ attach_function :shm_open, [:string, :int, :mode_t], :int
35
+ attach_function :shm_unlink, [:string], :int
36
+ end
37
+
38
+ module PSem
39
+ extend FFI::Library
40
+
41
+ lib = File.join(File.expand_path(File.dirname(__FILE__)),
42
+ 'process_shared/libpsem.' + FFI::Platform::LIBSUFFIX)
43
+ ffi_lib lib
44
+
45
+ attach_function :psem_alloc, [], :pointer
46
+ attach_function :psem_free, [:pointer], :void
47
+
48
+ attach_function :psem_open, [:pointer, :string, :uint, :uint], :int
49
+ attach_function :psem_close, [:pointer], :int
50
+ attach_function :psem_unlink, [:string], :int
51
+ attach_function :psem_post, [:pointer], :int
52
+ attach_function :psem_wait, [:pointer], :int
53
+ attach_function :psem_trywait, [:pointer], :int
54
+ attach_function :psem_timedwait, [:pointer, :pointer], :int
55
+ attach_function :psem_getvalue, [:pointer, :pointer], :int
56
+
57
+ attach_function :bsem_alloc, [], :pointer
58
+ attach_function :bsem_free, [:pointer], :void
59
+
60
+ attach_function :bsem_open, [:pointer, :string, :uint, :uint], :int
61
+ attach_function :bsem_close, [:pointer], :int
62
+ attach_function :bsem_unlink, [:string], :int
63
+ attach_function :bsem_post, [:pointer], :int
64
+ attach_function :bsem_wait, [:pointer], :int
65
+ attach_function :bsem_trywait, [:pointer], :int
66
+ attach_function :bsem_timedwait, [:pointer, :pointer], :int
67
+ attach_function :bsem_getvalue, [:pointer, :pointer], :int
68
+
69
+ class << self
70
+ include PSem
71
+
72
+ def test
73
+ bsem = bsem_alloc()
74
+
75
+ class << bsem
76
+ def value
77
+ @int ||= FFI::MemoryPointer.new(:int)
78
+ LibC.call { PSem.bsem_getvalue(self, @int) }
79
+ @int.get_int(0)
80
+ end
81
+ end
82
+
83
+ puts "alloc'ed at #{bsem.inspect}"
84
+ puts LibC.call { bsem_open(bsem, "foobar", 1, 1) }
85
+ puts "opened at #{bsem.inspect}"
86
+ puts LibC.call { bsem_unlink("foobar") }
87
+ puts "unlinked"
88
+
89
+ puts "waiting for sem..."
90
+ puts "value is #{bsem.value}"
91
+ LibC.call { bsem_wait(bsem) }
92
+ puts "acquired!"
93
+ puts "value is #{bsem.value}"
94
+ LibC.call { bsem_post(bsem) }
95
+ puts "posted!"
96
+ puts "value is #{bsem.value}"
97
+
98
+ puts LibC.call { bsem_close(bsem) }
99
+ bsem_free(bsem)
100
+ end
101
+ end
102
+ end
103
+
104
+ module PThread
105
+ extend FFI::Library
106
+
107
+ ffi_lib '/lib/x86_64-linux-gnu/libpthread-2.13.so' # 'pthread'
108
+
109
+ attach_function :pthread_mutex_init, [:pointer, :pointer], :int
110
+ attach_function :pthread_mutex_lock, [:pointer], :int
111
+ attach_function :pthread_mutex_trylock, [:pointer], :int
112
+ attach_function :pthread_mutex_unlock, [:pointer], :int
113
+ attach_function :pthread_mutex_destroy, [:pointer], :int
114
+
115
+ attach_function :pthread_mutexattr_init, [:pointer], :int
116
+ attach_function :pthread_mutexattr_settype, [:pointer, :int], :int
117
+ attach_function :pthread_mutexattr_gettype, [:pointer, :pointer], :int
118
+
119
+ attach_function :pthread_mutexattr_setpshared, [:pointer, :int], :int
120
+
121
+ class << self
122
+ def call(err_msg = 'error in pthreads', &block)
123
+ ret = yield
124
+ raise SystemCallError.new(err_msg, ret) unless ret == 0
125
+ end
126
+ end
127
+
128
+ module Helper
129
+ extend FFI::Library
130
+
131
+ # FIXME: this might not alwasy be ".so"
132
+ lib = File.join(File.expand_path(File.dirname(__FILE__)), 'pthread_sync_helper.so')
133
+ ffi_lib lib
134
+
135
+ attach_variable :sizeof_pthread_mutex_t, :size_t
136
+ attach_variable :sizeof_pthread_mutexattr_t, :size_t
137
+
138
+ attach_variable :o_rdwr, :int
139
+ attach_variable :o_creat, :int
140
+
141
+ [:pthread_process_shared,
142
+
143
+ :o_rdwr,
144
+ :o_creat,
145
+
146
+ :prot_read,
147
+ :prot_write,
148
+ :prot_exec,
149
+ :prot_none,
150
+
151
+ :map_shared,
152
+ :map_private].each do |sym|
153
+ attach_variable sym, :int
154
+ end
155
+
156
+ attach_variable :map_failed, :pointer
157
+
158
+ PTHREAD_PROCESS_SHARED = pthread_process_shared
159
+
160
+ O_RDWR = o_rdwr
161
+ O_CREAT = o_creat
162
+
163
+ PROT_READ = prot_read
164
+ PROT_WRITE = prot_write
165
+ PROT_EXEC = prot_exec
166
+ PROT_NONE = prot_none
167
+
168
+ MAP_FAILED = map_failed
169
+ MAP_SHARED = map_shared
170
+ MAP_PRIVATE = map_private
171
+ end
172
+
173
+ class Mutex
174
+ include PThread
175
+ include PThread::Helper
176
+
177
+ class << self
178
+ def alloc
179
+ FFI::MemoryPointer.new(Helper.sizeof_pthread_mutex_t)
180
+ end
181
+ end
182
+
183
+ def initialize(mutex = Mutex.alloc, attr = nil)
184
+ @mutex = mutex
185
+ PThread.call { pthread_mutex_init(@mutex, attr) }
186
+ end
187
+
188
+ def destroy
189
+ PThread.call { pthread_mutex_destroy(@mutex) }
190
+ end
191
+
192
+ def lock
193
+ PThread.call { pthread_mutex_lock(@mutex) }
194
+ end
195
+
196
+ def try_lock
197
+ PThread.call { pthread_mutex_trylock(@mutex) }
198
+ end
199
+
200
+ def unlock
201
+ PThread.call { pthread_mutex_unlock(@mutex) }
202
+ end
203
+ end
204
+
205
+ class MutexAttr
206
+ include PThread
207
+ include PThread::Helper
208
+
209
+ class << self
210
+ def alloc
211
+ FFI::MemoryPointer.new(Helper.sizeof_pthread_mutexattr_t)
212
+ end
213
+ end
214
+
215
+ def initialize(ptr = MutexAttr.alloc)
216
+ puts "have #{ptr}"
217
+ @ptr = ptr
218
+ PThread.call { pthread_mutexattr_init(@ptr) }
219
+ self.type = type if type
220
+ end
221
+
222
+ def pointer
223
+ @ptr
224
+ end
225
+
226
+ def pshared=(val)
227
+ PThread.call { pthread_mutexattr_setpshared(@ptr, val) }
228
+ end
229
+
230
+ def type=(type)
231
+ PThread.call { pthread_mutexattr_settype(@ptr, type) }
232
+ end
233
+
234
+ def type
235
+ t = FFI::MemoryPointer.new(:int)
236
+ PThread.call { pthread_mutexattr_gettype(@ptr, t) }
237
+ t.get_int(0)
238
+ end
239
+ end
240
+
241
+ class Int < FFI::Struct
242
+ layout :val => :int
243
+ end
244
+
245
+ class << self
246
+ include Helper
247
+
248
+ def test
249
+ puts "hi #{Helper.sizeof_pthread_mutex_t}"
250
+ puts Mutex.new
251
+
252
+
253
+ fd = LibC.call { RT.shm_open("/foo", O_CREAT | O_RDWR, 0777) }
254
+ LibC.call { LibC.ftruncate(fd, 100) }
255
+ pointer = LibC.mmap(nil, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)
256
+ puts pointer
257
+ puts pointer == MAP_FAILED
258
+ puts MAP_FAILED
259
+
260
+ attr = MutexAttr.new
261
+ attr.pshared = PTHREAD_PROCESS_SHARED
262
+
263
+ mutex = Mutex.new(pointer, attr.pointer)
264
+
265
+
266
+ fd = LibC.call { RT.shm_open("/someint", O_CREAT | O_RDWR, 0777) }
267
+ LibC.call { LibC.ftruncate(fd, 100) }
268
+ pointer = LibC.mmap(nil, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)
269
+ abort "failed" if pointer == MAP_FAILED
270
+
271
+ value = Int.new(pointer)
272
+ value[:val] = 0
273
+ puts "int[0]: #{value[:val]}"
274
+
275
+ puts "parent has mutex #{mutex}"
276
+
277
+ n = 10000
278
+
279
+ child = fork do
280
+ puts "child and I have mutex: #{mutex}"
281
+
282
+ n.times do |i|
283
+ mutex.lock
284
+ value[:val] = (value[:val] + 1)
285
+ mutex.unlock
286
+ end
287
+ end
288
+
289
+ n.times do |i|
290
+ mutex.lock
291
+ value[:val] = (value[:val] + 1)
292
+ mutex.unlock
293
+ end
294
+
295
+ Process.wait(child)
296
+
297
+ puts "value is now #{value[:val]}"
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'process_shared/bounded_semaphore'
3
+
4
+ module ProcessShared
5
+ describe BoundedSemaphore do
6
+ it 'never rises above its max value' do
7
+ max = 10
8
+ BoundedSemaphore.open(max) do |sem|
9
+ pids = []
10
+ 10.times do |i|
11
+ pids << fork do
12
+ 100.times do
13
+ if rand(3) == 0
14
+ sem.wait
15
+ else
16
+ sem.post
17
+ end
18
+ end
19
+
20
+ exit i
21
+ end
22
+ end
23
+
24
+ 100.times do
25
+ sem.value.must be_lte(max)
26
+ end
27
+
28
+ pids.each { |pid| Process.wait(pid) }
29
+ end
30
+ end
31
+
32
+ describe '#post and #wait' do
33
+ it 'increments and decrements the value' do
34
+ Semaphore.open(0) do |sem|
35
+ 10.times do |i|
36
+ sem.post
37
+ sem.value.must_equal(i + 1)
38
+ end
39
+
40
+ 10.times do |i|
41
+ sem.wait
42
+ sem.value.must_equal(10 - i - 1)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,9 @@
1
+ require 'process_shared/libc'
2
+
3
+ module ProcessShared
4
+ describe LibC do
5
+ it 'throws exceptions with invalid args' do
6
+ proc { LibC.mmap nil,2,0,0,1,0 }.must_raise(Errno::EINVAL)
7
+ end
8
+ end
9
+ end