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