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
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
require 'process_shared/posix/time_spec'
|
4
|
+
|
5
|
+
module ProcessShared
|
6
|
+
module Posix
|
7
|
+
class TimeVal < FFI::Struct
|
8
|
+
US_PER_NS = 1000
|
9
|
+
|
10
|
+
layout(:tv_sec, :time_t,
|
11
|
+
:tv_usec, :suseconds_t)
|
12
|
+
|
13
|
+
def to_time_spec
|
14
|
+
ts = TimeSpec.new
|
15
|
+
|
16
|
+
ts[:tv_sec] = self[:tv_sec];
|
17
|
+
ts[:tv_nsec] = self[:tv_usec] * US_PER_NS
|
18
|
+
|
19
|
+
ts
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,84 +1,37 @@
|
|
1
|
-
require 'process_shared/
|
2
|
-
require 'process_shared/abstract_semaphore'
|
1
|
+
require 'process_shared/with_self'
|
3
2
|
|
4
3
|
module ProcessShared
|
5
|
-
|
6
|
-
|
7
|
-
# Semaphore.new. If the optional code block is given, it will be
|
8
|
-
# passed +sem+ as an argument, and the Semaphore object will
|
9
|
-
# automatically be closed when the block terminates. In this
|
10
|
-
# instance, Semaphore.open returns the value of the block.
|
11
|
-
#
|
12
|
-
# @param [Integer] value the initial semaphore value
|
13
|
-
# @param [String] name not currently supported
|
14
|
-
def self.open(value = 1, name = nil, &block)
|
15
|
-
new(value, name).with_self(&block)
|
16
|
-
end
|
4
|
+
module Semaphore
|
5
|
+
include ProcessShared::WithSelf
|
17
6
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# resort).
|
26
|
-
#
|
27
|
-
# @param [Integer] value the initial semaphore value
|
28
|
-
# @param [String] name not currently supported
|
29
|
-
def initialize(value = 1, name = nil)
|
30
|
-
init(PSem.sizeof_psem_t, 'psem', name) do |sem_name|
|
31
|
-
psem_open(sem, sem_name, value, err)
|
7
|
+
class << self
|
8
|
+
# the implementation to use to create semaphores. impl is set
|
9
|
+
# based on the platform in 'process_shared'
|
10
|
+
attr_accessor :impl
|
11
|
+
|
12
|
+
def new(*args)
|
13
|
+
impl.new(*args)
|
32
14
|
end
|
33
|
-
end
|
34
15
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
#
|
45
|
-
# @param timeout [Numeric] the maximum seconds to wait, or nil to not wait
|
46
|
-
#
|
47
|
-
# @return If +timeout+ is nil and the semaphore cannot be
|
48
|
-
# decremented immediately, raise Errno::EAGAIN. If +timeout+
|
49
|
-
# passed before the semaphore could be decremented, raise
|
50
|
-
# Errno::ETIMEDOUT.
|
51
|
-
def try_wait(timeout = nil)
|
52
|
-
if timeout
|
53
|
-
psem_timedwait(sem, timeout, err)
|
54
|
-
else
|
55
|
-
psem_trywait(sem, err)
|
16
|
+
# With no associated block, open is a synonym for
|
17
|
+
# Semaphore.new. If the optional code block is given, it will be
|
18
|
+
# passed +sem+ as an argument, and the Semaphore object will
|
19
|
+
# automatically be closed when the block terminates. In this
|
20
|
+
# instance, Semaphore.open returns the value of the block.
|
21
|
+
#
|
22
|
+
# @param [Integer] value the initial semaphore value
|
23
|
+
def open(value = 1, &block)
|
24
|
+
new(value).with_self(&block)
|
56
25
|
end
|
57
26
|
end
|
58
27
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
# platforms that don't support this (e.g. Mac OS X).
|
67
|
-
#
|
68
|
-
# @return [Integer] the current value of the semaphore.
|
69
|
-
def value
|
70
|
-
int = FFI::MemoryPointer.new(:int)
|
71
|
-
psem_getvalue(sem, int, err)
|
72
|
-
int.get_int(0)
|
73
|
-
end
|
74
|
-
|
75
|
-
# Release the resources associated with this semaphore. Calls to
|
76
|
-
# other methods are undefined after {#close} has been called.
|
77
|
-
#
|
78
|
-
# Close must be called when the semaphore is no longer needed. An
|
79
|
-
# object finalizer will close the semaphore as a last resort.
|
80
|
-
def close
|
81
|
-
psem_close(sem, err)
|
28
|
+
def synchronize
|
29
|
+
wait
|
30
|
+
begin
|
31
|
+
yield
|
32
|
+
ensure
|
33
|
+
post
|
34
|
+
end
|
82
35
|
end
|
83
36
|
end
|
84
37
|
end
|
@@ -1,58 +1,21 @@
|
|
1
|
-
require 'process_shared/rt'
|
2
|
-
require 'process_shared/libc'
|
3
1
|
require 'process_shared/with_self'
|
4
2
|
require 'process_shared/shared_memory_io'
|
5
3
|
|
6
4
|
module ProcessShared
|
7
5
|
# Memory block shared across processes.
|
8
|
-
|
9
|
-
include WithSelf
|
6
|
+
module SharedMemory
|
7
|
+
include ProcessShared::WithSelf
|
10
8
|
|
11
|
-
|
9
|
+
class << self
|
10
|
+
attr_accessor :impl
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.make_finalizer(addr, size, fd)
|
18
|
-
proc do
|
19
|
-
pointer = FFI::Pointer.new(addr)
|
20
|
-
LibC.munmap(pointer, size)
|
21
|
-
LibC.close(fd)
|
12
|
+
def new(*args)
|
13
|
+
impl.new(*args)
|
22
14
|
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize(type_or_count = 1, count = 1)
|
26
|
-
@type, @count = case type_or_count
|
27
|
-
when Symbol
|
28
|
-
[type_or_count, count]
|
29
|
-
else
|
30
|
-
[:uchar, type_or_count]
|
31
|
-
end
|
32
|
-
|
33
|
-
@type_size = FFI.type_size(@type)
|
34
|
-
@size = @type_size * @count
|
35
15
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
0777)
|
40
|
-
RT.shm_unlink(name)
|
41
|
-
|
42
|
-
LibC.ftruncate(@fd, @size)
|
43
|
-
@pointer = LibC.mmap(nil,
|
44
|
-
@size,
|
45
|
-
LibC::PROT_READ | LibC::PROT_WRITE,
|
46
|
-
LibC::MAP_SHARED,
|
47
|
-
@fd,
|
48
|
-
0).
|
49
|
-
slice(0, size) # slice to get FFI::Pointer that knows its size
|
50
|
-
# (and thus does bounds checking)
|
51
|
-
|
52
|
-
@finalize = self.class.make_finalizer(@pointer.address, @size, @fd)
|
53
|
-
ObjectSpace.define_finalizer(self, @finalize)
|
54
|
-
|
55
|
-
super(@pointer)
|
16
|
+
def open(size, &block)
|
17
|
+
new(size).with_self(&block)
|
18
|
+
end
|
56
19
|
end
|
57
20
|
|
58
21
|
# Write the serialization of +obj+ (using Marshal.dump) to this
|
@@ -99,12 +62,7 @@ module ProcessShared
|
|
99
62
|
Marshal.load(to_shm_io)
|
100
63
|
end
|
101
64
|
|
102
|
-
|
103
|
-
ObjectSpace.undefine_finalizer(self)
|
104
|
-
@finalize.call
|
105
|
-
end
|
106
|
-
|
107
|
-
private
|
65
|
+
protected
|
108
66
|
|
109
67
|
def to_shm_io
|
110
68
|
SharedMemoryIO.new(self)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ProcessShared
|
2
|
+
module TimeSpec
|
3
|
+
NS_PER_S = 1e9
|
4
|
+
US_PER_NS = 1000
|
5
|
+
TV_NSEC_MAX = (NS_PER_S - 1)
|
6
|
+
|
7
|
+
# Assuming self responds to setting the value of [:tv_sec] and
|
8
|
+
# [:tv_nsec], add +secs+ to the time spec.
|
9
|
+
def add_seconds!(float_sec)
|
10
|
+
# add timeout in seconds to abs_timeout; careful with rounding
|
11
|
+
sec = float_sec.floor
|
12
|
+
nsec = ((float_sec - sec) * NS_PER_S).floor
|
13
|
+
|
14
|
+
self[:tv_sec] += sec
|
15
|
+
self[:tv_nsec] += nsec
|
16
|
+
while self[:tv_nsec] > TV_NSEC_MAX
|
17
|
+
self[:tv_sec] += 1
|
18
|
+
self[:tv_nsec] -= NS_PER_S
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mach/port'
|
3
|
+
|
4
|
+
module Mach
|
5
|
+
describe Port do
|
6
|
+
it 'creates a port' do
|
7
|
+
port = Port.new
|
8
|
+
port.destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'raises exception with invalid args' do
|
12
|
+
p = proc { Port.new(:right => 1234) }
|
13
|
+
p.must_raise Error::FAILURE
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'inserts rights' do
|
17
|
+
port = Port.new
|
18
|
+
port.insert_right(:make_send)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'mach'
|
2
|
+
require 'mach/functions'
|
3
|
+
|
4
|
+
include Mach
|
5
|
+
include Mach::Functions
|
6
|
+
|
7
|
+
sem = Semaphore.new(:value => 0)
|
8
|
+
|
9
|
+
# puts ">parent has sem #{sem.port}"
|
10
|
+
|
11
|
+
# fork do
|
12
|
+
# sleep 2 # make parent wait a bit
|
13
|
+
# puts "in child..."
|
14
|
+
# sem = Mach::Semaphore.new(:port => Mach::Task.self.get_bootstrap_port)
|
15
|
+
# puts "child signaling sem #{sem.port}"
|
16
|
+
# sem.signal
|
17
|
+
# end
|
18
|
+
|
19
|
+
# puts ">parent waiting on sem..."
|
20
|
+
# sem.wait
|
21
|
+
# puts ">parent done waiting!"
|
22
|
+
|
23
|
+
def struct(*layout)
|
24
|
+
yield FFI::Struct.new(nil, *layout)
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "sizeof MsgPortDescriptor: #{MsgPortDescriptor.size}"
|
28
|
+
port = Port.new
|
29
|
+
port.insert_right(:make_send)
|
30
|
+
|
31
|
+
Task.self.set_bootstrap_port(port)
|
32
|
+
puts "> self:#{mach_task_self} bootstrap:#{port.port} (#{Mach::Functions::bootstrap_port.to_i})"
|
33
|
+
|
34
|
+
child = fork do
|
35
|
+
parent_port = Task.self.get_bootstrap_port
|
36
|
+
puts "in child... self:#{mach_task_self} bootstrap:#{parent_port.port}"
|
37
|
+
|
38
|
+
#port = Port.new
|
39
|
+
#port.copy_send(parent_port)
|
40
|
+
|
41
|
+
#sem = port.receive_right
|
42
|
+
|
43
|
+
Task.self.copy_send(parent_port)
|
44
|
+
puts "child sleeping"
|
45
|
+
sleep 2
|
46
|
+
puts "child signaling semaphore"
|
47
|
+
sem.signal
|
48
|
+
puts "child out"
|
49
|
+
sleep 2
|
50
|
+
end
|
51
|
+
|
52
|
+
sleep 0.1
|
53
|
+
if Process.wait(child, Process::WNOHANG)
|
54
|
+
puts "child died!"
|
55
|
+
#exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
Task.self.set_bootstrap_port(Mach::Functions::bootstrap_port)
|
59
|
+
child_task_port = port.receive_right
|
60
|
+
puts "parent: child task port is #{child_task_port}"
|
61
|
+
|
62
|
+
sem.insert_right(:copy_send, :ipc_space => child_task_port)
|
63
|
+
|
64
|
+
puts "parent waiting"
|
65
|
+
sem.wait
|
66
|
+
puts "parent done waiting!"
|
67
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
require 'mach'
|
4
|
+
require 'mach/functions'
|
5
|
+
|
6
|
+
include Mach
|
7
|
+
include Mach::Functions
|
8
|
+
|
9
|
+
def setup_recv_port
|
10
|
+
port = new_memory_pointer :mach_port_t
|
11
|
+
mach_port_allocate(mach_task_self, :receive, port)
|
12
|
+
p = port.get_uint(0)
|
13
|
+
mach_port_insert_right(mach_task_self, p, p, :make_send)
|
14
|
+
p
|
15
|
+
end
|
16
|
+
|
17
|
+
def send_port(remote_port, port)
|
18
|
+
puts "send_port: (in #{mach_task_self}) sending #{port} -> #{remote_port}"
|
19
|
+
msg = FFI::Struct.new(nil,
|
20
|
+
:header, MsgHeader,
|
21
|
+
:body, MsgBody,
|
22
|
+
:task_port, MsgPortDescriptor)
|
23
|
+
msg[:header].tap do |h|
|
24
|
+
h[:remote_port] = remote_port
|
25
|
+
h[:local_port] = 0
|
26
|
+
h[:bits] = (MachMsgType[:copy_send] | (0 << 8)) | 0x80000000 # MACH_MSGH_BITS_COMPLEX
|
27
|
+
h[:size] = msg.size
|
28
|
+
end
|
29
|
+
|
30
|
+
msg[:body][:descriptor_count] = 1
|
31
|
+
|
32
|
+
msg[:task_port].tap do |p|
|
33
|
+
p[:name] = port
|
34
|
+
p[:disposition] = MachMsgType[:copy_send]
|
35
|
+
p[:type] = 0 # MACH_MSG_PORT_DESCRIPTOR;
|
36
|
+
end
|
37
|
+
|
38
|
+
mach_msg_send(msg)
|
39
|
+
end
|
40
|
+
|
41
|
+
def recv_port(recv_port)
|
42
|
+
msg = FFI::Struct.new(nil,
|
43
|
+
:header, MsgHeader,
|
44
|
+
:body, MsgBody,
|
45
|
+
:task_port, MsgPortDescriptor,
|
46
|
+
:trailer, MsgTrailer)
|
47
|
+
|
48
|
+
mach_msg(msg, 2, 0, msg.size, recv_port, 0, 0)
|
49
|
+
|
50
|
+
msg.size.times do |i|
|
51
|
+
print "%02x " % msg.to_ptr.get_uint8(i)
|
52
|
+
end
|
53
|
+
puts
|
54
|
+
|
55
|
+
msg[:task_port][:name]
|
56
|
+
end
|
57
|
+
|
58
|
+
def sampling_fork
|
59
|
+
parent_recv_port = setup_recv_port
|
60
|
+
task_set_special_port(mach_task_self, :bootstrap, parent_recv_port)
|
61
|
+
|
62
|
+
fork do
|
63
|
+
parent_recv_port_p = new_memory_pointer :mach_port_t
|
64
|
+
task_get_special_port(mach_task_self, :bootstrap, parent_recv_port_p)
|
65
|
+
parent_recv_port = parent_recv_port_p.get_uint(0)
|
66
|
+
puts "child self:#{mach_task_self} parent_recv:#{parent_recv_port}"
|
67
|
+
|
68
|
+
child_recv_port = setup_recv_port
|
69
|
+
puts "child sending #{mach_task_self}"
|
70
|
+
send_port(parent_recv_port, mach_task_self)
|
71
|
+
end
|
72
|
+
|
73
|
+
task_set_special_port(mach_task_self, :bootstrap, Mach::Functions::bootstrap_port)
|
74
|
+
child_task = recv_port(parent_recv_port)
|
75
|
+
puts "parent received #{child_task}"
|
76
|
+
end
|
77
|
+
|
78
|
+
sampling_fork
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mach'
|
3
|
+
|
4
|
+
module Mach
|
5
|
+
describe 'low level semaphore functions' do
|
6
|
+
include Functions
|
7
|
+
|
8
|
+
it 'raises exception with invalid args' do
|
9
|
+
p = proc { semaphore_create(mach_task_self, nil, 1234, 1) }
|
10
|
+
p.must_raise Error::INVALID_ARGUMENT
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Semaphore do
|
15
|
+
it 'creates a semaphore' do
|
16
|
+
sem = Semaphore.new
|
17
|
+
sem.destroy
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'raises exception with invalid args' do
|
21
|
+
p = proc { Semaphore.new(:sync_policy => :no_such) }
|
22
|
+
p.must_raise ArgumentError # Error::INVALID_ARGUMENT
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'signals/waits in same task' do
|
26
|
+
sem = Semaphore.new(:value => 0)
|
27
|
+
sem.signal
|
28
|
+
sem.wait
|
29
|
+
sem.destroy
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'coordinates access to shared resource between two tasks' do
|
33
|
+
sem = Semaphore.new(:value => 0)
|
34
|
+
|
35
|
+
port = Port.new
|
36
|
+
port.insert_right(:make_send)
|
37
|
+
Task.self.set_bootstrap_port(port)
|
38
|
+
|
39
|
+
child = fork do
|
40
|
+
parent_port = Task.self.get_bootstrap_port
|
41
|
+
Task.self.copy_send(parent_port)
|
42
|
+
# parent will copy send rights to sem into child task
|
43
|
+
sleep 0.5
|
44
|
+
sem.signal
|
45
|
+
Kernel.exit!
|
46
|
+
end
|
47
|
+
|
48
|
+
child_task_port = port.receive_right
|
49
|
+
|
50
|
+
start = Time.now.to_f
|
51
|
+
sem.insert_right(:copy_send, :ipc_space => child_task_port)
|
52
|
+
sem.wait
|
53
|
+
elapsed = Time.now.to_f - start
|
54
|
+
|
55
|
+
Process.wait child
|
56
|
+
|
57
|
+
elapsed.must be_gt(0.4)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|