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,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/psem'
2
- require 'process_shared/abstract_semaphore'
1
+ require 'process_shared/with_self'
3
2
 
4
3
  module ProcessShared
5
- class Semaphore < AbstractSemaphore
6
- # With no associated block, open is a synonym for
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
- # Create a new semaphore with initial value +value+. After
19
- # Kernel#fork, the semaphore will be shared across two (or more)
20
- # processes. The semaphore must be closed with #close in each
21
- # process that no longer needs the semaphore.
22
- #
23
- # (An object finalizer is registered that will close the semaphore
24
- # to avoid memory leaks, but this should be considered a last
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
- # Decrement the value of the semaphore. If the value is zero,
36
- # wait until another process increments via {#post}.
37
- def wait
38
- psem_wait(sem, err)
39
- end
40
-
41
- # Decrement the value of the semaphore if it can be done
42
- # immediately (i.e. if it was non-zero). Otherwise, wait up to
43
- # +timeout+ seconds until another process increments via {#post}.
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
- # Increment the value of the semaphore. If other processes are
60
- # waiting on this semaphore, one will be woken.
61
- def post
62
- psem_post(sem, err)
63
- end
64
-
65
- # Get the current value of the semaphore. Raises {Errno::NOTSUP} on
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,5 +1,7 @@
1
+ require 'process_shared'
2
+
1
3
  module ProcessShared
2
- class SharedArray < SharedMemory
4
+ class SharedArray < SharedMemory.impl
3
5
  include Enumerable
4
6
 
5
7
  # A fixed-size array in shared memory. Processes forked from this
@@ -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
- class SharedMemory < FFI::Pointer
9
- include WithSelf
6
+ module SharedMemory
7
+ include ProcessShared::WithSelf
10
8
 
11
- attr_reader :size, :type, :type_size, :count, :fd
9
+ class << self
10
+ attr_accessor :impl
12
11
 
13
- def self.open(size, &block)
14
- new(size).with_self(&block)
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
- name = "/ps-shm#{rand(10000)}"
37
- @fd = RT.shm_open(name,
38
- LibC::O_CREAT | LibC::O_RDWR | LibC::O_EXCL,
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
- def close
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