Skip to content

Commit ea2a0e9

Browse files
committed
Use a ReentrantLock for TruffleRuby.synchronized(object) {}
* So the SafepointManager can interrupt while the thread is trying to acquire the lock. * It seems not possible to interrupt acquiring a monitor with Java `synchronized`.
1 parent aae55f9 commit ea2a0e9

File tree

3 files changed

+37
-2
lines changed

3 files changed

+37
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Bug fixes:
1919
* Fix `Enumerator::Lazy#{chunk_while, slice_before, slice_after, slice_when}` to return instances of `Enumerator::Lazy` (#2273).
2020
* Fix `Truffle::Interop.source_location` to return unavailable source sections for modules instead of null (#2257).
2121
* Fix usage of `Thread.handle_interrupt` in `MonitorMixin#mon_synchronize`.
22+
* Fixed `TruffleRuby.synchronized` to handle guest safepoints (#2277).
2223

2324
Compatibility:
2425

src/main/java/org/truffleruby/Layouts.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public abstract class Layouts {
1919

2020
public static final HiddenKey OBJECT_ID_IDENTIFIER = new HiddenKey("object_id"); // long
2121
public static final HiddenKey FROZEN_IDENTIFIER = new HiddenKey("frozen?"); // boolean
22+
public static final HiddenKey OBJECT_LOCK = new HiddenKey("object_lock"); // ReentrantLock
2223
public static final HiddenKey ASSOCIATED_IDENTIFIER = new HiddenKey("associated"); // Pointer[]
2324
public static final HiddenKey FINALIZER_REF_IDENTIFIER = new HiddenKey("finalizerRef"); // FinalizerReference
2425
public static final HiddenKey MARKED_OBJECTS_IDENTIFIER = new HiddenKey("marked_objects"); // Object[]

src/main/java/org/truffleruby/extra/TruffleRubyNodes.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@
1010
package org.truffleruby.extra;
1111

1212
import com.oracle.truffle.api.dsl.Cached;
13+
import com.oracle.truffle.api.object.DynamicObjectLibrary;
1314
import org.jcodings.specific.UTF8Encoding;
15+
import org.truffleruby.Layouts;
1416
import org.truffleruby.RubyContext;
1517
import org.truffleruby.RubyLanguage;
1618
import org.truffleruby.builtins.CoreMethod;
1719
import org.truffleruby.builtins.CoreMethodArrayArgumentsNode;
1820
import org.truffleruby.builtins.CoreMethodNode;
1921
import org.truffleruby.builtins.CoreModule;
2022
import org.truffleruby.builtins.YieldingCoreMethodNode;
23+
import org.truffleruby.core.mutex.MutexOperations;
2124
import org.truffleruby.core.proc.RubyProc;
2225
import org.truffleruby.core.rope.CodeRange;
2326
import org.truffleruby.core.string.StringNodes;
@@ -29,6 +32,8 @@
2932
import com.oracle.truffle.api.dsl.Specialization;
3033
import org.truffleruby.language.RubyDynamicObject;
3134

35+
import java.util.concurrent.locks.ReentrantLock;
36+
3237
@CoreModule("TruffleRuby")
3338
public abstract class TruffleRubyNodes {
3439

@@ -104,11 +109,39 @@ protected Object fullMemoryBarrier() {
104109
@CoreMethod(names = "synchronized", onSingleton = true, required = 1, needsBlock = true)
105110
public abstract static class SynchronizedNode extends YieldingCoreMethodNode {
106111

107-
// We must not allow to synchronize on boxed primitives.
112+
/** We must not allow to synchronize on boxed primitives as that would be misleading. We use a ReentrantLock and
113+
* not simply Java's {@code synchronized} here as we need to be able to interrupt for guest safepoints and it is
114+
* not possible to interrupt Java's {@code synchronized (object) {}}. */
108115
@Specialization
109116
protected Object synchronize(RubyDynamicObject object, RubyProc block) {
110-
synchronized (object) {
117+
final ReentrantLock lock = getLockAndLock(object);
118+
try {
111119
return yield(block);
120+
} finally {
121+
MutexOperations.unlockInternal(lock);
122+
}
123+
}
124+
125+
@TruffleBoundary
126+
private ReentrantLock getLockAndLock(RubyDynamicObject object) {
127+
final ReentrantLock lock = getLock(object);
128+
MutexOperations.lockInternal(getContext(), lock, this);
129+
return lock;
130+
}
131+
132+
@TruffleBoundary
133+
private ReentrantLock getLock(RubyDynamicObject object) {
134+
final DynamicObjectLibrary objectLibrary = DynamicObjectLibrary.getUncached();
135+
136+
synchronized (object) {
137+
ReentrantLock lock = (ReentrantLock) objectLibrary.getOrDefault(object, Layouts.OBJECT_LOCK, null);
138+
if (lock != null) {
139+
return lock;
140+
} else {
141+
lock = new ReentrantLock();
142+
objectLibrary.put(object, Layouts.OBJECT_LOCK, lock);
143+
return lock;
144+
}
112145
}
113146
}
114147

0 commit comments

Comments
 (0)