process_shared 0.1.6 → 0.1.7

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/README.rdoc CHANGED
@@ -3,11 +3,23 @@
3
3
  Concurrency primitives that may be used in a cross-process way to
4
4
  coordinate share memory between processes.
5
5
 
6
- A small C library (libpsem) is compiled to provide portable access to
7
- semaphores (based on http://pyprocessing.berlios.de/). This library
8
- is then accessed using FFI to implement Ruby classes
9
- ProcessShared::Semaphore, ProcessShared::BoundedSemaphore,
10
- ProcessShared::Mutex, and ProcessShared::SharedMemory.
6
+ FFI is used to access POSIX semaphore on Linux or Mach semaphores on
7
+ Mac. Atop these semaphores are implemented ProcessShared::Semaphore,
8
+ ProcessShared::Mutex. POSIX shared memory is used to implement
9
+ ProcessShared::SharedMemory.
10
+
11
+ On Linux, POSIX semaphores support <tt>sem_timedwait()</tt> which can wait on
12
+ a semaphore but stop waiting after a timeout.
13
+
14
+ Mac OS X's implementation of POSIX semaphores does not support
15
+ timeouts. But, the Mach layer in Mac OS X has its own semaphores that
16
+ do support timeouts. Thus, process_shared implements a moderate
17
+ subset of the Mach API, which is quite a bit different from POSIX.
18
+ Namely, semaphores created in one process are not available in child
19
+ processes created via <tt>fork()</tt>. Mach does provide the means to copy
20
+ capabilities between tasks (Mach equivalent to processes).
21
+ process_shared overrides Ruby's <tt>fork</tt> methods so that semaphores are
22
+ copied from parent to child to emulate the POSIX behavior.
11
23
 
12
24
  This is an incomplete work in progress.
13
25
 
@@ -80,14 +92,8 @@ Install the gem with:
80
92
 
81
93
  * Test ConditionVariable
82
94
  * Implement optional override of core Thread/Mutex classes
83
- * Extend libpsem to win32? (See Python's processing library)
84
- * Break out tests that use PSem.getvalue() (which isn't supported on Mac OS X)
85
- so that the test suite will pass
95
+ * Extend to win32? (See Python's processing library)
86
96
  * Add finalizer to Mutex? (finalizer on Semaphore objects may be enough) or a method to
87
97
  explicitly close and release resources?
88
98
  * Test semantics of crashing processes who still hold locks, etc.
89
99
  * Is SharedArray with Enumerable mixing sufficient Array-like interface?
90
- * Remove bsem from libpsem as it is of little use and doesn't work on Mac OS X
91
- * Possibly implement BoundedSemaphore with arbitrary bound (in Ruby
92
- rather than relying on sem_getvalue()), but this is of little
93
- utility beyond extra error checking..
@@ -11,4 +11,4 @@ have_header('pthread.h')
11
11
  have_type('pthread_mutex_t', 'pthread.h')
12
12
  have_type('pthread_mutexattr_t', 'pthread.h')
13
13
 
14
- create_makefile('helper')
14
+ create_makefile('process_shared/posix/helper')
@@ -0,0 +1,31 @@
1
+ require 'process_shared'
2
+ require 'process_shared/mutex'
3
+
4
+ module ProcessShared
5
+ class Monitor < Mutex
6
+ def initialize
7
+ super
8
+ @lock_count = 0
9
+ end
10
+
11
+ def lock
12
+ if locked_by == ::Process.pid
13
+ @lock_count += 1
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def unlock
20
+ if locked_by == ::Process.pid
21
+ if @lock_count > 0
22
+ @lock_count -= 1
23
+ else
24
+ super
25
+ end
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ require 'process_shared'
2
+
3
+ module ProcessShared
4
+ module MonitorMixin
5
+ def self.extended(obj)
6
+ obj.send :mon_initialize
7
+ end
8
+
9
+ def mon_enter
10
+ @mon_monitor.lock
11
+ end
12
+
13
+ def mon_exit
14
+ @mon_monitor.unlock
15
+ end
16
+
17
+ def mon_synchronize
18
+ mon_enter
19
+ begin
20
+ yield
21
+ ensure
22
+ mon_exit
23
+ end
24
+ end
25
+ alias_method :synchronize, :mon_synchronize
26
+
27
+ def mon_try_enter
28
+ raise NotImplementedError, 'not implemented'
29
+ end
30
+ alias_method :try_mon_enter, :mon_try_enter
31
+
32
+ def new_cond
33
+ raise NotImplementedError, 'not implemented'
34
+ end
35
+
36
+ private
37
+
38
+ def mon_initialize
39
+ @mon_monitor = Monitor.new
40
+ end
41
+ end
42
+ end
@@ -94,7 +94,7 @@ module ProcessShared
94
94
  end
95
95
  end
96
96
 
97
- private
97
+ protected
98
98
 
99
99
  def locked_by
100
100
  with_internal_lock do
@@ -108,13 +108,8 @@ module ProcessShared
108
108
  end
109
109
  end
110
110
 
111
- def with_internal_lock
112
- @internal_sem.wait
113
- begin
114
- yield
115
- ensure
116
- @internal_sem.post
117
- end
111
+ def with_internal_lock(&block)
112
+ @internal_sem.synchronize &block
118
113
  end
119
114
  end
120
115
  end
@@ -28,4 +28,6 @@ end
28
28
  require 'process_shared/binary_semaphore'
29
29
  require 'process_shared/mutex'
30
30
  require 'process_shared/condition_variable'
31
+ require 'process_shared/monitor'
32
+ require 'process_shared/monitor_mixin'
31
33
 
@@ -0,0 +1,60 @@
1
+ module ProcessShared
2
+ # Classes that include this module should assign a lock object to
3
+ # @lock before each test.
4
+ module LockBehavior
5
+
6
+ # Fork +n+ processes. In each, yield the block (passing the process
7
+ # number), then call Kernel.exit! Waits for all processes to
8
+ # complete before returning.
9
+ def fork_many(n)
10
+ pids = []
11
+ n.times do |i|
12
+ pids << fork do
13
+ yield i
14
+ Kernel.exit!
15
+ end
16
+ end
17
+
18
+ pids.each { |pid| ::Process.wait(pid) }
19
+ end
20
+
21
+ def test_protects_access_to_a_shared_variable
22
+ mem = SharedMemory.new(:char)
23
+ mem.put_char(0, 0)
24
+
25
+ fork_many(10) do |i|
26
+ inc = (-1) ** i # half the procs increment; half decrement
27
+ 10.times do
28
+ @lock.lock
29
+ begin
30
+ mem.put_char(0, mem.get_char(0) + inc)
31
+ sleep 0.001
32
+ ensure
33
+ @lock.unlock
34
+ end
35
+ end
36
+ end
37
+
38
+ mem.get_char(0).must_equal(0)
39
+ end
40
+
41
+ def test_protects_access_to_a_shared_variable_with_synchronize
42
+ mem = SharedMemory.new(:char)
43
+ mem.put_char(0, 0)
44
+
45
+ fork_many(10) do |i|
46
+ inc = (-1) ** i # half the procs increment; half decrement
47
+ 10.times do
48
+ @lock.synchronize do
49
+ mem.put_char(0, mem.get_char(0) + inc)
50
+ sleep 0.001
51
+ end
52
+ end
53
+ end
54
+
55
+ mem.get_char(0).must_equal(0)
56
+ end
57
+
58
+
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'process_shared'
3
+
4
+ module ProcessShared
5
+ describe MonitorMixin do
6
+
7
+ before :each do
8
+ @obj = Object.new
9
+ @obj.extend(MonitorMixin)
10
+ end
11
+
12
+ it 'raises exception when unlocked by other process' do
13
+ pid = Kernel.fork do
14
+ @obj.mon_enter
15
+ sleep 0.2
16
+ @obj.mon_exit
17
+ Kernel.exit!
18
+ end
19
+
20
+ sleep 0.1
21
+ proc { @obj.mon_exit }.must_raise(ProcessError)
22
+
23
+ ::Process.wait(pid)
24
+ end
25
+
26
+ it 'raises nothing with nested lock' do
27
+ @obj.mon_enter
28
+ @obj.mon_enter
29
+ @obj.mon_exit
30
+ @obj.mon_exit
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'process_shared'
3
+
4
+ module ProcessShared
5
+ describe Monitor do
6
+
7
+ include LockBehavior
8
+
9
+ before :each do
10
+ @lock = Monitor.new
11
+ end
12
+
13
+ it 'raises exception when unlocked by other process' do
14
+ pid = Kernel.fork do
15
+ @lock.lock
16
+ sleep 0.2
17
+ @lock.unlock
18
+ Kernel.exit!
19
+ end
20
+
21
+ sleep 0.1
22
+ proc { @lock.unlock }.must_raise(ProcessError)
23
+
24
+ ::Process.wait(pid)
25
+ end
26
+
27
+ it 'raises nothing with nested lock' do
28
+ @lock.lock
29
+ @lock.lock
30
+ @lock.unlock
31
+ @lock.unlock
32
+ end
33
+ end
34
+ end
@@ -3,79 +3,31 @@ require 'process_shared'
3
3
 
4
4
  module ProcessShared
5
5
  describe Mutex do
6
- it 'protects access to a shared variable' do
7
- mutex = Mutex.new
8
- mem = SharedMemory.new(:char)
9
- mem.put_char(0, 0)
10
6
 
11
- pids = []
12
- 10.times do |i|
13
- inc = (-1) ** i # half the procs increment; half decrement
14
- pids << fork do
15
- 10.times do
16
- mutex.lock
17
- begin
18
- mem.put_char(0, mem.get_char(0) + inc)
19
- sleep 0.001
20
- ensure
21
- mutex.unlock
22
- end
23
- end
24
- Kernel.exit!
25
- end
26
- end
27
-
28
- pids.each { |pid| ::Process.wait(pid) }
7
+ include LockBehavior
29
8
 
30
- mem.get_char(0).must_equal(0)
9
+ before :each do
10
+ @lock = Mutex.new
31
11
  end
32
-
33
- it 'protects access to a shared variable with synchronize' do
34
- mutex = Mutex.new
35
- mem = SharedMemory.new(:char)
36
- mem.put_char(0, 0)
37
-
38
- pids = []
39
- 10.times do |i|
40
- inc = (-1) ** i # half the procs increment; half decrement
41
- pids << fork do
42
- 10.times do
43
- mutex.synchronize do
44
- mem.put_char(0, mem.get_char(0) + inc)
45
- sleep 0.001
46
- end
47
- end
48
- Kernel.exit!
49
- end
50
- end
51
-
52
- pids.each { |pid| ::Process.wait(pid) }
53
-
54
- mem.get_char(0).must_equal(0)
55
- end
56
-
12
+
57
13
  it 'raises exception when unlocked by other process' do
58
- mutex = Mutex.new
59
-
60
14
  pid = Kernel.fork do
61
- mutex.lock
15
+ @lock.lock
62
16
  sleep 0.2
63
- mutex.unlock
17
+ @lock.unlock
64
18
  Kernel.exit!
65
19
  end
66
20
 
67
21
  sleep 0.1
68
- proc { mutex.unlock }.must_raise(ProcessError)
22
+ proc { @lock.unlock }.must_raise(ProcessError)
69
23
 
70
24
  ::Process.wait(pid)
71
25
  end
72
26
 
73
27
  it 'raises exception when locked twice by same process' do
74
- mutex = Mutex.new
75
-
76
- mutex.lock
77
- proc { mutex.lock }.must_raise(ProcessError)
78
- mutex.unlock
28
+ @lock.lock
29
+ proc { @lock.lock }.must_raise(ProcessError)
30
+ @lock.unlock
79
31
  end
80
32
  end
81
33
  end
@@ -5,6 +5,25 @@ require 'process_shared'
5
5
 
6
6
  module ProcessShared
7
7
  describe Semaphore do
8
+
9
+ describe 'As Lock' do
10
+
11
+ include LockBehavior
12
+
13
+ before :each do
14
+ @lock = Semaphore.new
15
+ class << @lock
16
+ alias_method :lock, :wait
17
+ alias_method :unlock, :post
18
+ end
19
+ end
20
+
21
+ after :each do
22
+ @lock.close
23
+ end
24
+
25
+ end
26
+
8
27
  it 'coordinates access to shared object' do
9
28
  nprocs = 4 # number of processes
10
29
  nincrs = 1000 # each process increments nincrs times
data/spec/spec_helper.rb CHANGED
@@ -5,6 +5,7 @@ require 'minitest/autorun'
5
5
  require 'minitest/matchers'
6
6
 
7
7
  require 'process_shared'
8
+ require 'process_shared/lock_behavior'
8
9
 
9
10
  class RangeMatcher
10
11
  def initialize(operator, limit)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_shared
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-09 00:00:00.000000000Z
12
+ date: 2013-02-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
16
- requirement: &23371720 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '1.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *23371720
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
25
30
  - !ruby/object:Gem::Dependency
26
- name: rake
27
- requirement: &23370860 !ruby/object:Gem::Requirement
31
+ name: ci_reporter
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '0'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *23370860
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  - !ruby/object:Gem::Dependency
37
- name: rake-compiler
38
- requirement: &23369400 !ruby/object:Gem::Requirement
47
+ name: flog
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: '0'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *23369400
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: minitest
49
- requirement: &23367920 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,10 +69,47 @@ dependencies:
54
69
  version: '0'
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *23367920
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: minitest-matchers
60
- requirement: &23358140 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rake-compiler
112
+ requirement: !ruby/object:Gem::Requirement
61
113
  none: false
62
114
  requirements:
63
115
  - - ! '>='
@@ -65,10 +117,15 @@ dependencies:
65
117
  version: '0'
66
118
  type: :development
67
119
  prerelease: false
68
- version_requirements: *23358140
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
69
126
  - !ruby/object:Gem::Dependency
70
127
  name: version
71
- requirement: &23357400 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
72
129
  none: false
73
130
  requirements:
74
131
  - - ! '>='
@@ -76,71 +133,72 @@ dependencies:
76
133
  version: '0'
77
134
  type: :development
78
135
  prerelease: false
79
- version_requirements: *23357400
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
80
142
  description: FFI wrapper around portable semaphore library with mutex and condition
81
143
  vars built on top.
82
144
  email: pat@polycrystal.org
83
145
  executables: []
84
146
  extensions:
85
- - ext/pthread_sync_helper/extconf.rb
86
147
  - ext/helper/extconf.rb
87
148
  extra_rdoc_files:
88
149
  - README.rdoc
89
150
  - ChangeLog
90
151
  - COPYING
91
152
  files:
92
- - lib/mach.rb
93
- - lib/scratch.rb
94
- - lib/process_shared.rb
95
- - lib/mach/host.rb
96
153
  - lib/mach/clock.rb
97
- - lib/mach/semaphore.rb
98
- - lib/mach/functions.rb
99
- - lib/mach/time_spec.rb
100
154
  - lib/mach/error.rb
155
+ - lib/mach/functions.rb
156
+ - lib/mach/host.rb
101
157
  - lib/mach/port.rb
102
- - lib/mach/types.rb
158
+ - lib/mach/semaphore.rb
103
159
  - lib/mach/task.rb
104
- - lib/process_shared/synchronizable_semaphore.rb
160
+ - lib/mach/time_spec.rb
161
+ - lib/mach/types.rb
162
+ - lib/mach.rb
105
163
  - lib/process_shared/abstract_semaphore.rb
106
- - lib/process_shared/posix_call.rb
164
+ - lib/process_shared/binary_semaphore.rb
165
+ - lib/process_shared/condition_variable.rb
166
+ - lib/process_shared/define_singleton_method.rb
167
+ - lib/process_shared/mach/semaphore.rb
107
168
  - lib/process_shared/mach.rb
169
+ - lib/process_shared/monitor.rb
170
+ - lib/process_shared/monitor_mixin.rb
171
+ - lib/process_shared/mutex.rb
108
172
  - lib/process_shared/object_buffer.rb
109
- - lib/process_shared/process_error.rb
110
- - lib/process_shared/thread.rb
111
173
  - lib/process_shared/open_with_self.rb
112
174
  - lib/process_shared/posix/errno.rb
175
+ - lib/process_shared/posix/libc.rb
113
176
  - lib/process_shared/posix/semaphore.rb
177
+ - lib/process_shared/posix/shared_memory.rb
114
178
  - lib/process_shared/posix/time_spec.rb
115
- - lib/process_shared/posix/shared_array.rb
116
179
  - lib/process_shared/posix/time_val.rb
117
- - lib/process_shared/posix/libc.rb
118
- - lib/process_shared/posix/shared_memory.rb
119
- - lib/process_shared/mutex.rb
120
- - lib/process_shared/shared_memory_io.rb
121
- - lib/process_shared/time_spec.rb
180
+ - lib/process_shared/posix_call.rb
181
+ - lib/process_shared/process_error.rb
122
182
  - lib/process_shared/rt.rb
123
- - lib/process_shared/mach/semaphore.rb
124
183
  - lib/process_shared/shared_array.rb
125
- - lib/process_shared/binary_semaphore.rb
126
- - lib/process_shared/define_singleton_method.rb
127
- - lib/process_shared/condition_variable.rb
128
- - ext/pthread_sync_helper/pthread_sync_helper.c
129
- - ext/semaphore.c
184
+ - lib/process_shared/shared_memory_io.rb
185
+ - lib/process_shared/synchronizable_semaphore.rb
186
+ - lib/process_shared/time_spec.rb
187
+ - lib/process_shared.rb
130
188
  - ext/helper/helper.c
131
- - ext/pthread_sync_helper/extconf.rb
132
189
  - ext/helper/extconf.rb
133
- - spec/mach/scratch2.rb
134
- - spec/mach/task_spec.rb
135
- - spec/mach/scratch.rb
136
190
  - spec/mach/port_spec.rb
137
191
  - spec/mach/semaphore_spec.rb
138
- - spec/process_shared/shared_memory_spec.rb
192
+ - spec/mach/task_spec.rb
139
193
  - spec/process_shared/binary_semaphore_spec.rb
140
194
  - spec/process_shared/condition_variable_spec.rb
195
+ - spec/process_shared/lock_behavior.rb
196
+ - spec/process_shared/monitor_mixin_spec.rb
197
+ - spec/process_shared/monitor_spec.rb
141
198
  - spec/process_shared/mutex_spec.rb
142
- - spec/process_shared/shared_array_spec.rb
143
199
  - spec/process_shared/semaphore_spec.rb
200
+ - spec/process_shared/shared_array_spec.rb
201
+ - spec/process_shared/shared_memory_spec.rb
144
202
  - spec/spec_helper.rb
145
203
  - README.rdoc
146
204
  - ChangeLog
@@ -159,7 +217,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
159
217
  version: '0'
160
218
  segments:
161
219
  - 0
162
- hash: 615501367692780745
220
+ hash: -4453840700159840592
163
221
  required_rubygems_version: !ruby/object:Gem::Requirement
164
222
  none: false
165
223
  requirements:
@@ -168,10 +226,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
226
  version: '0'
169
227
  segments:
170
228
  - 0
171
- hash: 615501367692780745
229
+ hash: -4453840700159840592
172
230
  requirements: []
173
231
  rubyforge_project:
174
- rubygems_version: 1.8.10
232
+ rubygems_version: 1.8.23
175
233
  signing_key:
176
234
  specification_version: 3
177
235
  summary: process-shared synchronization primitives