process_shared 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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