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.
Files changed (58) hide show
  1. data/ext/helper/extconf.rb +14 -0
  2. data/ext/helper/helper.c +70 -0
  3. data/lib/mach.rb +12 -0
  4. data/lib/mach/clock.rb +24 -0
  5. data/lib/mach/error.rb +56 -0
  6. data/lib/mach/functions.rb +342 -0
  7. data/lib/mach/host.rb +28 -0
  8. data/lib/mach/port.rb +143 -0
  9. data/lib/mach/semaphore.rb +74 -0
  10. data/lib/mach/task.rb +42 -0
  11. data/lib/mach/time_spec.rb +16 -0
  12. data/lib/process_shared.rb +17 -1
  13. data/lib/process_shared/binary_semaphore.rb +18 -7
  14. data/lib/process_shared/mach.rb +93 -0
  15. data/lib/process_shared/mach/semaphore.rb +39 -0
  16. data/lib/process_shared/posix/errno.rb +40 -0
  17. data/lib/process_shared/posix/libc.rb +78 -0
  18. data/lib/process_shared/posix/semaphore.rb +125 -0
  19. data/lib/process_shared/posix/shared_array.rb +71 -0
  20. data/lib/process_shared/posix/shared_memory.rb +82 -0
  21. data/lib/process_shared/posix/time_spec.rb +13 -0
  22. data/lib/process_shared/posix/time_val.rb +23 -0
  23. data/lib/process_shared/semaphore.rb +26 -73
  24. data/lib/process_shared/shared_array.rb +3 -1
  25. data/lib/process_shared/shared_memory.rb +10 -52
  26. data/lib/process_shared/time_spec.rb +22 -0
  27. data/spec/mach/port_spec.rb +21 -0
  28. data/spec/mach/scratch.rb +67 -0
  29. data/spec/mach/scratch2.rb +78 -0
  30. data/spec/mach/semaphore_spec.rb +60 -0
  31. data/spec/mach/task_spec.rb +31 -0
  32. data/spec/process_shared/scratch.rb +21 -0
  33. data/spec/process_shared/semaphore_spec.rb +12 -11
  34. data/spec/process_shared/shared_memory_spec.rb +1 -1
  35. metadata +46 -36
  36. data/ext/libpsem/bsem.c +0 -188
  37. data/ext/libpsem/bsem.h +0 -32
  38. data/ext/libpsem/constants.c +0 -22
  39. data/ext/libpsem/constants.h +0 -18
  40. data/ext/libpsem/extconf.rb +0 -40
  41. data/ext/libpsem/mempcpy.c +0 -7
  42. data/ext/libpsem/mempcpy.h +0 -13
  43. data/ext/libpsem/mutex.c +0 -15
  44. data/ext/libpsem/mutex.h +0 -14
  45. data/ext/libpsem/psem.c +0 -15
  46. data/ext/libpsem/psem.h +0 -45
  47. data/ext/libpsem/psem_error.c +0 -46
  48. data/ext/libpsem/psem_error.h +0 -11
  49. data/ext/libpsem/psem_posix.c +0 -160
  50. data/ext/libpsem/psem_posix.h +0 -10
  51. data/lib/process_shared/bounded_semaphore.rb +0 -46
  52. data/lib/process_shared/libc.rb +0 -36
  53. data/lib/process_shared/libpsem.bundle +0 -0
  54. data/lib/process_shared/libpsem.so +0 -0
  55. data/lib/process_shared/psem.rb +0 -113
  56. data/spec/process_shared/bounded_semaphore_spec.rb +0 -48
  57. data/spec/process_shared/libc_spec.rb +0 -9
  58. 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
@@ -0,0 +1,13 @@
1
+ require 'ffi'
2
+ require 'process_shared/time_spec'
3
+
4
+ module ProcessShared
5
+ module Posix
6
+ class TimeSpec < FFI::Struct
7
+ include ProcessShared::TimeSpec
8
+
9
+ layout(:tv_sec, :time_t,
10
+ :tv_nsec, :long)
11
+ end
12
+ end
13
+ end