process_shared 0.1.6 → 0.1.7

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