Skip to content

Commit 15b187f

Browse files
committed
Critical section support
Fixes #485
1 parent 9fe15df commit 15b187f

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package io.smallrye.common.process;
2+
3+
import java.lang.invoke.ConstantBootstraps;
4+
import java.lang.invoke.MethodHandles;
5+
import java.lang.invoke.VarHandle;
6+
import java.util.concurrent.CopyOnWriteArraySet;
7+
import java.util.concurrent.locks.LockSupport;
8+
9+
/**
10+
* Methods for managing "critical sections" of code.
11+
* A "critical section" is defined as any program subunit within a thread which could have
12+
* a deleterious impact on the state of the running system if
13+
* interrupted by process exit partway through execution.
14+
* <p>
15+
* This is intended to be used with a {@code try}-with-resources construct
16+
* in order to ensure that the critical section is safely concluded.
17+
* If the returned handle is not properly used to {@code close()} the critical section,
18+
* the process may never exit.
19+
*/
20+
public final class CriticalSection {
21+
private static final CriticalSection INSTANCE = new CriticalSection();
22+
private static final Thread shutdownThread = new Thread(CriticalSection::run, "Critical section exit thread");
23+
private static final VarHandle stateHandle = ConstantBootstraps.fieldVarHandle(MethodHandles.lookup(), "state",
24+
VarHandle.class, CriticalSection.class, long.class);
25+
private static final CopyOnWriteArraySet<Thread> interruptibleThreads = new CopyOnWriteArraySet<>();
26+
27+
private static final long ST_EXITING = 1L << 63;
28+
private static final long CNT_MASK = -1L & ~ST_EXITING;
29+
30+
static {
31+
Runtime.getRuntime().addShutdownHook(shutdownThread);
32+
}
33+
34+
private CriticalSection() {
35+
}
36+
37+
@SuppressWarnings("unused")
38+
private long state;
39+
40+
static void run() {
41+
long oldVal = (long) stateHandle.getAndBitwiseOr(ST_EXITING) & CNT_MASK;
42+
while (oldVal > 0) {
43+
interruptibleThreads.forEach(Thread::interrupt);
44+
// clear interrupted flag
45+
Thread.interrupted();
46+
LockSupport.park(INSTANCE);
47+
oldVal = (long) stateHandle.getVolatile() & CNT_MASK;
48+
}
49+
// done
50+
}
51+
52+
/**
53+
* Attempt to enter a critical section.
54+
* If the process is shutting down, and no other critical sections are entered,
55+
* this method does not return.
56+
*
57+
* @return a handle to use to exit the critical section (not {@code null})
58+
*/
59+
public static Closer enter() {
60+
tryEnter();
61+
return new Closer(false);
62+
}
63+
64+
/**
65+
* Attempt to enter a critical section.
66+
* If the process is shutting down, and no other critical sections are entered,
67+
* this method does not return.
68+
* Otherwise, this thread will be interrupted no less than one time when the
69+
* process is exiting.
70+
*
71+
* @return a handle to use to exit the critical section (not {@code null})
72+
*/
73+
public static Closer enterInterruptibly() {
74+
long oldVal = tryEnter();
75+
if ((oldVal & ST_EXITING) != 0) {
76+
Thread.currentThread().interrupt();
77+
}
78+
// if this thread is already in the set, then an outer block carries the interruptible status
79+
return new Closer(interruptibleThreads.add(Thread.currentThread()));
80+
}
81+
82+
private static long tryEnter() {
83+
long oldVal = (long) stateHandle.getVolatile();
84+
for (;;) {
85+
if (oldVal == ST_EXITING) {
86+
// no critical sections and exit was requested, so shutdown will commence soon
87+
for (;;) {
88+
Thread.interrupted();
89+
LockSupport.park(INSTANCE);
90+
}
91+
} else {
92+
long witness = (long) stateHandle.compareAndExchange(oldVal, oldVal + 1);
93+
if (witness == oldVal) {
94+
break;
95+
}
96+
oldVal = witness;
97+
}
98+
}
99+
return oldVal;
100+
}
101+
102+
/**
103+
* The close handle for the critical section.
104+
*/
105+
public static final class Closer implements AutoCloseable {
106+
private static final VarHandle closedHandle = ConstantBootstraps.fieldVarHandle(MethodHandles.lookup(), "closed",
107+
VarHandle.class, Closer.class, boolean.class);
108+
109+
private final boolean interruptible;
110+
111+
@SuppressWarnings("unused")
112+
private boolean closed;
113+
114+
private Closer(final boolean interruptible) {
115+
this.interruptible = interruptible;
116+
}
117+
118+
/**
119+
* Finish the critical section (idempotent).
120+
*/
121+
public void close() {
122+
if (closedHandle.compareAndSet(this, false, true)) {
123+
long oldVal = (long) stateHandle.getAndAdd(-1L);
124+
if (oldVal == ST_EXITING + 1) {
125+
// we were the last one, so wake up the runner
126+
LockSupport.unpark(shutdownThread);
127+
}
128+
if (interruptible) {
129+
interruptibleThreads.remove(Thread.currentThread());
130+
}
131+
}
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)