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.
- data/COPYING +19 -0
- data/ChangeLog +0 -0
- data/README.rdoc +69 -0
- data/ext/libpsem/bsem.c +188 -0
- data/ext/libpsem/bsem.h +32 -0
- data/ext/libpsem/constants.c +22 -0
- data/ext/libpsem/constants.h +18 -0
- data/ext/libpsem/extconf.rb +36 -0
- data/ext/libpsem/mempcpy.c +7 -0
- data/ext/libpsem/mempcpy.h +13 -0
- data/ext/libpsem/mutex.c +15 -0
- data/ext/libpsem/mutex.h +14 -0
- data/ext/libpsem/psem.c +15 -0
- data/ext/libpsem/psem.h +43 -0
- data/ext/libpsem/psem_error.c +46 -0
- data/ext/libpsem/psem_error.h +11 -0
- data/ext/libpsem/psem_posix.c +130 -0
- data/ext/libpsem/psem_posix.h +10 -0
- data/ext/pthread_sync_helper/extconf.rb +9 -0
- data/ext/pthread_sync_helper/pthread_sync_helper.c +43 -0
- data/ext/semaphore.c +623 -0
- data/lib/process_shared.rb +6 -0
- data/lib/process_shared/abstract_semaphore.rb +50 -0
- data/lib/process_shared/bounded_semaphore.rb +43 -0
- data/lib/process_shared/condition_variable.rb +27 -0
- data/lib/process_shared/libc.rb +36 -0
- data/lib/process_shared/libpsem.bundle +0 -0
- data/lib/process_shared/libpsem.so +0 -0
- data/lib/process_shared/mutex.rb +103 -0
- data/lib/process_shared/posix_call.rb +29 -0
- data/lib/process_shared/process_error.rb +3 -0
- data/lib/process_shared/psem.rb +109 -0
- data/lib/process_shared/rt.rb +21 -0
- data/lib/process_shared/semaphore.rb +60 -0
- data/lib/process_shared/shared_memory.rb +45 -0
- data/lib/process_shared/thread.rb +30 -0
- data/lib/process_shared/with_self.rb +20 -0
- data/lib/scratch.rb +300 -0
- data/spec/process_shared/bounded_semaphore_spec.rb +48 -0
- data/spec/process_shared/libc_spec.rb +9 -0
- data/spec/process_shared/mutex_spec.rb +74 -0
- data/spec/process_shared/psem_spec.rb +136 -0
- data/spec/process_shared/semaphore_spec.rb +76 -0
- data/spec/process_shared/shared_memory_spec.rb +36 -0
- data/spec/spec_helper.rb +35 -0
- metadata +139 -0
@@ -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
|
Binary file
|
Binary file
|
@@ -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,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
|