Skip to content

Commit 00b825c

Browse files
committed
[GR-17457] Java monitor mixin
PullRequest: truffleruby/2771
2 parents 8ede413 + d94f3e9 commit 00b825c

File tree

17 files changed

+361
-115
lines changed

17 files changed

+361
-115
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Compatibility:
2222

2323
Performance:
2424

25+
* Moved most of `MonitorMixin` to primitives to deal with interrupts more efficiently (#2375).
2526

2627
Changes:
2728

bench/micro/monitor/synchronize.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved. This
2+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
3+
# redistribute it and/or modify it under the terms of the:
4+
#
5+
# Eclipse Public License version 2.0, or
6+
# GNU General Public License version 2, or
7+
# GNU Lesser General Public License version 2.1.
8+
9+
require 'monitor'
10+
11+
m = Monitor.new
12+
benchmark 'monitor-synchronize' do
13+
m.synchronize do
14+
end
15+
end

lib/mri/monitor.rb

Lines changed: 33 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
# frozen_string_literal: false
2-
# = monitor.rb
3-
#
4-
# Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
5-
#
6-
# This library is distributed under the terms of the Ruby license.
7-
# You can freely distribute/modify this library.
8-
#
1+
# truffleruby_primitives: true
92

3+
# Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. This
4+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
5+
# redistribute it and/or modify it under the terms of the:
106
#
7+
# Eclipse Public License version 2.0, or
8+
# GNU General Public License version 2, or
9+
# GNU Lesser General Public License version 2.1.
10+
11+
# Original version licensed under LICENSE.RUBY as it is derived from
12+
# lib/ruby/stdlib/digest.rb and is Copyright (C) 2001 Shugo Maeda
13+
# <shugo@ruby-lang.org>
14+
1115
# In concurrent programming, a monitor is an object or module intended to be
1216
# used safely by more than one thread. The defining characteristic of a
1317
# monitor is that its methods are executed with mutual exclusion. That is, at
@@ -87,17 +91,14 @@
8791
# MonitorMixin module.
8892
#
8993
module MonitorMixin
90-
EXCEPTION_NEVER = {Exception => :never}.freeze
91-
EXCEPTION_IMMEDIATE = {Exception => :immediate}.freeze
92-
9394
#
9495
# FIXME: This isn't documented in Nutshell.
9596
#
9697
# Since MonitorMixin.new_cond returns a ConditionVariable, and the example
9798
# above calls while_wait and signal, this class should be documented.
9899
#
100+
99101
class ConditionVariable
100-
class Timeout < Exception; end
101102

102103
#
103104
# Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
@@ -106,18 +107,7 @@ class Timeout < Exception; end
106107
# even if no other thread doesn't signal.
107108
#
108109
def wait(timeout = nil)
109-
Thread.handle_interrupt(EXCEPTION_NEVER) do
110-
@monitor.__send__(:mon_check_owner)
111-
count = @monitor.__send__(:mon_exit_for_cond)
112-
begin
113-
Thread.handle_interrupt(EXCEPTION_IMMEDIATE) do
114-
@cond.wait(@monitor.instance_variable_get(:@mon_mutex), timeout)
115-
end
116-
return true
117-
ensure
118-
@monitor.__send__(:mon_enter_for_cond, count)
119-
end
120-
end
110+
@cond.wait(@mon_mutex, timeout)
121111
end
122112

123113
#
@@ -142,23 +132,27 @@ def wait_until
142132
# Wakes up the first thread in line waiting for this lock.
143133
#
144134
def signal
145-
@monitor.__send__(:mon_check_owner)
135+
check_owner
146136
@cond.signal
147137
end
148138

149139
#
150140
# Wakes up all threads waiting for this lock.
151141
#
152142
def broadcast
153-
@monitor.__send__(:mon_check_owner)
143+
check_owner
154144
@cond.broadcast
155145
end
156146

157147
private
158148

159-
def initialize(monitor)
160-
@monitor = monitor
161-
@cond = Thread::ConditionVariable.new
149+
def check_owner
150+
raise ThreadError, "current thread not owner" unless @mon_mutex.owned?
151+
end
152+
153+
def initialize(mutex)
154+
@cond = ::ConditionVariable.new
155+
@mon_mutex = mutex
162156
end
163157
end
164158

@@ -171,15 +165,7 @@ def self.extend_object(obj)
171165
# Attempts to enter exclusive section. Returns +false+ if lock fails.
172166
#
173167
def mon_try_enter
174-
if @mon_owner != Thread.current
175-
unless @mon_mutex.try_lock
176-
return false
177-
end
178-
@mon_owner = Thread.current
179-
@mon_count = 0
180-
end
181-
@mon_count += 1
182-
return true
168+
Primitive.monitor_try_enter(@mon_mutex)
183169
end
184170
# For backward compatibility
185171
alias try_mon_enter mon_try_enter
@@ -188,24 +174,14 @@ def mon_try_enter
188174
# Enters exclusive section.
189175
#
190176
def mon_enter
191-
if @mon_owner != Thread.current
192-
@mon_mutex.lock
193-
@mon_owner = Thread.current
194-
@mon_count = 0
195-
end
196-
@mon_count += 1
177+
Primitive.monitor_enter(@mon_mutex)
197178
end
198179

199180
#
200181
# Leaves exclusive section.
201182
#
202183
def mon_exit
203-
mon_check_owner
204-
@mon_count -=1
205-
if @mon_count == 0
206-
@mon_owner = nil
207-
@mon_mutex.unlock
208-
end
184+
Primitive.monitor_exit(@mon_mutex)
209185
end
210186

211187
#
@@ -219,27 +195,16 @@ def mon_locked?
219195
# Returns true if this monitor is locked by current thread.
220196
#
221197
def mon_owned?
222-
@mon_mutex.locked? && @mon_owner == Thread.current
198+
@mon_mutex.owned?
223199
end
224200

225201
#
226202
# Enters exclusive section and executes the block. Leaves the exclusive
227203
# section automatically when the block exits. See example under
228204
# +MonitorMixin+.
229205
#
230-
def mon_synchronize
231-
# Prevent interrupt on handling interrupts; for example timeout errors
232-
# it may break locking state.
233-
Thread.handle_interrupt(EXCEPTION_NEVER) do
234-
mon_enter
235-
begin
236-
Thread.handle_interrupt(EXCEPTION_IMMEDIATE) do
237-
yield
238-
end
239-
ensure
240-
mon_exit
241-
end
242-
end
206+
def mon_synchronize(&block)
207+
Primitive.monitor_synchronize(@mon_mutex, block)
243208
end
244209
alias synchronize mon_synchronize
245210

@@ -248,11 +213,9 @@ def mon_synchronize
248213
# receiver.
249214
#
250215
def new_cond
251-
return ConditionVariable.new(self)
216+
ConditionVariable.new(@mon_mutex)
252217
end
253218

254-
private
255-
256219
# Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead
257220
# of this constructor. Have look at the examples above to understand how to
258221
# use this module.
@@ -264,32 +227,13 @@ def initialize(*args)
264227
# Initializes the MonitorMixin after being included in a class or when an
265228
# object has been extended with the MonitorMixin
266229
def mon_initialize
267-
if defined?(@mon_mutex) && @mon_mutex_owner_object_id == object_id
268-
raise ThreadError, "already initialized"
230+
if defined?(@mon_mutex) && Primitive.object_equal(@mon_mutex_owner_object, self)
231+
raise ThreadError, 'already initialized'
269232
end
270233
@mon_mutex = Thread::Mutex.new
271-
@mon_mutex_owner_object_id = object_id
272-
@mon_owner = nil
273-
@mon_count = 0
234+
@mon_mutex_owner_object = self
274235
end
275236

276-
def mon_check_owner
277-
if @mon_owner != Thread.current
278-
raise ThreadError, "current thread not owner"
279-
end
280-
end
281-
282-
def mon_enter_for_cond(count)
283-
@mon_owner = Thread.current
284-
@mon_count = count
285-
end
286-
287-
def mon_exit_for_cond
288-
count = @mon_count
289-
@mon_owner = nil
290-
@mon_count = 0
291-
return count
292-
end
293237
end
294238

295239
# Use the Monitor class when you want to have a lock object for blocks with
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
require_relative '../../spec_helper'
2+
require 'monitor'
3+
4+
describe "Monitor#enter" do
5+
it "acquires the monitor" do
6+
monitor = Monitor.new
7+
10.times do
8+
wait_q = Queue.new
9+
continue_q = Queue.new
10+
11+
thread = Thread.new do
12+
begin
13+
monitor.enter
14+
wait_q << true
15+
continue_q.pop
16+
ensure
17+
monitor.exit
18+
end
19+
end
20+
21+
wait_q.pop
22+
monitor.mon_locked?.should == true
23+
continue_q << true
24+
thread.join
25+
monitor.mon_locked?.should == false
26+
end
27+
end
28+
end
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
require_relative '../../spec_helper'
2+
require 'monitor'
3+
4+
describe "Monitor#new_cond" do
5+
it "creates a MonitorMixin::ConditionVariable" do
6+
m = Monitor.new
7+
c = m.new_cond
8+
c.class.should == MonitorMixin::ConditionVariable
9+
end
10+
11+
it 'returns a condition variable which can be waited on by a thread holding the monitor' do
12+
m = Monitor.new
13+
c = m.new_cond
14+
15+
10.times do
16+
17+
wait_q = Queue.new
18+
thread = Thread.new do
19+
m.synchronize do
20+
wait_q << true
21+
c.wait
22+
end
23+
:done
24+
end
25+
26+
wait_q.pop
27+
28+
# Synchronize can't happen until the other thread is waiting.
29+
m.synchronize { c.signal }
30+
31+
thread.join
32+
thread.value.should == :done
33+
end
34+
end
35+
36+
it 'returns a condition variable which can be waited on by a thread holding the monitor inside multiple synchronize blocks' do
37+
m = Monitor.new
38+
c = m.new_cond
39+
40+
10.times do
41+
42+
wait_q = Queue.new
43+
thread = Thread.new do
44+
m.synchronize do
45+
m.synchronize do
46+
wait_q << true
47+
c.wait
48+
end
49+
end
50+
:done
51+
end
52+
53+
wait_q.pop
54+
55+
#No need to wait here as we cannot synchronize until the other thread is waiting.
56+
m.synchronize { c.signal }
57+
58+
thread.join
59+
thread.value.should == :done
60+
end
61+
end
62+
63+
it 'returns a condition variable which can be signalled by a thread holding the monitor inside multiple synchronize blocks' do
64+
m = Monitor.new
65+
c = m.new_cond
66+
67+
10.times do
68+
69+
wait_q = Queue.new
70+
thread = Thread.new do
71+
m.synchronize do
72+
wait_q << true
73+
c.wait
74+
end
75+
:done
76+
end
77+
78+
wait_q.pop
79+
80+
# Synchronize can't happen until the other thread is waiting.
81+
m.synchronize { m.synchronize { c.signal } }
82+
83+
thread.join
84+
thread.value.should == :done
85+
end
86+
end
87+
88+
end

0 commit comments

Comments
 (0)