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