process_shared 0.0.1

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 (46) hide show
  1. data/COPYING +19 -0
  2. data/ChangeLog +0 -0
  3. data/README.rdoc +69 -0
  4. data/ext/libpsem/bsem.c +188 -0
  5. data/ext/libpsem/bsem.h +32 -0
  6. data/ext/libpsem/constants.c +22 -0
  7. data/ext/libpsem/constants.h +18 -0
  8. data/ext/libpsem/extconf.rb +36 -0
  9. data/ext/libpsem/mempcpy.c +7 -0
  10. data/ext/libpsem/mempcpy.h +13 -0
  11. data/ext/libpsem/mutex.c +15 -0
  12. data/ext/libpsem/mutex.h +14 -0
  13. data/ext/libpsem/psem.c +15 -0
  14. data/ext/libpsem/psem.h +43 -0
  15. data/ext/libpsem/psem_error.c +46 -0
  16. data/ext/libpsem/psem_error.h +11 -0
  17. data/ext/libpsem/psem_posix.c +130 -0
  18. data/ext/libpsem/psem_posix.h +10 -0
  19. data/ext/pthread_sync_helper/extconf.rb +9 -0
  20. data/ext/pthread_sync_helper/pthread_sync_helper.c +43 -0
  21. data/ext/semaphore.c +623 -0
  22. data/lib/process_shared.rb +6 -0
  23. data/lib/process_shared/abstract_semaphore.rb +50 -0
  24. data/lib/process_shared/bounded_semaphore.rb +43 -0
  25. data/lib/process_shared/condition_variable.rb +27 -0
  26. data/lib/process_shared/libc.rb +36 -0
  27. data/lib/process_shared/libpsem.bundle +0 -0
  28. data/lib/process_shared/libpsem.so +0 -0
  29. data/lib/process_shared/mutex.rb +103 -0
  30. data/lib/process_shared/posix_call.rb +29 -0
  31. data/lib/process_shared/process_error.rb +3 -0
  32. data/lib/process_shared/psem.rb +109 -0
  33. data/lib/process_shared/rt.rb +21 -0
  34. data/lib/process_shared/semaphore.rb +60 -0
  35. data/lib/process_shared/shared_memory.rb +45 -0
  36. data/lib/process_shared/thread.rb +30 -0
  37. data/lib/process_shared/with_self.rb +20 -0
  38. data/lib/scratch.rb +300 -0
  39. data/spec/process_shared/bounded_semaphore_spec.rb +48 -0
  40. data/spec/process_shared/libc_spec.rb +9 -0
  41. data/spec/process_shared/mutex_spec.rb +74 -0
  42. data/spec/process_shared/psem_spec.rb +136 -0
  43. data/spec/process_shared/semaphore_spec.rb +76 -0
  44. data/spec/process_shared/shared_memory_spec.rb +36 -0
  45. data/spec/spec_helper.rb +35 -0
  46. metadata +139 -0
@@ -0,0 +1,6 @@
1
+ require 'ffi'
2
+
3
+ require 'process_shared/semaphore'
4
+ require 'process_shared/bounded_semaphore'
5
+ require 'process_shared/mutex'
6
+ require 'process_shared/shared_memory'
@@ -0,0 +1,50 @@
1
+ require 'process_shared/psem'
2
+ require 'process_shared/with_self'
3
+
4
+ module ProcessShared
5
+ class AbstractSemaphore
6
+ include WithSelf
7
+ protected
8
+ include ProcessShared::PSem
9
+ public
10
+
11
+ # Generate a name for a semaphore.
12
+ def self.gen_name(middle, name = nil)
13
+ if name
14
+ name
15
+ else
16
+ @count ||= 0
17
+ @count += 1
18
+ "ps-#{middle}-#{Process.pid}-#{@count}"
19
+ end
20
+ end
21
+
22
+ def self.make_finalizer(name)
23
+ proc { ProcessShared::PSem.psem_unlink(name, nil) }
24
+ end
25
+
26
+ # private_class_method :new
27
+
28
+ protected
29
+
30
+ attr_reader :sem, :err
31
+
32
+ def init(size, middle, name, &block)
33
+ @sem = FFI::MemoryPointer.new(size)
34
+ @err = FFI::MemoryPointer.new(:pointer)
35
+ psem_name = AbstractSemaphore.gen_name(middle, name)
36
+ block.call(psem_name)
37
+
38
+ if name
39
+ # name explicitly given. Don't unlink because we might want to share it with another process.
40
+ # Instead, register a finalizer to unlink.
41
+ ObjectSpace.define_finalizer(self, self.class.make_finalizer(name))
42
+ else
43
+ # On Linux, removes the entry in /dev/shm and prevents other
44
+ # processes from opening this semaphore unless they inherit it
45
+ # as forked children.
46
+ psem_unlink(psem_name, err) unless name
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ require 'process_shared/psem'
2
+ require 'process_shared/semaphore'
3
+
4
+ module ProcessShared
5
+ class BoundedSemaphore < Semaphore
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(maxvalue, value = 1, name = nil, &block)
15
+ new(maxvalue, value, name).with_self(&block)
16
+ end
17
+
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(maxvalue, value = 1, name = nil)
30
+ init(PSem.sizeof_bsem_t, 'bsem', name) do |sem_name|
31
+ bsem_open(sem, sem_name, maxvalue, value, err)
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ alias_method :psem_unlink, :bsem_unlink
38
+ alias_method :psem_close, :bsem_close
39
+ alias_method :psem_wait, :bsem_wait
40
+ alias_method :psem_post, :bsem_post
41
+ alias_method :psem_getvalue, :bsem_getvalue
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ require 'process_shared/semaphore'
2
+
3
+ module ProcessShared
4
+ # TODO: implement this
5
+ class ConditionVariable
6
+ def initialize
7
+ @sem = Semaphore.new
8
+ end
9
+
10
+ def broadcast
11
+ @sem.post
12
+ end
13
+
14
+ def signal
15
+ @sem.post
16
+ end
17
+
18
+ def wait(mutex, timeout = nil)
19
+ mutex.unlock
20
+ begin
21
+ @sem.wait
22
+ ensure
23
+ mutex.lock
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ require 'ffi'
2
+
3
+ require 'process_shared/posix_call'
4
+ require 'process_shared/psem'
5
+
6
+ module ProcessShared
7
+ module LibC
8
+ extend FFI::Library
9
+ extend PosixCall
10
+
11
+ ffi_lib FFI::Library::LIBC
12
+
13
+ MAP_FAILED = FFI::Pointer.new(-1)
14
+ MAP_SHARED = PSem.map_shared
15
+ MAP_PRIVATE = PSem.map_private
16
+
17
+ PROT_READ = PSem.prot_read
18
+ PROT_WRITE = PSem.prot_write
19
+ PROT_EXEC = PSem.prot_exec
20
+ PROT_NONE = PSem.prot_none
21
+
22
+ O_RDWR = PSem.o_rdwr
23
+ O_CREAT = PSem.o_creat
24
+ O_EXCL = PSem.o_excl
25
+
26
+ attach_variable :errno, :int
27
+
28
+ attach_function :mmap, [:pointer, :size_t, :int, :int, :int, :off_t], :pointer
29
+ attach_function :munmap, [:pointer, :size_t], :int
30
+ attach_function :ftruncate, [:int, :off_t], :int
31
+ attach_function :close, [:int], :int
32
+
33
+ error_check(:mmap) { |v| v == MAP_FAILED }
34
+ error_check(:munmap, :ftruncate, :close)
35
+ end
36
+ end
@@ -0,0 +1,103 @@
1
+ require 'process_shared/bounded_semaphore'
2
+ require 'process_shared/with_self'
3
+ require 'process_shared/shared_memory'
4
+ require 'process_shared/process_error'
5
+
6
+ module ProcessShared
7
+ class Mutex
8
+ include WithSelf
9
+
10
+ def self.open(&block)
11
+ new.with_self(&block)
12
+ end
13
+
14
+ def initialize
15
+ @internal_sem = BoundedSemaphore.new(1)
16
+ @locked_by = SharedMemory.new(:int)
17
+
18
+ @sem = BoundedSemaphore.new(1)
19
+ end
20
+
21
+ # @return [Mutex]
22
+ def lock
23
+ @sem.wait
24
+ self.locked_by = ::Process.pid
25
+ self
26
+ end
27
+
28
+ # @return [Boolean]
29
+ def locked?
30
+ locked_by > 0
31
+ end
32
+
33
+ # Releases the lock and sleeps timeout seconds if it is given and
34
+ # non-nil or forever.
35
+ #
36
+ # @return [Numeric]
37
+ def sleep(timeout = nil)
38
+ unlock
39
+ begin
40
+ timeout ? sleep(timeout) : sleep
41
+ ensure
42
+ lock
43
+ end
44
+ end
45
+
46
+ # @return [Boolean]
47
+ def try_lock
48
+ with_internal_lock do
49
+ if @locked_by.get_int(0) > 0
50
+ false # was locked
51
+ else
52
+ @sem.wait
53
+ self.locked_by = ::Process.pid
54
+ true
55
+ end
56
+ end
57
+ end
58
+
59
+ # @return [Mutex]
60
+ def unlock
61
+ if (p = locked_by) != ::Process.pid
62
+ raise ProcessError, "lock is held by #{p} not #{::Process.pid}"
63
+ end
64
+
65
+ @sem.post
66
+ self
67
+ end
68
+
69
+ # Acquire the lock, yield the block, then ensure the lock is
70
+ # unlocked.
71
+ def synchronize
72
+ lock
73
+ begin
74
+ yield
75
+ ensure
76
+ unlock
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def locked_by
83
+ with_internal_lock do
84
+ @locked_by.get_int(0)
85
+ end
86
+ end
87
+
88
+ def locked_by=(val)
89
+ with_internal_lock do
90
+ @locked_by.put_int(0, val)
91
+ end
92
+ end
93
+
94
+ def with_internal_lock
95
+ @internal_sem.wait
96
+ begin
97
+ yield
98
+ ensure
99
+ @internal_sem.post
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,29 @@
1
+ # require 'process_shared/libc' - circular dependency here...
2
+
3
+ module ProcessShared
4
+ module PosixCall
5
+ # Replace methods in `syms` with error checking wrappers that
6
+ # invoke the original method and raise a SystemCallError with the
7
+ # current errno if the return value is an error.
8
+ #
9
+ # Errors are detected if the block returns true when called with
10
+ # the original method's return value.
11
+ def error_check(*syms, &is_err)
12
+ unless block_given?
13
+ is_err = lambda { |v| (v == -1) }
14
+ end
15
+
16
+ syms.each do |sym|
17
+ method = self.method(sym)
18
+ define_singleton_method(sym) do |*args|
19
+ ret = method.call(*args)
20
+ if is_err.call(ret)
21
+ raise SystemCallError.new("error in #{sym}", LibC.errno)
22
+ else
23
+ ret
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module ProcessShared
2
+ class ProcessError < Exception; end
3
+ end
@@ -0,0 +1,109 @@
1
+ require 'ffi'
2
+
3
+ module ProcessShared
4
+ module PSem
5
+ class Error < FFI::Struct
6
+ layout(:source, :int,
7
+ :errno, :int)
8
+ end
9
+
10
+ extend FFI::Library
11
+
12
+ # Workaround FFI dylib/bundle issue. See https://github.com/ffi/ffi/issues/42
13
+ suffix = if FFI::Platform.mac?
14
+ 'bundle'
15
+ else
16
+ FFI::Platform::LIBSUFFIX
17
+ end
18
+
19
+ ffi_lib File.join(File.expand_path(File.dirname(__FILE__)),
20
+ 'libpsem.' + suffix)
21
+
22
+ class << self
23
+ # Replace methods in `syms` with error checking wrappers that
24
+ # invoke the original psem method and raise an appropriate
25
+ # error.
26
+ def psem_error_check(*syms)
27
+ syms.each do |sym|
28
+ method = self.method(sym)
29
+
30
+ block = lambda do |*args|
31
+ if method.call(*args) < 0
32
+ errp = args[-1]
33
+ unless errp.nil?
34
+ begin
35
+ err = Error.new(errp.get_pointer(0))
36
+ if err[:source] == PSem.e_source_system
37
+ raise SystemCallError.new("error in #{sym}", err[:errno])
38
+ else
39
+ raise "error in #{sym}: #{err.get_integer(1)}"
40
+ end
41
+ ensure
42
+ psem_error_free(err)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ define_method(sym, &block)
49
+ define_singleton_method(sym, &block)
50
+ end
51
+ end
52
+ end
53
+
54
+ # Generic constants
55
+
56
+ int_consts = [:o_rdwr,
57
+ :o_creat,
58
+ :o_excl,
59
+
60
+ :prot_read,
61
+ :prot_write,
62
+ :prot_exec,
63
+ :prot_none,
64
+
65
+ :map_shared,
66
+ :map_private]
67
+ int_consts.each { |sym| attach_variable sym, :int }
68
+
69
+ # Other constants, functions
70
+
71
+ attach_function :psem_error_free, :error_free, [:pointer], :void
72
+
73
+ attach_variable :e_source_system, :E_SOURCE_SYSTEM, :int
74
+ attach_variable :e_source_psem, :E_SOURCE_PSEM, :int
75
+
76
+ attach_variable :e_name_too_long, :E_NAME_TOO_LONG, :int
77
+
78
+ attach_variable :sizeof_psem_t, :size_t
79
+ attach_variable :sizeof_bsem_t, :size_t
80
+
81
+ # PSem functions
82
+
83
+ attach_function :psem_open, [:pointer, :string, :uint, :pointer], :int
84
+ attach_function :psem_close, [:pointer, :pointer], :int
85
+ attach_function :psem_unlink, [:string, :pointer], :int
86
+ attach_function :psem_post, [:pointer, :pointer], :int
87
+ attach_function :psem_wait, [:pointer, :pointer], :int
88
+ attach_function :psem_trywait, [:pointer, :pointer], :int
89
+ attach_function :psem_timedwait, [:pointer, :pointer, :pointer], :int
90
+ attach_function :psem_getvalue, [:pointer, :pointer, :pointer], :int
91
+
92
+ psem_error_check(:psem_open, :psem_close, :psem_unlink, :psem_post,
93
+ :psem_wait, :psem_trywait, :psem_timedwait, :psem_getvalue)
94
+
95
+ # BSem functions
96
+
97
+ attach_function :bsem_open, [:pointer, :string, :uint, :uint, :pointer], :int
98
+ attach_function :bsem_close, [:pointer, :pointer], :int
99
+ attach_function :bsem_unlink, [:string, :pointer], :int
100
+ attach_function :bsem_post, [:pointer, :pointer], :int
101
+ attach_function :bsem_wait, [:pointer, :pointer], :int
102
+ attach_function :bsem_trywait, [:pointer, :pointer], :int
103
+ attach_function :bsem_timedwait, [:pointer, :pointer, :pointer], :int
104
+ attach_function :bsem_getvalue, [:pointer, :pointer, :pointer], :int
105
+
106
+ psem_error_check(:bsem_open, :bsem_close, :bsem_unlink, :bsem_post,
107
+ :bsem_wait, :bsem_trywait, :bsem_timedwait, :bsem_getvalue)
108
+ end
109
+ end
@@ -0,0 +1,21 @@
1
+ require 'process_shared/posix_call'
2
+ require 'process_shared/psem'
3
+
4
+ module ProcessShared
5
+ module RT
6
+ extend FFI::Library
7
+ extend PosixCall
8
+
9
+ # FIXME: mac and linux OK, but what about everything else?
10
+ if FFI::Platform.mac?
11
+ ffi_lib 'c'
12
+ else
13
+ ffi_lib 'rt'
14
+ end
15
+
16
+ attach_function :shm_open, [:string, :int, :mode_t], :int
17
+ attach_function :shm_unlink, [:string], :int
18
+
19
+ error_check :shm_open, :shm_unlink
20
+ end
21
+ end
@@ -0,0 +1,60 @@
1
+ require 'process_shared/psem'
2
+ require 'process_shared/abstract_semaphore'
3
+
4
+ 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
17
+
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)
32
+ end
33
+ end
34
+
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
+ # Increment the value of the semaphore. If other processes are
42
+ # waiting on this semaphore, one will be woken.
43
+ def post
44
+ psem_post(sem, err)
45
+ end
46
+
47
+ # Get the current value of the semaphore.
48
+ #
49
+ # @return [Integer] the current value of the semaphore.
50
+ def value
51
+ int = FFI::MemoryPointer.new(:int)
52
+ psem_getvalue(sem, int, err)
53
+ int.get_int(0)
54
+ end
55
+
56
+ def close
57
+ psem_close(sem, err)
58
+ end
59
+ end
60
+ end