Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions core/src/main/java/org/jruby/RubyThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,7 @@ public void lockInterruptibly(Lock lock) throws InterruptedException {
@Override
public Object run(ThreadContext context, Lock reentrantLock) throws InterruptedException {
reentrantLock.lockInterruptibly();
heldLocks.add(lock);
return reentrantLock;
}

Expand All @@ -2174,7 +2175,6 @@ public void wakeup(RubyThread thread, Lock reentrantLock) {
thread.getNativeThread().interrupt();
}
});
heldLocks.add(lock);
}

/**
Expand Down Expand Up @@ -2211,7 +2211,12 @@ public void unlock(Lock lock) {
public void unlockAll() {
assert Thread.currentThread() == getNativeThread();
for (Lock lock : heldLocks) {
lock.unlock();
try {
lock.unlock();
} catch (IllegalMonitorStateException imse) {
// don't allow a bad lock to prevent others from unlocking
getRuntime().getWarnings().warn("BUG: attempted to unlock a non-acquired lock " + lock + " in thread " + toString());
}
}
}

Expand Down
33 changes: 33 additions & 0 deletions spec/regression/GH-6405_race_in_interruptible_lock_acquisition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Acquired locks must go into the thread's lock list, so they are released when the thread terminates.
# The following code simulates threads attempting to lock at the same time another thread (main) is
# trying to kill them. This led to the regression behind #6405 and #6326 where the kill event would
# interrupt the thread before it had added the locked lock to its lock list, resulting in it remaining
# locked forever.
describe "A Thread that acquires a lock immediately before being killed" do
it "releases that lock when killed" do
def rand_sleep
sleep(rand / 500)
end

mutex = Mutex.new
200.times do
ts = 3.times.map do
Thread.new do
rand_sleep
mutex.synchronize { rand_sleep }
end
end
3.times do
rand_sleep
ts.sample.kill
end

# lock in main thread to force some threads to wait
mutex.synchronize {}

ts.each(&:join)
end

expect(mutex).to_not be_locked
end
end