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
data/lib/mach/host.rb
ADDED
@@ -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
|
+
|
data/lib/mach/port.rb
ADDED
@@ -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
|
data/lib/mach/task.rb
ADDED
@@ -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
|
+
|
data/lib/process_shared.rb
CHANGED
@@ -19,7 +19,23 @@ if RUBY_VERSION =~ /^1.8/
|
|
19
19
|
end
|
20
20
|
|
21
21
|
require 'process_shared/semaphore'
|
22
|
-
require 'process_shared/
|
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 '
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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)
|