Skip to content

Commit bc95d40

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

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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", VarHandle.class, CriticalSection.class, long.class);
24+
private static final CopyOnWriteArraySet<Thread> interruptibleThreads = new CopyOnWriteArraySet<>();
25+
26+
private static final long ST_EXITING = 1L << 63;
27+
private static final long CNT_MASK = -1L & ~ST_EXITING;
28+
29+
static {
30+
Runtime.getRuntime().addShutdownHook(shutdownThread);
31+
}
32+
33+
private CriticalSection() {}
34+
35+
@SuppressWarnings("unused")
36+
private long state;
37+
38+
static void run() {
39+
long oldVal = (long) stateHandle.getAndBitwiseOr(ST_EXITING) & CNT_MASK;
40+
while (oldVal > 0) {
41+
interruptibleThreads.forEach(Thread::interrupt);
42+
// clear interrupted flag
43+
Thread.interrupted();
44+
LockSupport.park(INSTANCE);
45+
oldVal = (long) stateHandle.getVolatile() & CNT_MASK;
46+
}
47+
// done
48+
}
49+
50+
/**
51+
* Attempt to enter a critical section.
52+
* If the process is shutting down, and no other critical sections are entered,
53+
* this method does not return.
54+
*
55+
* @return a handle to use to exit the critical section (not {@code null})
56+
*/
57+
public static Closer enter() {
58+
tryEnter();
59+
return new Closer(false);
60+
}
61+
62+
/**
63+
* Attempt to enter a critical section.
64+
* If the process is shutting down, and no other critical sections are entered,
65+
* this method does not return.
66+
* Otherwise, this thread will be interrupted no less than one time when the
67+
* process is exiting.
68+
*
69+
* @return a handle to use to exit the critical section (not {@code null})
70+
*/
71+
public static Closer enterInterruptibly() {
72+
long oldVal = tryEnter();
73+
if ((oldVal & ST_EXITING) != 0) {
74+
Thread.currentThread().interrupt();
75+
}
76+
// if this thread is already in the set, then an outer block carries the interruptible status
77+
return new Closer(interruptibleThreads.add(Thread.currentThread()));
78+
}
79+
80+
private static long tryEnter() {
81+
long oldVal = (long) stateHandle.getVolatile();
82+
for (;;) {
83+
if (oldVal == ST_EXITING) {
84+
// no critical sections and exit was requested, so shutdown will commence soon
85+
for (;;) {
86+
Thread.interrupted();
87+
LockSupport.park(INSTANCE);
88+
}
89+
} else {
90+
long witness = (long) stateHandle.compareAndExchange(oldVal, oldVal + 1);
91+
if (witness == oldVal) {
92+
break;
93+
}
94+
oldVal = witness;
95+
}
96+
}
97+
return oldVal;
98+
}
99+
100+
/**
101+
* The close handle for the critical section.
102+
*/
103+
public static final class Closer implements AutoCloseable {
104+
private static final VarHandle closedHandle = ConstantBootstraps.fieldVarHandle(MethodHandles.lookup(), "closed", VarHandle.class, Closer.class, boolean.class);
105+
106+
private final boolean interruptible;
107+
108+
@SuppressWarnings("unused")
109+
private boolean closed;
110+
111+
private Closer(final boolean interruptible) {
112+
this.interruptible = interruptible;
113+
}
114+
115+
/**
116+
* Finish the critical section (idempotent).
117+
*/
118+
public void close() {
119+
if (closedHandle.compareAndSet(this, false, true)) {
120+
long oldVal = (long) stateHandle.getAndAdd(-1L);
121+
if (oldVal == ST_EXITING + 1) {
122+
// we were the last one, so wake up the runner
123+
LockSupport.unpark(shutdownThread);
124+
}
125+
if (interruptible) {
126+
interruptibleThreads.remove(Thread.currentThread());
127+
}
128+
}
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)