process_shared 0.1.8 → 0.1.9
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/lib/process_shared/monitor.rb +2 -2
- data/lib/process_shared/mutex.rb +32 -19
- data/spec/process_shared/mutex_spec.rb +28 -1
- metadata +3 -3
@@ -9,7 +9,7 @@ module ProcessShared
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def lock
|
12
|
-
if locked_by ==
|
12
|
+
if locked_by == current_process_and_thread
|
13
13
|
@lock_count += 1
|
14
14
|
else
|
15
15
|
super
|
@@ -17,7 +17,7 @@ module ProcessShared
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def unlock
|
20
|
-
if locked_by ==
|
20
|
+
if locked_by == current_process_and_thread
|
21
21
|
if @lock_count > 0
|
22
22
|
@lock_count -= 1
|
23
23
|
else
|
data/lib/process_shared/mutex.rb
CHANGED
@@ -4,13 +4,14 @@ require 'process_shared/process_error'
|
|
4
4
|
|
5
5
|
module ProcessShared
|
6
6
|
# This Mutex class is implemented as a Semaphore with a second
|
7
|
-
# internal Semaphore used to track the locking process
|
7
|
+
# internal Semaphore used to track the locking process and thread.
|
8
|
+
#
|
8
9
|
# {ProcessError} is raised if either {#unlock} is called by a
|
9
|
-
# process different from the locking process, or
|
10
|
-
# called while the process already holds the
|
11
|
-
# not re-entrant). This tracking is not
|
12
|
-
# of course (current implementation uses
|
13
|
-
# and {SharedMemory} segment).
|
10
|
+
# process + thread different from the locking process + thread, or
|
11
|
+
# if {#lock} is called while the process + thread already holds the
|
12
|
+
# lock (i.e. the mutex is not re-entrant). This tracking is not
|
13
|
+
# without performance cost, of course (current implementation uses
|
14
|
+
# the additional {Semaphore} and {SharedMemory} segment).
|
14
15
|
#
|
15
16
|
# The API is intended to be identical to the {::Mutex} in the core
|
16
17
|
# Ruby library.
|
@@ -23,25 +24,25 @@ module ProcessShared
|
|
23
24
|
|
24
25
|
def initialize
|
25
26
|
@internal_sem = Semaphore.new
|
26
|
-
@locked_by = SharedMemory.new(:
|
27
|
+
@locked_by = SharedMemory.new(:uint64, 2) # [Process ID, Thread ID]
|
27
28
|
|
28
29
|
@sem = Semaphore.new
|
29
30
|
end
|
30
31
|
|
31
32
|
# @return [Mutex]
|
32
33
|
def lock
|
33
|
-
if
|
34
|
-
raise ProcessError, "already locked by this process #{
|
34
|
+
if (p, t = current_process_and_thread) == locked_by
|
35
|
+
raise ProcessError, "already locked by this process #{p}, thread #{t}"
|
35
36
|
end
|
36
37
|
|
37
38
|
@sem.wait
|
38
|
-
self.locked_by =
|
39
|
+
self.locked_by = current_process_and_thread
|
39
40
|
self
|
40
41
|
end
|
41
42
|
|
42
43
|
# @return [Boolean]
|
43
44
|
def locked?
|
44
|
-
locked_by
|
45
|
+
locked_by != UNLOCKED
|
45
46
|
end
|
46
47
|
|
47
48
|
# Releases the lock and sleeps timeout seconds if it is given and
|
@@ -60,11 +61,11 @@ module ProcessShared
|
|
60
61
|
# @return [Boolean]
|
61
62
|
def try_lock
|
62
63
|
with_internal_lock do
|
63
|
-
if
|
64
|
+
if locked?
|
64
65
|
false # was locked
|
65
66
|
else
|
66
67
|
@sem.wait # should return immediately
|
67
|
-
self.locked_by =
|
68
|
+
self.locked_by = current_process_and_thread
|
68
69
|
true
|
69
70
|
end
|
70
71
|
end
|
@@ -72,11 +73,11 @@ module ProcessShared
|
|
72
73
|
|
73
74
|
# @return [Mutex]
|
74
75
|
def unlock
|
75
|
-
if (p = locked_by) !=
|
76
|
-
raise ProcessError, "lock is held by #{p} not #{
|
76
|
+
if (p, t = locked_by) != (cp, ct = current_process_and_thread)
|
77
|
+
raise ProcessError, "lock is held by process #{p}, thread #{t}: not process #{cp}, thread #{ct}"
|
77
78
|
end
|
78
79
|
|
79
|
-
self.locked_by =
|
80
|
+
self.locked_by = UNLOCKED
|
80
81
|
@sem.post
|
81
82
|
self
|
82
83
|
end
|
@@ -96,20 +97,32 @@ module ProcessShared
|
|
96
97
|
|
97
98
|
protected
|
98
99
|
|
100
|
+
# @return [Array<(Fixnum, Fixnum)>]
|
101
|
+
# If locked, IDs of the locking process and thread, otherwise +UNLOCKED+
|
99
102
|
def locked_by
|
100
103
|
with_internal_lock do
|
101
|
-
@locked_by.
|
104
|
+
@locked_by.read_array_of_uint64(2)
|
102
105
|
end
|
103
106
|
end
|
104
107
|
|
105
|
-
|
108
|
+
# @param [Array<(Fixnum, Fixnum)>] ary
|
109
|
+
# Set the IDs of the locking process and thread, or +UNLOCKED+ if none
|
110
|
+
def locked_by=(ary)
|
106
111
|
with_internal_lock do
|
107
|
-
@locked_by.
|
112
|
+
@locked_by.write_array_of_uint64(ary)
|
108
113
|
end
|
109
114
|
end
|
110
115
|
|
111
116
|
def with_internal_lock(&block)
|
112
117
|
@internal_sem.synchronize &block
|
113
118
|
end
|
119
|
+
|
120
|
+
# @return [Array<(Fixnum, Fixnum)>] IDs of the current process and thread
|
121
|
+
def current_process_and_thread
|
122
|
+
[::Process.pid, Thread.current.object_id]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Represents the state of being unlocked
|
126
|
+
UNLOCKED = [0, 0].freeze
|
114
127
|
end
|
115
128
|
end
|
@@ -24,10 +24,37 @@ module ProcessShared
|
|
24
24
|
::Process.wait(pid)
|
25
25
|
end
|
26
26
|
|
27
|
-
it 'raises exception when
|
27
|
+
it 'raises exception when unlocked by other thread in same process' do
|
28
|
+
t = Thread.new do
|
29
|
+
@lock.lock
|
30
|
+
sleep 0.2
|
31
|
+
@lock.unlock
|
32
|
+
end
|
33
|
+
|
34
|
+
sleep 0.1
|
35
|
+
proc { @lock.unlock }.must_raise(ProcessError)
|
36
|
+
|
37
|
+
t.join
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises exception when locked twice by same process and thread' do
|
28
41
|
@lock.lock
|
29
42
|
proc { @lock.lock }.must_raise(ProcessError)
|
30
43
|
@lock.unlock
|
31
44
|
end
|
45
|
+
|
46
|
+
it 'does not raise when locked by different threads on same process' do
|
47
|
+
t = Thread.new do
|
48
|
+
@lock.lock
|
49
|
+
sleep 0.2
|
50
|
+
@lock.unlock
|
51
|
+
end
|
52
|
+
|
53
|
+
sleep 0.1
|
54
|
+
@lock.synchronize { }
|
55
|
+
|
56
|
+
t.join
|
57
|
+
end
|
58
|
+
|
32
59
|
end
|
33
60
|
end
|
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.
|
4
|
+
version: 0.1.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -226,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
226
226
|
version: '0'
|
227
227
|
segments:
|
228
228
|
- 0
|
229
|
-
hash: -
|
229
|
+
hash: -3321129615357213550
|
230
230
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
231
231
|
none: false
|
232
232
|
requirements:
|
@@ -235,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
235
235
|
version: '0'
|
236
236
|
segments:
|
237
237
|
- 0
|
238
|
-
hash: -
|
238
|
+
hash: -3321129615357213550
|
239
239
|
requirements: []
|
240
240
|
rubyforge_project:
|
241
241
|
rubygems_version: 1.8.23
|