process_shared 0.1.7 → 0.1.8
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/ext/pthread_sync_helper/extconf.rb +9 -0
- data/ext/pthread_sync_helper/pthread_sync_helper.c +43 -0
- data/ext/semaphore.c +623 -0
- data/lib/mach/functions.rb +6 -4
- data/lib/process_shared/posix/semaphore.rb +6 -3
- data/lib/process_shared/posix/shared_array.rb +71 -0
- data/lib/process_shared/thread.rb +30 -0
- data/lib/scratch.rb +300 -0
- data/spec/mach/scratch.rb +67 -0
- data/spec/mach/scratch2.rb +78 -0
- data/spec/process_shared/lock_behavior.rb +0 -1
- data/spec/process_shared/semaphore_spec.rb +59 -0
- metadata +46 -37
data/lib/mach/functions.rb
CHANGED
@@ -72,8 +72,8 @@ module Mach
|
|
72
72
|
|
73
73
|
# Attach a function as with +attach_function+, but check the
|
74
74
|
# return value and raise an exception on errors.
|
75
|
-
def self.attach_mach_function(sym, argtypes, rettype)
|
76
|
-
attach_function(sym, argtypes, rettype)
|
75
|
+
def self.attach_mach_function(sym, argtypes, rettype, options = nil)
|
76
|
+
attach_function(sym, argtypes, rettype, options)
|
77
77
|
error_check(sym)
|
78
78
|
end
|
79
79
|
|
@@ -208,10 +208,12 @@ module Mach
|
|
208
208
|
:kern_return_t)
|
209
209
|
attach_mach_function(:semaphore_wait,
|
210
210
|
[:semaphore_t],
|
211
|
-
:kern_return_t
|
211
|
+
:kern_return_t,
|
212
|
+
:blocking => true)
|
212
213
|
attach_mach_function(:semaphore_timedwait,
|
213
214
|
[:semaphore_t, TimeSpec.val],
|
214
|
-
:kern_return_t
|
215
|
+
:kern_return_t,
|
216
|
+
:blocking => true)
|
215
217
|
|
216
218
|
end
|
217
219
|
end
|
@@ -26,9 +26,12 @@ module ProcessShared
|
|
26
26
|
|
27
27
|
attach_function :sem_getvalue, [:sem_p, :pointer], :int
|
28
28
|
attach_function :sem_post, [:sem_p], :int
|
29
|
-
attach_function :sem_wait, [:sem_p], :int
|
30
|
-
attach_function :sem_trywait, [:sem_p], :int
|
31
|
-
|
29
|
+
attach_function :sem_wait, [:sem_p], :int, :blocking => true
|
30
|
+
attach_function :sem_trywait, [:sem_p], :int, :blocking => true
|
31
|
+
|
32
|
+
# Workaround bug which only appears to affect Ruby 1.8.7 and REE
|
33
|
+
BLOCKING_SEM_TIMEDWAIT = (RUBY_VERSION != '1.8.7')
|
34
|
+
attach_function :sem_timedwait, [:sem_p, TimeSpec], :int, :blocking => BLOCKING_SEM_TIMEDWAIT
|
32
35
|
|
33
36
|
error_check(:sem_close, :sem_unlink, :sem_init, :sem_destroy,
|
34
37
|
:sem_getvalue, :sem_post, :sem_wait, :sem_trywait,
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ProcessShared
|
2
|
+
module Posix
|
3
|
+
class SharedArray < SharedMemory
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
# A fixed-size array in shared memory. Processes forked from this
|
7
|
+
# one will be able to read and write shared data to the array.
|
8
|
+
# Access should be synchronized using a {Mutex}, {Semaphore}, or
|
9
|
+
# other means.
|
10
|
+
#
|
11
|
+
# Note that {Enumerable} methods such as {#map}, {#sort},
|
12
|
+
# etc. return new {Array} objects rather than modifying the shared
|
13
|
+
# array.
|
14
|
+
#
|
15
|
+
# @param [Symbol] type_or_count the data type as a symbol
|
16
|
+
# understood by FFI (e.g. :int, :double)
|
17
|
+
#
|
18
|
+
# @param [Integer] count number of array elements
|
19
|
+
def initialize(type_or_count = 1, count = 1)
|
20
|
+
super(type_or_count, count)
|
21
|
+
|
22
|
+
# See https://github.com/ffi/ffi/issues/118
|
23
|
+
ffi_type = FFI.find_type(self.type)
|
24
|
+
|
25
|
+
name = if ffi_type.inspect =~ /FFI::Type::Builtin:(\w+)*/
|
26
|
+
# name will be something like int32
|
27
|
+
$1.downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
unless name
|
31
|
+
raise ArgumentError, "could not find FFI::Type for #{self.type}"
|
32
|
+
end
|
33
|
+
|
34
|
+
getter = "get_#{name}"
|
35
|
+
setter = "put_#{name}"
|
36
|
+
|
37
|
+
# singleton class
|
38
|
+
sclass = class << self; self; end
|
39
|
+
|
40
|
+
unless sclass.method_defined?(getter)
|
41
|
+
raise ArgumentError, "no element getter for #{self.type} (#{getter})"
|
42
|
+
end
|
43
|
+
|
44
|
+
unless sclass.method_defined?(setter)
|
45
|
+
raise ArgumentError, "no element setter for #{self.type} (#{setter})"
|
46
|
+
end
|
47
|
+
|
48
|
+
sclass.send(:alias_method, :get_type, getter)
|
49
|
+
sclass.send(:alias_method, :put_type, setter)
|
50
|
+
end
|
51
|
+
|
52
|
+
def each
|
53
|
+
# NOTE: using @count because Enumerable defines its own count
|
54
|
+
# method...
|
55
|
+
@count.times { |i| yield self[i] }
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_with_index
|
59
|
+
@count.times { |i| yield self[i], i }
|
60
|
+
end
|
61
|
+
|
62
|
+
def [](i)
|
63
|
+
get_type(i * self.type_size)
|
64
|
+
end
|
65
|
+
|
66
|
+
def []=(i, val)
|
67
|
+
put_type(i * self.type_size, val)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
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
|
data/lib/scratch.rb
ADDED
@@ -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,67 @@
|
|
1
|
+
require 'mach'
|
2
|
+
require 'mach/functions'
|
3
|
+
|
4
|
+
include Mach
|
5
|
+
include Mach::Functions
|
6
|
+
|
7
|
+
sem = Semaphore.new(:value => 0)
|
8
|
+
|
9
|
+
# puts ">parent has sem #{sem.port}"
|
10
|
+
|
11
|
+
# fork do
|
12
|
+
# sleep 2 # make parent wait a bit
|
13
|
+
# puts "in child..."
|
14
|
+
# sem = Mach::Semaphore.new(:port => Mach::Task.self.get_bootstrap_port)
|
15
|
+
# puts "child signaling sem #{sem.port}"
|
16
|
+
# sem.signal
|
17
|
+
# end
|
18
|
+
|
19
|
+
# puts ">parent waiting on sem..."
|
20
|
+
# sem.wait
|
21
|
+
# puts ">parent done waiting!"
|
22
|
+
|
23
|
+
def struct(*layout)
|
24
|
+
yield FFI::Struct.new(nil, *layout)
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "sizeof MsgPortDescriptor: #{MsgPortDescriptor.size}"
|
28
|
+
port = Port.new
|
29
|
+
port.insert_right(:make_send)
|
30
|
+
|
31
|
+
Task.self.set_bootstrap_port(port)
|
32
|
+
puts "> self:#{mach_task_self} bootstrap:#{port.port} (#{Mach::Functions::bootstrap_port.to_i})"
|
33
|
+
|
34
|
+
child = fork do
|
35
|
+
parent_port = Task.self.get_bootstrap_port
|
36
|
+
puts "in child... self:#{mach_task_self} bootstrap:#{parent_port.port}"
|
37
|
+
|
38
|
+
#port = Port.new
|
39
|
+
#port.copy_send(parent_port)
|
40
|
+
|
41
|
+
#sem = port.receive_right
|
42
|
+
|
43
|
+
Task.self.copy_send(parent_port)
|
44
|
+
puts "child sleeping"
|
45
|
+
sleep 2
|
46
|
+
puts "child signaling semaphore"
|
47
|
+
sem.signal
|
48
|
+
puts "child out"
|
49
|
+
sleep 2
|
50
|
+
end
|
51
|
+
|
52
|
+
sleep 0.1
|
53
|
+
if Process.wait(child, Process::WNOHANG)
|
54
|
+
puts "child died!"
|
55
|
+
#exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
Task.self.set_bootstrap_port(Mach::Functions::bootstrap_port)
|
59
|
+
child_task_port = port.receive_right
|
60
|
+
puts "parent: child task port is #{child_task_port}"
|
61
|
+
|
62
|
+
sem.insert_right(:copy_send, :ipc_space => child_task_port)
|
63
|
+
|
64
|
+
puts "parent waiting"
|
65
|
+
sem.wait
|
66
|
+
puts "parent done waiting!"
|
67
|
+
|