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.
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