process_shared 0.0.4 → 0.1.0
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/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
|