Skip to content

Commit 7286f52

Browse files
author
Alan Bateman
committed
8322829: Refactor nioBlocker to avoid blocking while holding Thread's interrupt lock
Reviewed-by: jpai
1 parent 07fce8e commit 7286f52

File tree

6 files changed

+118
-54
lines changed

6 files changed

+118
-54
lines changed

src/java.base/share/classes/java/lang/System.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1994, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -2374,7 +2374,7 @@ E[] getEnumConstantsShared(Class<E> klass) {
23742374
return klass.getEnumConstantsShared();
23752375
}
23762376
public void blockedOn(Interruptible b) {
2377-
Thread.blockedOn(b);
2377+
Thread.currentThread().blockedOn(b);
23782378
}
23792379
public void registerShutdownHook(int slot, boolean registerShutdownInProgress, Runnable hook) {
23802380
Shutdown.add(slot, registerShutdownInProgress, hook);

src/java.base/share/classes/java/lang/Thread.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1994, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -345,15 +345,20 @@ void inheritScopedValueBindings(ThreadContainer container) {
345345
* operation, if any. The blocker's interrupt method should be invoked
346346
* after setting this thread's interrupt status.
347347
*/
348-
volatile Interruptible nioBlocker;
348+
private Interruptible nioBlocker;
349+
350+
Interruptible nioBlocker() {
351+
//assert Thread.holdsLock(interruptLock);
352+
return nioBlocker;
353+
}
349354

350355
/* Set the blocker field; invoked via jdk.internal.access.SharedSecrets
351356
* from java.nio code
352357
*/
353-
static void blockedOn(Interruptible b) {
354-
Thread me = Thread.currentThread();
355-
synchronized (me.interruptLock) {
356-
me.nioBlocker = b;
358+
void blockedOn(Interruptible b) {
359+
//assert Thread.currentThread() == this;
360+
synchronized (interruptLock) {
361+
nioBlocker = b;
357362
}
358363
}
359364

@@ -1699,15 +1704,19 @@ public void interrupt() {
16991704
checkAccess();
17001705

17011706
// thread may be blocked in an I/O operation
1707+
Interruptible blocker;
17021708
synchronized (interruptLock) {
1703-
Interruptible b = nioBlocker;
1704-
if (b != null) {
1709+
blocker = nioBlocker;
1710+
if (blocker != null) {
17051711
interrupted = true;
17061712
interrupt0(); // inform VM of interrupt
1707-
b.interrupt(this);
1708-
return;
1713+
blocker.interrupt(this);
17091714
}
17101715
}
1716+
if (blocker != null) {
1717+
blocker.postInterrupt();
1718+
return;
1719+
}
17111720
}
17121721
interrupted = true;
17131722
interrupt0(); // inform VM of interrupt

src/java.base/share/classes/java/lang/VirtualThread.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -852,18 +852,32 @@ boolean joinNanos(long nanos) throws InterruptedException {
852852
return true;
853853
}
854854

855+
@Override
856+
void blockedOn(Interruptible b) {
857+
notifyJvmtiDisableSuspend(true);
858+
try {
859+
super.blockedOn(b);
860+
} finally {
861+
notifyJvmtiDisableSuspend(false);
862+
}
863+
}
864+
855865
@Override
856866
@SuppressWarnings("removal")
857867
public void interrupt() {
858868
if (Thread.currentThread() != this) {
859869
checkAccess();
870+
871+
// if current thread is a virtual thread then prevent it from being
872+
// suspended when entering or holding interruptLock
873+
Interruptible blocker;
860874
notifyJvmtiDisableSuspend(true);
861875
try {
862876
synchronized (interruptLock) {
863877
interrupted = true;
864-
Interruptible b = nioBlocker;
865-
if (b != null) {
866-
b.interrupt(this);
878+
blocker = nioBlocker();
879+
if (blocker != null) {
880+
blocker.interrupt(this);
867881
}
868882

869883
// interrupt carrier thread if mounted
@@ -873,6 +887,11 @@ public void interrupt() {
873887
} finally {
874888
notifyJvmtiDisableSuspend(false);
875889
}
890+
891+
// notify blocker after releasing interruptLock
892+
if (blocker != null) {
893+
blocker.postInterrupt();
894+
}
876895
} else {
877896
interrupted = true;
878897
carrierThread.setInterrupt();

src/java.base/share/classes/java/nio/channels/spi/AbstractInterruptibleChannel.java

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -32,9 +32,9 @@
3232
import java.nio.channels.InterruptibleChannel;
3333

3434
import jdk.internal.access.SharedSecrets;
35+
import jdk.internal.misc.Unsafe;
3536
import sun.nio.ch.Interruptible;
3637

37-
3838
/**
3939
* Base implementation class for interruptible channels.
4040
*
@@ -89,10 +89,26 @@ public abstract class AbstractInterruptibleChannel
8989
private final Object closeLock = new Object();
9090
private volatile boolean closed;
9191

92+
// invoked if a Thread is interrupted when blocked in an I/O op
93+
private final Interruptible interruptor;
94+
9295
/**
9396
* Initializes a new instance of this class.
9497
*/
95-
protected AbstractInterruptibleChannel() { }
98+
protected AbstractInterruptibleChannel() {
99+
this.interruptor = new Interruptible() {
100+
@Override
101+
public void interrupt(Thread target) {
102+
AbstractInterruptibleChannel.this.trySetTarget(target);
103+
}
104+
@Override
105+
public void postInterrupt() {
106+
try {
107+
AbstractInterruptibleChannel.this.close();
108+
} catch (IOException x) { }
109+
}
110+
};
111+
}
96112

97113
/**
98114
* Closes this channel.
@@ -139,8 +155,15 @@ public final boolean isOpen() {
139155

140156
// -- Interruption machinery --
141157

142-
private Interruptible interruptor;
143-
private volatile Thread interrupted;
158+
private static final Unsafe U = Unsafe.getUnsafe();
159+
private static final long INTERRUPTED_TARGET =
160+
U.objectFieldOffset(AbstractInterruptibleChannel.class, "interruptedTarget");
161+
private volatile Object interruptedTarget; // Thread or placeholder object
162+
163+
private void trySetTarget(Thread target) {
164+
// can't use VarHandle here as CAS may park on first usage
165+
U.compareAndSetReference(this, INTERRUPTED_TARGET, null, target);
166+
}
144167

145168
/**
146169
* Marks the beginning of an I/O operation that might block indefinitely.
@@ -151,24 +174,12 @@ public final boolean isOpen() {
151174
* closing and interruption for this channel. </p>
152175
*/
153176
protected final void begin() {
154-
if (interruptor == null) {
155-
interruptor = new Interruptible() {
156-
public void interrupt(Thread target) {
157-
synchronized (closeLock) {
158-
if (closed)
159-
return;
160-
closed = true;
161-
interrupted = target;
162-
try {
163-
AbstractInterruptibleChannel.this.implCloseChannel();
164-
} catch (IOException x) { }
165-
}
166-
}};
167-
}
168177
blockedOn(interruptor);
169178
Thread me = Thread.currentThread();
170-
if (me.isInterrupted())
179+
if (me.isInterrupted()) {
171180
interruptor.interrupt(me);
181+
interruptor.postInterrupt();
182+
}
172183
}
173184

174185
/**
@@ -194,10 +205,14 @@ protected final void end(boolean completed)
194205
throws AsynchronousCloseException
195206
{
196207
blockedOn(null);
197-
Thread interrupted = this.interrupted;
198-
if (interrupted != null && interrupted == Thread.currentThread()) {
199-
this.interrupted = null;
200-
throw new ClosedByInterruptException();
208+
Object interruptedTarget = this.interruptedTarget;
209+
if (interruptedTarget != null) {
210+
interruptor.postInterrupt();
211+
if (interruptedTarget == Thread.currentThread()) {
212+
// replace with dummy object to avoid retaining reference to this thread
213+
this.interruptedTarget = new Object();
214+
throw new ClosedByInterruptException();
215+
}
201216
}
202217
if (!completed && closed)
203218
throw new AsynchronousCloseException();

src/java.base/share/classes/java/nio/channels/spi/AbstractSelector.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -89,6 +89,9 @@ public abstract class AbstractSelector
8989
// cancelled-key, not used by the JDK Selector implementations
9090
private final Set<SelectionKey> cancelledKeys;
9191

92+
// invoked if a Thread is interrupted when blocked on a selection op
93+
private final Interruptible interruptor;
94+
9295
/**
9396
* Initializes a new instance of this class.
9497
*
@@ -103,6 +106,15 @@ protected AbstractSelector(SelectorProvider provider) {
103106
} else {
104107
this.cancelledKeys = new HashSet<>();
105108
}
109+
this.interruptor = new Interruptible() {
110+
@Override
111+
public void interrupt(Thread ignore) {
112+
}
113+
@Override
114+
public void postInterrupt() {
115+
AbstractSelector.this.wakeup();
116+
}
117+
};
106118
}
107119

108120
void cancel(SelectionKey k) { // package-private
@@ -209,8 +221,6 @@ protected final void deregister(AbstractSelectionKey key) {
209221

210222
// -- Interruption machinery --
211223

212-
private Interruptible interruptor = null;
213-
214224
/**
215225
* Marks the beginning of an I/O operation that might block indefinitely.
216226
*
@@ -225,16 +235,11 @@ protected final void deregister(AbstractSelectionKey key) {
225235
* blocked in an I/O operation upon the selector. </p>
226236
*/
227237
protected final void begin() {
228-
if (interruptor == null) {
229-
interruptor = new Interruptible() {
230-
public void interrupt(Thread ignore) {
231-
AbstractSelector.this.wakeup();
232-
}};
233-
}
234238
AbstractInterruptibleChannel.blockedOn(interruptor);
235239
Thread me = Thread.currentThread();
236-
if (me.isInterrupted())
237-
interruptor.interrupt(me);
240+
if (me.isInterrupted()) {
241+
interruptor.postInterrupt();
242+
}
238243
}
239244

240245
/**
@@ -248,5 +253,4 @@ public void interrupt(Thread ignore) {
248253
protected final void end() {
249254
AbstractInterruptibleChannel.blockedOn(null);
250255
}
251-
252256
}

src/java.base/share/classes/sun/nio/ch/Interruptible.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,6 +31,23 @@
3131

3232
public interface Interruptible {
3333

34-
public void interrupt(Thread t);
34+
/**
35+
* Invoked by Thread.interrupt when the given Thread is interrupted. Thread.interrupt
36+
* invokes this method while holding the given Thread's interrupt lock. This method
37+
* is also invoked by AbstractInterruptibleChannel when beginning an I/O operation
38+
* with the current thread's interrupt status set. This method must not block.
39+
*/
40+
void interrupt(Thread target);
41+
42+
/**
43+
* Invoked by Thread.interrupt after releasing the Thread's interrupt lock.
44+
* It may also be invoked by AbstractInterruptibleChannel or AbstractSelector when
45+
* beginning an I/O operation with the current thread's interrupt status set, or at
46+
* the end of an I/O operation when any thread doing I/O on the channel (or selector)
47+
* has been interrupted. This method closes the channel (or wakes up the Selector) to
48+
* ensure that AsynchronousCloseException or ClosedByInterruptException is thrown.
49+
* This method is required to be idempotent.
50+
*/
51+
void postInterrupt();
3552

3653
}

0 commit comments

Comments
 (0)