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.
@@ -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