process_shared 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ext/helper/extconf.rb +14 -0
- data/ext/helper/helper.c +70 -0
- data/lib/mach.rb +12 -0
- data/lib/mach/clock.rb +24 -0
- data/lib/mach/error.rb +56 -0
- data/lib/mach/functions.rb +342 -0
- data/lib/mach/host.rb +28 -0
- data/lib/mach/port.rb +143 -0
- data/lib/mach/semaphore.rb +74 -0
- data/lib/mach/task.rb +42 -0
- data/lib/mach/time_spec.rb +16 -0
- data/lib/process_shared.rb +17 -1
- data/lib/process_shared/binary_semaphore.rb +18 -7
- data/lib/process_shared/mach.rb +93 -0
- data/lib/process_shared/mach/semaphore.rb +39 -0
- data/lib/process_shared/posix/errno.rb +40 -0
- data/lib/process_shared/posix/libc.rb +78 -0
- data/lib/process_shared/posix/semaphore.rb +125 -0
- data/lib/process_shared/posix/shared_array.rb +71 -0
- data/lib/process_shared/posix/shared_memory.rb +82 -0
- data/lib/process_shared/posix/time_spec.rb +13 -0
- data/lib/process_shared/posix/time_val.rb +23 -0
- data/lib/process_shared/semaphore.rb +26 -73
- data/lib/process_shared/shared_array.rb +3 -1
- data/lib/process_shared/shared_memory.rb +10 -52
- data/lib/process_shared/time_spec.rb +22 -0
- data/spec/mach/port_spec.rb +21 -0
- data/spec/mach/scratch.rb +67 -0
- data/spec/mach/scratch2.rb +78 -0
- data/spec/mach/semaphore_spec.rb +60 -0
- data/spec/mach/task_spec.rb +31 -0
- data/spec/process_shared/scratch.rb +21 -0
- data/spec/process_shared/semaphore_spec.rb +12 -11
- data/spec/process_shared/shared_memory_spec.rb +1 -1
- metadata +46 -36
- data/ext/libpsem/bsem.c +0 -188
- data/ext/libpsem/bsem.h +0 -32
- data/ext/libpsem/constants.c +0 -22
- data/ext/libpsem/constants.h +0 -18
- data/ext/libpsem/extconf.rb +0 -40
- data/ext/libpsem/mempcpy.c +0 -7
- data/ext/libpsem/mempcpy.h +0 -13
- data/ext/libpsem/mutex.c +0 -15
- data/ext/libpsem/mutex.h +0 -14
- data/ext/libpsem/psem.c +0 -15
- data/ext/libpsem/psem.h +0 -45
- data/ext/libpsem/psem_error.c +0 -46
- data/ext/libpsem/psem_error.h +0 -11
- data/ext/libpsem/psem_posix.c +0 -160
- data/ext/libpsem/psem_posix.h +0 -10
- data/lib/process_shared/bounded_semaphore.rb +0 -46
- data/lib/process_shared/libc.rb +0 -36
- data/lib/process_shared/libpsem.bundle +0 -0
- data/lib/process_shared/libpsem.so +0 -0
- data/lib/process_shared/psem.rb +0 -113
- data/spec/process_shared/bounded_semaphore_spec.rb +0 -48
- data/spec/process_shared/libc_spec.rb +0 -9
- data/spec/process_shared/psem_spec.rb +0 -136
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'mach'
|
2
|
+
require 'mach/error'
|
3
|
+
|
4
|
+
require 'process_shared/mach'
|
5
|
+
|
6
|
+
module ProcessShared
|
7
|
+
module Mach
|
8
|
+
# Extends ::Mach::Semaphore to be compatible with ProcessShared::Semaphore
|
9
|
+
class Semaphore < ::Mach::Semaphore
|
10
|
+
include ProcessShared::Semaphore
|
11
|
+
|
12
|
+
def initialize(value = 1)
|
13
|
+
super(:value => value)
|
14
|
+
ProcessShared::Mach.shared_ports.add self
|
15
|
+
end
|
16
|
+
|
17
|
+
def try_wait(timeout = nil)
|
18
|
+
secs = timeout ? timeout : 0
|
19
|
+
begin
|
20
|
+
# TODO catch and convert exceptions...
|
21
|
+
timedwait(secs)
|
22
|
+
rescue Mach::Error::OPERATION_TIMED_OUT => e
|
23
|
+
klass = secs == 0 ? Errno::EAGAIN : Errno::ETIMEDOUT
|
24
|
+
raise klass, e.message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_method :post, :signal
|
29
|
+
|
30
|
+
def value
|
31
|
+
raise Errno::ENOTSUP
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
# TODO
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module ProcessShared
|
4
|
+
module Posix
|
5
|
+
module Errno
|
6
|
+
extend FFI::Library
|
7
|
+
|
8
|
+
ffi_lib FFI::Library::LIBC
|
9
|
+
|
10
|
+
attach_variable :errno, :int
|
11
|
+
|
12
|
+
# Replace methods in +syms+ with error checking wrappers that
|
13
|
+
# invoke the original method and raise a {SystemCallError} with
|
14
|
+
# the current errno if the return value is an error.
|
15
|
+
#
|
16
|
+
# Errors are detected if the block returns true when called with
|
17
|
+
# the original method's return value.
|
18
|
+
def error_check(*syms, &is_err)
|
19
|
+
unless block_given?
|
20
|
+
is_err = lambda { |v| (v == -1) }
|
21
|
+
end
|
22
|
+
|
23
|
+
syms.each do |sym|
|
24
|
+
method = self.method(sym)
|
25
|
+
new_method_body = proc do |*args|
|
26
|
+
ret = method.call(*args)
|
27
|
+
if is_err.call(ret)
|
28
|
+
raise SystemCallError.new("error in #{sym}", Errno.errno)
|
29
|
+
else
|
30
|
+
ret
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
define_singleton_method(sym, &new_method_body)
|
35
|
+
define_method(sym, &new_method_body)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
require 'process_shared/posix/errno'
|
4
|
+
require 'process_shared/posix/time_val'
|
5
|
+
|
6
|
+
module ProcessShared
|
7
|
+
module Posix
|
8
|
+
module LibC
|
9
|
+
module Helper
|
10
|
+
extend FFI::Library
|
11
|
+
|
12
|
+
# Workaround FFI dylib/bundle issue. See https://github.com/ffi/ffi/issues/42
|
13
|
+
suffix = if FFI::Platform.mac?
|
14
|
+
'bundle'
|
15
|
+
else
|
16
|
+
FFI::Platform::LIBSUFFIX
|
17
|
+
end
|
18
|
+
|
19
|
+
ffi_lib File.join(File.expand_path(File.dirname(__FILE__)),
|
20
|
+
'helper.' + suffix)
|
21
|
+
|
22
|
+
[:o_rdwr,
|
23
|
+
:o_creat,
|
24
|
+
:o_excl,
|
25
|
+
|
26
|
+
:prot_read,
|
27
|
+
:prot_write,
|
28
|
+
:prot_exec,
|
29
|
+
:prot_none,
|
30
|
+
|
31
|
+
:map_shared,
|
32
|
+
:map_private].each do |sym|
|
33
|
+
attach_variable sym, :int
|
34
|
+
end
|
35
|
+
|
36
|
+
[:sizeof_sem_t].each do |sym|
|
37
|
+
attach_variable sym, :size_t
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
extend FFI::Library
|
42
|
+
extend Errno
|
43
|
+
|
44
|
+
ffi_lib FFI::Library::LIBC
|
45
|
+
|
46
|
+
MAP_FAILED = FFI::Pointer.new(-1)
|
47
|
+
MAP_SHARED = Helper.map_shared
|
48
|
+
MAP_PRIVATE = Helper.map_private
|
49
|
+
|
50
|
+
PROT_READ = Helper.prot_read
|
51
|
+
PROT_WRITE = Helper.prot_write
|
52
|
+
PROT_EXEC = Helper.prot_exec
|
53
|
+
PROT_NONE = Helper.prot_none
|
54
|
+
|
55
|
+
O_RDWR = Helper.o_rdwr
|
56
|
+
O_CREAT = Helper.o_creat
|
57
|
+
O_EXCL = Helper.o_excl
|
58
|
+
|
59
|
+
def self.type_size(type)
|
60
|
+
case type
|
61
|
+
when :sem_t
|
62
|
+
Helper.sizeof_sem_t
|
63
|
+
else
|
64
|
+
FFI.type_size(type)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
attach_function :mmap, [:pointer, :size_t, :int, :int, :int, :off_t], :pointer
|
69
|
+
attach_function :munmap, [:pointer, :size_t], :int
|
70
|
+
attach_function :ftruncate, [:int, :off_t], :int
|
71
|
+
attach_function :close, [:int], :int
|
72
|
+
attach_function :gettimeofday, [TimeVal, :pointer], :int
|
73
|
+
|
74
|
+
error_check(:mmap) { |v| v == MAP_FAILED }
|
75
|
+
error_check(:munmap, :ftruncate, :close, :gettimeofday)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'process_shared/semaphore'
|
2
|
+
|
3
|
+
require 'process_shared/posix/errno'
|
4
|
+
require 'process_shared/posix/libc'
|
5
|
+
require 'process_shared/posix/time_val'
|
6
|
+
require 'process_shared/posix/time_spec'
|
7
|
+
|
8
|
+
module ProcessShared
|
9
|
+
module Posix
|
10
|
+
class Semaphore
|
11
|
+
module Foreign
|
12
|
+
extend FFI::Library
|
13
|
+
extend Errno
|
14
|
+
|
15
|
+
ffi_lib 'rt' # 'pthread'
|
16
|
+
|
17
|
+
typedef :pointer, :sem_p
|
18
|
+
|
19
|
+
attach_function :sem_open, [:string, :int], :sem_p
|
20
|
+
attach_function :sem_close, [:sem_p], :int
|
21
|
+
attach_function :sem_unlink, [:string], :int
|
22
|
+
|
23
|
+
attach_function :sem_init, [:sem_p, :int, :uint], :int
|
24
|
+
attach_function :sem_destroy, [:sem_p], :int
|
25
|
+
|
26
|
+
attach_function :sem_getvalue, [:sem_p, :pointer], :int
|
27
|
+
attach_function :sem_post, [:sem_p], :int
|
28
|
+
attach_function :sem_wait, [:sem_p], :int
|
29
|
+
attach_function :sem_trywait, [:sem_p], :int
|
30
|
+
attach_function :sem_timedwait, [:sem_p, TimeSpec], :int
|
31
|
+
|
32
|
+
error_check(:sem_close, :sem_unlink, :sem_init, :sem_destroy,
|
33
|
+
:sem_getvalue, :sem_post, :sem_wait, :sem_trywait,
|
34
|
+
:sem_timedwait)
|
35
|
+
end
|
36
|
+
|
37
|
+
include Foreign
|
38
|
+
include ProcessShared::Semaphore
|
39
|
+
|
40
|
+
# Make a Proc suitable for use as a finalizer that will call
|
41
|
+
# +shm_unlink+ on +sem+.
|
42
|
+
#
|
43
|
+
# @return [Proc] a finalizer
|
44
|
+
def self.make_finalizer(sem)
|
45
|
+
proc { LibC.shm_unlink(sem) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a new semaphore with initial value +value+. After
|
49
|
+
# Kernel#fork, the semaphore will be shared across two (or more)
|
50
|
+
# processes. The semaphore must be closed with #close in each
|
51
|
+
# process that no longer needs the semaphore.
|
52
|
+
#
|
53
|
+
# (An object finalizer is registered that will close the semaphore
|
54
|
+
# to avoid memory leaks, but this should be considered a last
|
55
|
+
# resort).
|
56
|
+
#
|
57
|
+
# @param [Integer] value the initial semaphore value
|
58
|
+
def initialize(value = 1)
|
59
|
+
@sem = SharedMemory.new(LibC.type_size(:sem_t))
|
60
|
+
sem_init(@sem, 1, value)
|
61
|
+
ObjectSpace.define_finalizer(self, self.class.make_finalizer(@sem))
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get the current value of the semaphore. Raises {Errno::NOTSUP} on
|
65
|
+
# platforms that don't support this (e.g. Mac OS X).
|
66
|
+
#
|
67
|
+
# @return [Integer] the current value of the semaphore.
|
68
|
+
def value
|
69
|
+
int = FFI::MemoryPointer.new(:int)
|
70
|
+
sem_getvalue(@sem, int)
|
71
|
+
int.read_int
|
72
|
+
end
|
73
|
+
|
74
|
+
# Increment the value of the semaphore. If other processes are
|
75
|
+
# waiting on this semaphore, one will be woken.
|
76
|
+
def post
|
77
|
+
sem_post(@sem)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Decrement the value of the semaphore. If the value is zero,
|
81
|
+
# wait until another process increments via {#post}.
|
82
|
+
def wait
|
83
|
+
sem_wait(@sem)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Decrement the value of the semaphore if it can be done
|
87
|
+
# immediately (i.e. if it was non-zero). Otherwise, wait up to
|
88
|
+
# +timeout+ seconds until another process increments via {#post}.
|
89
|
+
#
|
90
|
+
# @param timeout [Numeric] the maximum seconds to wait, or nil to not wait
|
91
|
+
#
|
92
|
+
# @return If +timeout+ is nil and the semaphore cannot be
|
93
|
+
# decremented immediately, raise Errno::EAGAIN. If +timeout+
|
94
|
+
# passed before the semaphore could be decremented, raise
|
95
|
+
# Errno::ETIMEDOUT.
|
96
|
+
def try_wait(timeout = nil)
|
97
|
+
if timeout
|
98
|
+
now = TimeVal.new
|
99
|
+
LibC.gettimeofday(now, nil)
|
100
|
+
abs_timeout = now.to_time_spec
|
101
|
+
abs_timeout.add_seconds!(timeout)
|
102
|
+
sem_timedwait(@sem, abs_timeout)
|
103
|
+
else
|
104
|
+
sem_trywait(@sem)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Close the shared memory block holding the semaphore.
|
109
|
+
#
|
110
|
+
# FIXME: May leak the semaphore memory on some platforms,
|
111
|
+
# according to the Linux man page for sem_destroy(3). (Should not
|
112
|
+
# be destroyed as it may be in use by other processes.)
|
113
|
+
def close
|
114
|
+
# sem_destroy(@sem)
|
115
|
+
|
116
|
+
# Not entirely sure what to do here. sem_destroy() goes with
|
117
|
+
# sem_init() (unnamed semaphroe), but other processes cannot use
|
118
|
+
# a destroyed semaphore.
|
119
|
+
@sem.close
|
120
|
+
@sem = nil
|
121
|
+
ObjectSpace.undefine_finalizer(self)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -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,82 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
require 'process_shared/posix/errno'
|
4
|
+
require 'process_shared/posix/libc'
|
5
|
+
require 'process_shared/shared_memory'
|
6
|
+
|
7
|
+
module ProcessShared
|
8
|
+
module Posix
|
9
|
+
# Memory block shared across processes.
|
10
|
+
class SharedMemory < FFI::Pointer
|
11
|
+
module Foreign
|
12
|
+
extend FFI::Library
|
13
|
+
extend Errno
|
14
|
+
|
15
|
+
# FIXME: mac and linux OK, but what about everything else?
|
16
|
+
if FFI::Platform.mac?
|
17
|
+
ffi_lib 'c'
|
18
|
+
else
|
19
|
+
ffi_lib 'rt'
|
20
|
+
end
|
21
|
+
|
22
|
+
attach_function :shm_open, [:string, :int, :mode_t], :int
|
23
|
+
attach_function :shm_unlink, [:string], :int
|
24
|
+
|
25
|
+
error_check :shm_open, :shm_unlink
|
26
|
+
end
|
27
|
+
|
28
|
+
include SharedMemory::Foreign
|
29
|
+
include LibC
|
30
|
+
|
31
|
+
include ProcessShared::SharedMemory
|
32
|
+
|
33
|
+
attr_reader :size, :type, :type_size, :count, :fd
|
34
|
+
|
35
|
+
def self.make_finalizer(addr, size, fd)
|
36
|
+
proc do
|
37
|
+
pointer = FFI::Pointer.new(addr)
|
38
|
+
LibC.munmap(pointer, size)
|
39
|
+
LibC.close(fd)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(type_or_count = 1, count = 1)
|
44
|
+
@type, @count = case type_or_count
|
45
|
+
when Symbol
|
46
|
+
[type_or_count, count]
|
47
|
+
else
|
48
|
+
[:uchar, type_or_count]
|
49
|
+
end
|
50
|
+
|
51
|
+
@type_size = FFI.type_size(@type)
|
52
|
+
@size = @type_size * @count
|
53
|
+
|
54
|
+
name = "/ps-shm#{rand(10000)}"
|
55
|
+
@fd = shm_open(name,
|
56
|
+
O_CREAT | O_RDWR | O_EXCL,
|
57
|
+
0777)
|
58
|
+
shm_unlink(name)
|
59
|
+
|
60
|
+
ftruncate(@fd, @size)
|
61
|
+
@pointer = mmap(nil,
|
62
|
+
@size,
|
63
|
+
LibC::PROT_READ | LibC::PROT_WRITE,
|
64
|
+
LibC::MAP_SHARED,
|
65
|
+
@fd,
|
66
|
+
0).
|
67
|
+
slice(0, size) # slice to get FFI::Pointer that knows its size
|
68
|
+
# (and thus does bounds checking)
|
69
|
+
|
70
|
+
@finalize = self.class.make_finalizer(@pointer.address, @size, @fd)
|
71
|
+
ObjectSpace.define_finalizer(self, @finalize)
|
72
|
+
|
73
|
+
super(@pointer)
|
74
|
+
end
|
75
|
+
|
76
|
+
def close
|
77
|
+
ObjectSpace.undefine_finalizer(self)
|
78
|
+
@finalize.call
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|