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,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)