process_shared 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,7 @@ module ProcessShared
9
9
  end
10
10
 
11
11
  def lock
12
- if locked_by == ::Process.pid
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 == ::Process.pid
20
+ if locked_by == current_process_and_thread
21
21
  if @lock_count > 0
22
22
  @lock_count -= 1
23
23
  else
@@ -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 is tracked.
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 if {#lock} is
10
- # called while the process already holds the lock (i.e. the mutex is
11
- # not re-entrant). This tracking is not without performance cost,
12
- # of course (current implementation uses the additional {Semaphore}
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(:int)
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 locked_by == ::Process.pid
34
- raise ProcessError, "already locked by this process #{::Process.pid}"
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 = ::Process.pid
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 > 0
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 @locked_by.get_int(0) > 0
64
+ if locked?
64
65
  false # was locked
65
66
  else
66
67
  @sem.wait # should return immediately
67
- self.locked_by = ::Process.pid
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) != ::Process.pid
76
- raise ProcessError, "lock is held by #{p} not #{::Process.pid}"
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 = 0
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.get_int(0)
104
+ @locked_by.read_array_of_uint64(2)
102
105
  end
103
106
  end
104
107
 
105
- def locked_by=(val)
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.put_int(0, val)
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 locked twice by same process' do
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.8
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: -1670336126622666309
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: -1670336126622666309
238
+ hash: -3321129615357213550
239
239
  requirements: []
240
240
  rubyforge_project:
241
241
  rubygems_version: 1.8.23