Skip to content

Commit 1adb157

Browse files
committed
[GR-20089] Always use condLock.lock() for locking the ConditionVariable's ReentrantLock.
PullRequest: truffleruby/1184
2 parents 3b0754a + 5d9f537 commit 1adb157

File tree

3 files changed

+41
-47
lines changed

3 files changed

+41
-47
lines changed

src/main/java/org/truffleruby/core/mutex/ConditionVariableNodes.java

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import org.truffleruby.builtins.PrimitiveArrayArgumentsNode;
2323
import org.truffleruby.core.thread.GetCurrentRubyThreadNode;
2424
import org.truffleruby.core.thread.ThreadManager;
25-
import org.truffleruby.core.thread.ThreadManager.BlockingAction;
2625
import org.truffleruby.core.thread.ThreadStatus;
2726
import org.truffleruby.language.Visibility;
2827
import org.truffleruby.language.objects.AllocateObjectNode;
@@ -44,8 +43,12 @@ public abstract static class AllocateNode extends CoreMethodArrayArgumentsNode {
4443

4544
@Specialization
4645
protected DynamicObject allocate(DynamicObject rubyClass) {
47-
ReentrantLock lock = new ReentrantLock();
48-
return allocateNode.allocate(rubyClass, lock, lock.newCondition(), 0, 0);
46+
// condLock is only held for a short number of non-blocking instructions,
47+
// so there is no need to poll for safepoints while locking it.
48+
// It is an internal lock and so locking should be done with condLock.lock()
49+
// to avoid changing the Ruby Thread status and consume Java thread interrupts.
50+
final ReentrantLock condLock = new ReentrantLock();
51+
return allocateNode.allocate(rubyClass, condLock, condLock.newCondition(), 0, 0);
4952
}
5053

5154
}
@@ -95,29 +98,34 @@ private void waitInternal(DynamicObject conditionVariable, ReentrantLock mutexLo
9598
// it should only be considered if we are inside Mutex#sleep when Thread#{run,wakeup} is called.
9699
Layouts.THREAD.getWakeUp(thread).set(false);
97100

98-
// condLock must be locked before unlocking mutexLock, to avoid losing potential signals
99-
getContext().getThreadManager().runUntilResult(this, () -> {
100-
condLock.lockInterruptibly();
101-
return BlockingAction.SUCCESS;
102-
});
103-
mutexLock.unlock();
104-
105-
Layouts.CONDITION_VARIABLE
106-
.setWaiters(conditionVariable, Layouts.CONDITION_VARIABLE.getWaiters(conditionVariable) + 1);
101+
// condLock must be locked before unlocking mutexLock, to avoid losing potential signals.
102+
// We must not change the Ruby Thread status and not consume a Java thread interrupt while locking condLock.
103+
// If there is an interrupt, it should be consumed by condition.await() and the Ruby Thread sleep status
104+
// must imply being ready to be interrupted by Thread#{run,wakeup}.
105+
condLock.lock();
107106
try {
108-
awaitSignal(conditionVariable, thread, durationInNanos, condLock, condition, endNanoTime);
109-
} catch (Error | RuntimeException e) {
110-
/*
111-
* Consume a signal if one was waiting. We do this because the error may have
112-
* occurred while we were waiting, or at some point after exiting a safepoint that
113-
* throws an exception and another thread has attempted to signal us. It is valid
114-
* for us to consume this signal because we are still marked as waiting for it.
115-
*/
116-
consumeSignal(conditionVariable);
117-
throw e;
107+
mutexLock.unlock();
108+
109+
Layouts.CONDITION_VARIABLE.setWaiters(
110+
conditionVariable,
111+
Layouts.CONDITION_VARIABLE.getWaiters(conditionVariable) + 1);
112+
try {
113+
awaitSignal(conditionVariable, thread, durationInNanos, condLock, condition, endNanoTime);
114+
} catch (Error | RuntimeException e) {
115+
/*
116+
* Consume a signal if one was waiting. We do this because the error may have
117+
* occurred while we were waiting, or at some point after exiting a safepoint that
118+
* throws an exception and another thread has attempted to signal us. It is valid
119+
* for us to consume this signal because we are still marked as waiting for it.
120+
*/
121+
consumeSignal(conditionVariable);
122+
throw e;
123+
} finally {
124+
Layouts.CONDITION_VARIABLE.setWaiters(
125+
conditionVariable,
126+
Layouts.CONDITION_VARIABLE.getWaiters(conditionVariable) - 1);
127+
}
118128
} finally {
119-
Layouts.CONDITION_VARIABLE
120-
.setWaiters(conditionVariable, Layouts.CONDITION_VARIABLE.getWaiters(conditionVariable) - 1);
121129
condLock.unlock();
122130
MutexOperations.internalLockEvenWithException(mutexLock, this, getContext());
123131
}
@@ -205,13 +213,7 @@ protected DynamicObject signal(DynamicObject self) {
205213
final ReentrantLock condLock = Layouts.CONDITION_VARIABLE.getLock(self);
206214
final Condition condition = Layouts.CONDITION_VARIABLE.getCondition(self);
207215

208-
if (!condLock.tryLock()) {
209-
getContext().getThreadManager().runUntilResult(this, () -> {
210-
condLock.lockInterruptibly();
211-
return BlockingAction.SUCCESS;
212-
});
213-
}
214-
216+
condLock.lock();
215217
try {
216218
if (Layouts.CONDITION_VARIABLE.getWaiters(self) > 0) {
217219
Layouts.CONDITION_VARIABLE.setSignals(self, Layouts.CONDITION_VARIABLE.getSignals(self) + 1);
@@ -234,13 +236,7 @@ protected DynamicObject broadcast(DynamicObject self) {
234236
final ReentrantLock condLock = Layouts.CONDITION_VARIABLE.getLock(self);
235237
final Condition condition = Layouts.CONDITION_VARIABLE.getCondition(self);
236238

237-
if (!condLock.tryLock()) {
238-
getContext().getThreadManager().runUntilResult(this, () -> {
239-
condLock.lockInterruptibly();
240-
return BlockingAction.SUCCESS;
241-
});
242-
}
243-
239+
condLock.lock();
244240
try {
245241
if (Layouts.CONDITION_VARIABLE.getWaiters(self) > 0) {
246242
Layouts.CONDITION_VARIABLE.setSignals(

src/main/java/org/truffleruby/core/mutex/MutexOperations.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,20 @@ public static void lockInternal(RubyContext context, ReentrantLock lock, RubyNod
4444

4545
@TruffleBoundary
4646
protected static void lockEvenWithExceptions(ReentrantLock lock, DynamicObject thread, RubyNode currentNode) {
47-
final RubyContext context = currentNode.getContext();
48-
49-
if (lock.isHeldByCurrentThread()) {
50-
throw new RaiseException(context, context.getCoreExceptions().threadErrorRecursiveLocking(currentNode));
51-
}
52-
5347
// We need to re-lock this lock after a Mutex#sleep, no matter what, even if another thread throw us an exception.
5448
// Yet, we also need to allow safepoints to happen otherwise the thread that could unlock could be blocked.
5549
try {
56-
internalLockEvenWithException(lock, currentNode, context);
50+
internalLockEvenWithException(lock, currentNode, currentNode.getContext());
5751
} finally {
5852
Layouts.THREAD.getOwnedLocks(thread).add(lock);
5953
}
6054
}
6155

6256
protected static void internalLockEvenWithException(ReentrantLock lock, RubyNode currentNode, RubyContext context) {
57+
if (lock.isHeldByCurrentThread()) {
58+
throw new RaiseException(context, context.getCoreExceptions().threadErrorRecursiveLocking(currentNode));
59+
}
60+
6361
if (lock.tryLock()) {
6462
return;
6563
}

src/main/java/org/truffleruby/core/thread/ThreadNodes.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,9 +454,9 @@ protected DynamicObject wakeup(DynamicObject rubyThread) {
454454
throw new RaiseException(getContext(), coreExceptions().threadErrorKilledThread(this));
455455
}
456456

457+
// This only interrupts Kernel#sleep, Mutex#sleep and ConditionVariable#wait by having those check for the
458+
// wakeup flag. Other operations just retry when interrupted.
457459
Layouts.THREAD.getWakeUp(rubyThread).set(true);
458-
459-
// TODO: should only interrupt sleep
460460
getContext().getThreadManager().interrupt(thread);
461461

462462
return rubyThread;

0 commit comments

Comments
 (0)