process_shared 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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