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,28 @@
1
+ require 'mach/functions'
2
+ require 'mach/port'
3
+ require 'mach/clock'
4
+
5
+ module Mach
6
+ class Host < Port
7
+ include Functions
8
+
9
+ # @return [Task]
10
+ def self.self
11
+ new(Functions.mach_host_self)
12
+ end
13
+
14
+ def initialize(host)
15
+ super(:port => host)
16
+ end
17
+
18
+ alias_method :host, :port
19
+
20
+ def get_clock_service
21
+ mem = new_memory_pointer(:clock_id_t)
22
+ host_get_clock_service(host, 0, mem)
23
+ clock_id = Port.new(:port => mem.read_int)
24
+ Clock.new clock_id
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,143 @@
1
+ require 'ffi'
2
+ require 'mach/functions'
3
+
4
+ module Mach
5
+ # Michael Weber's "Some Fun with Mach Ports" was an indispensable
6
+ # resource in learning the Mach ports API.
7
+ #
8
+ # @see http://www.foldr.org/~michaelw/log/computers/macosx/task-info-fun-with-mach
9
+ class Port
10
+ include Functions
11
+
12
+ class SendRightMsg < FFI::Struct
13
+ include Functions
14
+
15
+ layout(:header, MsgHeader,
16
+ :body, MsgBody,
17
+ :port, MsgPortDescriptor)
18
+ end
19
+
20
+ class ReceiveRightMsg < FFI::Struct
21
+ include Functions
22
+
23
+ layout(:header, MsgHeader,
24
+ :body, MsgBody,
25
+ :port, MsgPortDescriptor,
26
+ :trailer, MsgTrailer)
27
+ end
28
+
29
+ attr_reader :ipc_space, :port
30
+
31
+ # @param [Hash] opts
32
+ #
33
+ # @option opts [Integer] :ipc_space defaults to +mach_task_self+
34
+ #
35
+ # @option opts [MachPortRight] :right defaults to +:receive+
36
+ #
37
+ # @option opts [Port, Integer] :port if given, the existing port
38
+ # is wrapped in a new Port object; otherwise a new port is
39
+ # allocated according to the other options
40
+ def initialize(opts = {})
41
+ @ipc_space = opts[:ipc_space] || mach_task_self
42
+ right = opts[:right] || :receive
43
+
44
+ @port = if opts[:port]
45
+ opts[:port].to_i
46
+ else
47
+ mem = new_memory_pointer(:mach_port_right_t)
48
+ mach_port_allocate(@ipc_space.to_i, right, mem)
49
+ mem.get_uint(0)
50
+ end
51
+ end
52
+
53
+ # With this alias, we can call #to_i on either bare Integer ports
54
+ # or wrapped Port objects when passing the arg to a foreign
55
+ # function.
56
+ alias_method :to_i, :port
57
+
58
+ def to_s
59
+ "#<#{self.class} #{to_i}>"
60
+ end
61
+
62
+ def ==(other)
63
+ (port == other.port) && (ipc_space == other.ipc_space)
64
+ end
65
+
66
+ def destroy(opts = {})
67
+ ipc_space = opts[:ipc_space] || @ipc_space
68
+ mach_port_destroy(ipc_space, @port)
69
+ end
70
+
71
+ def deallocate(opts = {})
72
+ ipc_space = opts[:ipc_space] || @ipc_space
73
+ mach_port_deallocate(ipc_space.to_i, @port)
74
+ end
75
+
76
+ # Insert +right+ into another ipc space. The current task must
77
+ # have sufficient rights to insert the requested right.
78
+ #
79
+ # @param [MachMsgType] right
80
+ #
81
+ # @param [Hash] opts
82
+ #
83
+ # @option opts [Port,Integer] :ipc_space the space (task port) into which
84
+ # +right+ will be inserted; defaults to this port's ipc_space
85
+ #
86
+ # @option opts [Port,Integer] :port the name the port right should
87
+ # have in :ipc_space; defaults to the same name as this port
88
+ def insert_right(right, opts = {})
89
+ ipc_space = opts[:ipc_space] || @ipc_space
90
+ port_name = opts[:port_name] || @port
91
+
92
+ mach_port_insert_right(ipc_space.to_i, port_name.to_i, @port, right)
93
+ end
94
+
95
+ # Send +right+ on this Port to +remote_port+. The current task
96
+ # must already have the requisite rights allowing it to send
97
+ # +right+.
98
+ def send_right(right, remote_port)
99
+ msg = SendRightMsg.new
100
+
101
+ msg[:header].tap do |h|
102
+ h[:remote_port] = remote_port.to_i
103
+ h[:local_port] = MACH_PORT_NULL
104
+ h[:bits] =
105
+ (MachMsgType[right] | (0 << 8)) | 0x80000000 # MACH_MSGH_BITS_COMPLEX
106
+ h[:size] = 40 # msg.size
107
+ end
108
+
109
+ msg[:body][:descriptor_count] = 1
110
+
111
+ msg[:port].tap do |p|
112
+ p[:name] = port
113
+ p[:disposition] = MachMsgType[right]
114
+ p[:type] = 0 # MACH_MSG_PORT_DESCRIPTOR;
115
+ end
116
+
117
+ mach_msg_send msg
118
+ end
119
+
120
+ # Copy the send right on this port and send it in a message to
121
+ # +remote_port+. The current task must have an existing send
122
+ # right on this Port.
123
+ def copy_send(remote_port)
124
+ send_right(:copy_send, remote_port)
125
+ end
126
+
127
+ # Create a new Port by receiving a port right message on this
128
+ # port.
129
+ def receive_right
130
+ msg = ReceiveRightMsg.new
131
+
132
+ mach_msg(msg,
133
+ 2, # MACH_RCV_MSG,
134
+ 0,
135
+ msg.size,
136
+ port,
137
+ MACH_MSG_TIMEOUT_NONE,
138
+ MACH_PORT_NULL)
139
+
140
+ self.class.new :port => msg[:port][:name]
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,74 @@
1
+ require 'mach/functions'
2
+ require 'mach/port'
3
+ require 'mach/host'
4
+ require 'mach/clock'
5
+
6
+ module Mach
7
+ class Semaphore < Port
8
+ include Functions
9
+
10
+ # Create a new Semaphore.
11
+ #
12
+ # @param [Hash] opts
13
+ #
14
+ # @option opts [Integer] :value the initial value of the
15
+ # semaphore; defaults to 1
16
+ #
17
+ # @option opts [Integer] :task the Mach task that owns the
18
+ # semaphore (defaults to Mach.task_self)
19
+ #
20
+ # @options opts [Integer] :sync_policy the sync policy for this
21
+ # semaphore (defaults to SyncPolicy::FIFO)
22
+ #
23
+ # @options opts [Integer] :port existing port to wrap with a
24
+ # Semaphore object; otherwise a new semaphore is created
25
+ #
26
+ # @return [Integer] a semaphore port name
27
+ def initialize(opts = {})
28
+ value = opts[:value] || 1
29
+ task = (opts[:task] && opts[:task].to_i) || ipc_space || mach_task_self
30
+ sync_policy = opts[:sync_policy] || :fifo
31
+
32
+ port = if opts[:port]
33
+ opts[:port].to_i
34
+ else
35
+ mem = new_memory_pointer(:semaphore_t)
36
+ semaphore_create(task, mem, sync_policy, value)
37
+ mem.get_uint(0)
38
+ end
39
+
40
+ super(:port => port, :ipc_space => task)
41
+ end
42
+
43
+ # Destroy a Semaphore.
44
+ #
45
+ # @param [Hash] opts
46
+ #
47
+ # @option opts [Integer] :task the Mach task that owns the
48
+ # semaphore (defaults to the owning task)
49
+ def destroy(opts = {})
50
+ task = opts[:task] || ipc_space || mach_task_self
51
+ semaphore_destroy(task.to_i, port)
52
+ end
53
+
54
+ def signal
55
+ semaphore_signal(port)
56
+ end
57
+
58
+ def signal_all
59
+ semaphore_signal_all(port)
60
+ end
61
+
62
+ def wait
63
+ semaphore_wait(port)
64
+ end
65
+
66
+ # @see http://pkaudio.blogspot.com/2010/05/mac-os-x-no-timed-semaphore-waits.html
67
+ def timedwait(secs)
68
+ timespec = TimeSpec.new
69
+ timespec.add_seconds!(secs)
70
+
71
+ semaphore_timedwait(port, timespec)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,42 @@
1
+ require 'mach/functions'
2
+ require 'mach/port'
3
+
4
+ module Mach
5
+ class Task < Port
6
+ include Functions
7
+
8
+ # @return [Task]
9
+ def self.self
10
+ new(Functions.mach_task_self)
11
+ end
12
+
13
+ def initialize(task)
14
+ super(:port => task)
15
+ end
16
+
17
+ alias_method :task, :port
18
+
19
+ # @param [MachSpecialPort] which_port
20
+ def get_special_port(which_port)
21
+ mem = new_memory_pointer(:mach_port_t)
22
+ task_get_special_port(task, which_port, mem)
23
+ Port.new(:port => mem.get_uint(0))
24
+ end
25
+
26
+ # @param [MachSpecialPort] which_port
27
+ #
28
+ # @param [Port,Integer] newport
29
+ def set_special_port(which_port, newport)
30
+ task_set_special_port(task, which_port, newport.to_i)
31
+ end
32
+
33
+ def get_bootstrap_port
34
+ get_special_port(:bootstrap)
35
+ end
36
+
37
+ def set_bootstrap_port(port)
38
+ set_special_port(:bootstrap, port)
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,16 @@
1
+ require 'ffi'
2
+
3
+ module Mach
4
+ class TimeSpec < FFI::Struct
5
+ layout(:tv_sec, :uint,
6
+ :tv_nsec, :int) # clock_res_t
7
+
8
+ def to_s
9
+ "#<%s tv_sec=%d tv_nsec=%d>" % [self.class,
10
+ self[:tv_sec],
11
+ self[:tv_nsec]]
12
+
13
+ end
14
+ end
15
+ end
16
+
@@ -19,7 +19,23 @@ if RUBY_VERSION =~ /^1.8/
19
19
  end
20
20
 
21
21
  require 'process_shared/semaphore'
22
- require 'process_shared/bounded_semaphore'
22
+ require 'process_shared/binary_semaphore'
23
23
  require 'process_shared/mutex'
24
24
  require 'process_shared/shared_memory'
25
25
 
26
+ module ProcessShared
27
+ case FFI::Platform::OS
28
+ when 'linux'
29
+ require 'process_shared/posix/shared_memory'
30
+ require 'process_shared/posix/semaphore'
31
+
32
+ SharedMemory.impl = Posix::SharedMemory
33
+ Semaphore.impl = Posix::Semaphore
34
+ when 'darwin'
35
+ require 'process_shared/posix/shared_memory'
36
+ require 'process_shared/mach/semaphore'
37
+
38
+ SharedMemory.impl = Posix::SharedMemory
39
+ Semaphore.impl = Mach::Semaphore
40
+ end
41
+ end
@@ -1,4 +1,7 @@
1
- require 'process_shared/psem'
1
+ require 'forwardable'
2
+
3
+ require 'process_shared'
4
+ require 'process_shared/with_self'
2
5
  require 'process_shared/semaphore'
3
6
  require 'process_shared/process_error'
4
7
 
@@ -9,7 +12,16 @@ module ProcessShared
9
12
  # exception.
10
13
  #
11
14
  # This is identical to a Semaphore but with extra error checking.
12
- class BinarySemaphore < Semaphore
15
+ class BinarySemaphore
16
+ extend Forwardable
17
+ include ProcessShared::WithSelf
18
+
19
+ def_delegators :@sem, :wait, :try_wait, :synchronize, :value, :close
20
+
21
+ def self.open(value = 1, &block)
22
+ new(value).with_self(&block)
23
+ end
24
+
13
25
  # Create a new semaphore with initial value +value+. After
14
26
  # {Kernel#fork}, the semaphore will be shared across two (or more)
15
27
  # processes. The semaphore must be closed with {#close} in each
@@ -20,10 +32,9 @@ module ProcessShared
20
32
  # resort).
21
33
  #
22
34
  # @param [Integer] value the initial semaphore value
23
- # @param [String] name not currently supported
24
- def initialize(value = 1, name = nil)
35
+ def initialize(value = 1)
25
36
  raise ArgumentErrror 'value must be 0 or 1' if (value < 0 or value > 1)
26
- super(value, name)
37
+ @sem = Semaphore.new(value)
27
38
  end
28
39
 
29
40
  # Increment from zero to one.
@@ -37,11 +48,11 @@ module ProcessShared
37
48
  begin
38
49
  try_wait
39
50
  # oops, value was not zero...
40
- psem_post(sem, err)
51
+ @sem.post
41
52
  raise ProcessError, 'post would raise value over bound'
42
53
  rescue Errno::EAGAIN
43
54
  # ok, value was zero
44
- psem_post(sem, err)
55
+ @sem.post
45
56
  end
46
57
  end
47
58
  end
@@ -0,0 +1,93 @@
1
+ require 'set'
2
+ require 'mach'
3
+
4
+ module ProcessShared
5
+ module Mach
6
+ include ::Mach
7
+
8
+ # The set of ports that should be shared to forked child
9
+ # processes.
10
+ #
11
+ # FIXME: protect with (original ruby) mutex?
12
+ def self.shared_ports
13
+ @shared_ports ||= Set.new
14
+ end
15
+
16
+ def self.after_fork_child
17
+ parent_port = Task.self.get_bootstrap_port
18
+
19
+ # give parent permission to send to child's task port
20
+ Task.self.copy_send(parent_port)
21
+
22
+ # create a second port and give the parent permission to send
23
+ port = Port.new
24
+ port.insert_right(:make_send)
25
+ port.copy_send(parent_port)
26
+
27
+ # parent copies sem, mutex port permissions directly to child
28
+ # task port
29
+
30
+ # wait for parent to send orig bootstrap port
31
+ orig_bootstrap = port.receive_right
32
+ Task.self.set_special_port(:bootstrap, orig_bootstrap)
33
+ end
34
+
35
+ def self.after_fork_parent(port)
36
+ child_task_port = port.receive_right
37
+ shared_ports.each do |p|
38
+ p.insert_right(:copy_send, :ipc_space => child_task_port)
39
+ end
40
+
41
+ child_port = port.receive_right
42
+ ::Mach::bootstrap_port.copy_send(child_port)
43
+ end
44
+ end
45
+ end
46
+
47
+ module Kernel
48
+ # Override to call Process::fork.
49
+ def fork(*args, &block)
50
+ Process.fork(*args, &block)
51
+ end
52
+ end
53
+
54
+ module Process
55
+ class << self
56
+ unless respond_to? :__mach_original_fork__
57
+ alias_method :__mach_original_fork__, :fork
58
+ end
59
+
60
+ # Override to first copy all shared ports (semaphores, etc.) from
61
+ # parent process to child process.
62
+ def fork
63
+ # make a port for receiving message from child
64
+ port = Mach::Port.new
65
+ port.insert_right(:make_send)
66
+ Mach::Task.self.set_bootstrap_port(port)
67
+
68
+ if block_given?
69
+ pid = __mach_original_fork__ do
70
+ ProcessShared::Mach.after_fork_child
71
+ yield
72
+ end
73
+
74
+ ProcessShared::Mach.after_fork_parent(port)
75
+ pid
76
+ else
77
+ if pid = __mach_original_fork__
78
+ ProcessShared::Mach.after_fork_parent(port)
79
+ pid
80
+ else
81
+ ProcessShared::Mach.after_fork_child
82
+ nil
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ require 'mach/time_spec'
90
+ require 'process_shared/time_spec'
91
+
92
+ # Monkey patch to add #add_seconds! method
93
+ Mach::TimeSpec.send(:include, ProcessShared::TimeSpec)