Skip to content

Commit ace15eb

Browse files
committed
[JSPWIKI-1178] Address potential deadlock with JDK 21 Virtual Threads.
Refactored synchronized blocks/methods containing blocking operations to prevent potential deadlocks introduced by the upcoming Virtual Threads feature in JDK 21.
1 parent 37ea946 commit ace15eb

35 files changed

+1903
-872
lines changed

jspwiki-event/src/main/java/org/apache/wiki/event/WikiEventManager.java

+57-13
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Licensed to the Apache Software Foundation (ASF) under one
3333
import java.util.Set;
3434
import java.util.TreeSet;
3535
import java.util.Vector;
36+
import java.util.concurrent.locks.ReentrantLock;
3637

3738
/**
3839
* A singleton class that manages the addition and removal of WikiEvent listeners to a event source, as well as the firing of events
@@ -141,6 +142,17 @@ public final class WikiEventManager {
141142
/* Singleton instance of the WikiEventManager. */
142143
private static WikiEventManager c_instance;
143144

145+
/**
146+
* A lock used to ensure thread safety when accessing shared resources.
147+
* This lock provides more flexibility and capabilities than the intrinsic locking mechanism,
148+
* such as the ability to attempt to acquire a lock with a timeout, or to interrupt a thread
149+
* waiting to acquire a lock.
150+
*
151+
* @see java.util.concurrent.locks.ReentrantLock
152+
*/
153+
private static final ReentrantLock lock = new ReentrantLock();
154+
155+
144156
/** Constructor for a WikiEventManager. */
145157
private WikiEventManager() {
146158
c_instance = this;
@@ -154,10 +166,15 @@ private WikiEventManager() {
154166
* @return A shared instance of the WikiEventManager
155167
*/
156168
public static WikiEventManager getInstance() {
157-
if( c_instance == null ) {
158-
synchronized( WikiEventManager.class ) {
159-
return new WikiEventManager();
160-
// start up any post-instantiation services here
169+
if (c_instance == null) {
170+
lock.lock();
171+
try {
172+
if (c_instance == null) {
173+
c_instance = new WikiEventManager();
174+
// start up any post-instantiation services here
175+
}
176+
} finally {
177+
lock.unlock();
161178
}
162179
}
163180
return c_instance;
@@ -242,7 +259,8 @@ public static boolean removeWikiEventListener( final WikiEventListener listener
242259
// get the Map.entry object for the entire Map, then check match on entry (listener)
243260
final WikiEventManager mgr = getInstance();
244261
final Map< Object, WikiEventDelegate > sources = Collections.synchronizedMap( mgr.getDelegates() );
245-
synchronized( sources ) {
262+
lock.lock();
263+
try {
246264
// get an iterator over the Map.Enty objects in the map
247265
for( final Map.Entry< Object, WikiEventDelegate > entry : sources.entrySet() ) {
248266
// the entry value is the delegate
@@ -253,16 +271,24 @@ public static boolean removeWikiEventListener( final WikiEventListener listener
253271
removed = true; // was removed
254272
}
255273
}
274+
} finally {
275+
lock.unlock();
256276
}
257277
return removed;
258278
}
259279

260280
private void removeDelegates() {
261-
synchronized( m_delegates ) {
281+
lock.lock();
282+
try {
262283
m_delegates.clear();
284+
} finally {
285+
lock.unlock();
263286
}
264-
synchronized( m_preloadCache ) {
287+
lock.lock();
288+
try {
265289
m_preloadCache.clear();
290+
} finally {
291+
lock.unlock();
266292
}
267293
}
268294

@@ -315,7 +341,8 @@ private Map< Object, WikiEventDelegate > getDelegates() {
315341
* @return the WikiEventDelegate.
316342
*/
317343
private WikiEventDelegate getDelegateFor( final Object client ) {
318-
synchronized( m_delegates ) {
344+
lock.lock();
345+
try {
319346
if( client == null || client instanceof Class ) { // then preload the cache
320347
final WikiEventDelegate delegate = new WikiEventDelegate( client );
321348
m_preloadCache.add( delegate );
@@ -342,6 +369,8 @@ private WikiEventDelegate getDelegateFor( final Object client ) {
342369
m_delegates.put( client, delegate );
343370
}
344371
return delegate;
372+
} finally {
373+
lock.unlock();
345374
}
346375
}
347376

@@ -386,7 +415,8 @@ private static final class WikiEventDelegate {
386415
* @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set
387416
*/
388417
public Set< WikiEventListener > getWikiEventListeners() {
389-
synchronized( m_listenerList ) {
418+
lock.lock();
419+
try {
390420
final TreeSet< WikiEventListener > set = new TreeSet<>( new WikiEventListenerComparator() );
391421
for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) {
392422
final WikiEventListener l = wikiEventListenerWeakReference.get();
@@ -396,6 +426,8 @@ public Set< WikiEventListener > getWikiEventListeners() {
396426
}
397427

398428
return Collections.unmodifiableSet( set );
429+
} finally {
430+
lock.unlock();
399431
}
400432
}
401433

@@ -406,13 +438,16 @@ public Set< WikiEventListener > getWikiEventListeners() {
406438
* @return true if the listener was added (i.e., it was not already in the list and was added)
407439
*/
408440
public boolean addWikiEventListener( final WikiEventListener listener ) {
409-
synchronized( m_listenerList ) {
441+
lock.lock();
442+
try {
410443
final boolean listenerAlreadyContained = m_listenerList.stream()
411444
.map( WeakReference::get )
412445
.anyMatch( ref -> ref == listener );
413446
if( !listenerAlreadyContained ) {
414447
return m_listenerList.add( new WeakReference<>( listener ) );
415448
}
449+
} finally {
450+
lock.unlock();
416451
}
417452
return false;
418453
}
@@ -424,14 +459,17 @@ public boolean addWikiEventListener( final WikiEventListener listener ) {
424459
* @return true if the listener was removed (i.e., it was actually in the list and was removed)
425460
*/
426461
public boolean removeWikiEventListener( final WikiEventListener listener ) {
427-
synchronized( m_listenerList ) {
462+
lock.lock();
463+
try {
428464
for( final Iterator< WeakReference< WikiEventListener > > i = m_listenerList.iterator(); i.hasNext(); ) {
429465
final WikiEventListener l = i.next().get();
430466
if( l == listener ) {
431467
i.remove();
432468
return true;
433469
}
434470
}
471+
} finally {
472+
lock.unlock();
435473
}
436474

437475
return false;
@@ -441,8 +479,11 @@ public boolean removeWikiEventListener( final WikiEventListener listener ) {
441479
* Returns true if there are one or more listeners registered with this instance.
442480
*/
443481
public boolean isListening() {
444-
synchronized( m_listenerList ) {
482+
lock.lock();
483+
try {
445484
return !m_listenerList.isEmpty();
485+
} finally {
486+
lock.unlock();
446487
}
447488
}
448489

@@ -452,7 +493,8 @@ public boolean isListening() {
452493
public void fireEvent( final WikiEvent event ) {
453494
boolean needsCleanup = false;
454495
try {
455-
synchronized( m_listenerList ) {
496+
lock.lock();
497+
try {
456498
for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) {
457499
final WikiEventListener listener = wikiEventListenerWeakReference.get();
458500
if( listener != null ) {
@@ -472,6 +514,8 @@ public void fireEvent( final WikiEvent event ) {
472514
}
473515
}
474516

517+
} finally {
518+
lock.unlock();
475519
}
476520
} catch( final ConcurrentModificationException e ) {
477521
// We don't die, we just don't do notifications in that case.

jspwiki-kendra-searchprovider/src/main/java/org/apache/wiki/search/kendra/KendraSearchProvider.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Licensed to the Apache Software Foundation (ASF) under one
5555
import java.nio.ByteBuffer;
5656
import java.nio.charset.StandardCharsets;
5757
import java.util.*;
58+
import java.util.concurrent.locks.ReentrantLock;
5859

5960
import static java.lang.String.format;
6061

@@ -87,7 +88,18 @@ public class KendraSearchProvider implements SearchProvider {
8788
private static final String PROP_KENDRA_INDEXDELAY = "jspwiki.kendra.indexdelay";
8889
private static final String PROP_KENDRA_INITIALDELAY = "jspwiki.kendra.initialdelay";
8990

91+
/**
92+
* A lock used to ensure thread safety when accessing shared resources.
93+
* This lock provides more flexibility and capabilities than the intrinsic locking mechanism,
94+
* such as the ability to attempt to acquire a lock with a timeout, or to interrupt a thread
95+
* waiting to acquire a lock.
96+
*
97+
* @see java.util.concurrent.locks.ReentrantLock
98+
*/
99+
private final ReentrantLock lock;
100+
90101
public KendraSearchProvider() {
102+
lock = new ReentrantLock();
91103
}
92104

93105
/**
@@ -339,14 +351,17 @@ private void doPartialReindex() {
339351
}
340352
LOG.debug( "Indexing updated pages. Please wait ..." );
341353
final String executionId = startExecution();
342-
synchronized ( updates ) {
354+
lock.lock();
355+
try {
343356
try {
344357
while ( updates.size() > 0 ) {
345358
indexOnePage( updates.remove( 0 ), executionId );
346359
}
347360
} finally {
348361
stopExecution();
349362
}
363+
} finally {
364+
lock.unlock();
350365
}
351366
}
352367

jspwiki-main/src/main/java/org/apache/wiki/WatchDog.java

+41-8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Licensed to the Apache Software Foundation (ASF) under one
2828
import java.util.Set;
2929
import java.util.Stack;
3030
import java.util.concurrent.ConcurrentHashMap;
31+
import java.util.concurrent.locks.ReentrantLock;
3132

3233

3334
/**
@@ -57,6 +58,16 @@ public final class WatchDog {
5758
private static final Map< Integer, WeakReference< WatchDog > > c_kennel = new ConcurrentHashMap<>();
5859
private static WikiBackgroundThread c_watcherThread;
5960

61+
/**
62+
* A lock used to ensure thread safety when accessing shared resources.
63+
* This lock provides more flexibility and capabilities than the intrinsic locking mechanism,
64+
* such as the ability to attempt to acquire a lock with a timeout, or to interrupt a thread
65+
* waiting to acquire a lock.
66+
*
67+
* @see java.util.concurrent.locks.ReentrantLock
68+
*/
69+
private final ReentrantLock lock;
70+
6071
/**
6172
* Returns the current watchdog for the current thread. This is the preferred method of getting you a Watchdog, since it
6273
* keeps an internal list of Watchdogs for you so that there won't be more than one watchdog per thread.
@@ -92,11 +103,16 @@ public WatchDog( final Engine engine, final Watchable watch ) {
92103
m_engine = engine;
93104
m_watchable = watch;
94105

95-
synchronized( WatchDog.class ) {
106+
lock = new ReentrantLock();
107+
108+
lock.lock();
109+
try {
96110
if( c_watcherThread == null ) {
97111
c_watcherThread = new WatchDogThread( engine );
98112
c_watcherThread.start();
99113
}
114+
} finally {
115+
lock.unlock();
100116
}
101117
}
102118

@@ -136,25 +152,31 @@ private static void scrub() {
136152
* Can be used to enable the WatchDog. Will cause a new Thread to be created, if none was existing previously.
137153
*/
138154
public void enable() {
139-
synchronized( WatchDog.class ) {
155+
lock.lock();
156+
try {
140157
if( !m_enabled ) {
141158
m_enabled = true;
142159
c_watcherThread = new WatchDogThread( m_engine );
143160
c_watcherThread.start();
144161
}
162+
} finally {
163+
lock.unlock();
145164
}
146165
}
147166

148167
/**
149168
* Is used to disable a WatchDog. The watchdog thread is shut down and resources released.
150169
*/
151170
public void disable() {
152-
synchronized( WatchDog.class ) {
171+
lock.lock();
172+
try {
153173
if( m_enabled ) {
154174
m_enabled = false;
155175
c_watcherThread.shutdown();
156176
c_watcherThread = null;
157177
}
178+
} finally {
179+
lock.unlock();
158180
}
159181
}
160182

@@ -186,9 +208,12 @@ public void enterState( final String state ) {
186208
*/
187209
public void enterState( final String state, final int expectedCompletionTime ) {
188210
LOG.debug( "{}: Entering state {}, expected completion in {} s", m_watchable.getName(), state, expectedCompletionTime );
189-
synchronized( m_stateStack ) {
211+
lock.lock();
212+
try {
190213
final State st = new State( state, expectedCompletionTime );
191214
m_stateStack.push( st );
215+
} finally {
216+
lock.unlock();
192217
}
193218
}
194219

@@ -208,7 +233,8 @@ public void exitState() {
208233
*/
209234
public void exitState( final String state ) {
210235
if( !m_stateStack.empty() ) {
211-
synchronized( m_stateStack ) {
236+
lock.lock();
237+
try {
212238
final State st = m_stateStack.peek();
213239
if( state == null || st.getState().equals( state ) ) {
214240
m_stateStack.pop();
@@ -218,6 +244,8 @@ public void exitState( final String state ) {
218244
// FIXME: should actually go and fix things for that
219245
LOG.error( "exitState() called before enterState()" );
220246
}
247+
} finally {
248+
lock.unlock();
221249
}
222250
} else {
223251
LOG.warn( "Stack for " + m_watchable.getName() + " is empty!" );
@@ -244,8 +272,8 @@ public boolean isWatchableAlive() {
244272

245273
private void check() {
246274
LOG.debug( "Checking watchdog '{}'", m_watchable.getName() );
247-
248-
synchronized( m_stateStack ) {
275+
lock.lock();
276+
try {
249277
if( !m_stateStack.empty() ) {
250278
final State st = m_stateStack.peek();
251279
final long now = System.currentTimeMillis();
@@ -261,6 +289,8 @@ private void check() {
261289
} else {
262290
LOG.warn( "Stack for " + m_watchable.getName() + " is empty!" );
263291
}
292+
} finally {
293+
lock.unlock();
264294
}
265295
}
266296

@@ -302,14 +332,17 @@ private void dumpStackTraceForWatchable() {
302332
*/
303333
@Override
304334
public String toString() {
305-
synchronized( m_stateStack ) {
335+
lock.lock();
336+
try {
306337
String state = "Idle";
307338

308339
if( !m_stateStack.empty() ) {
309340
final State st = m_stateStack.peek();
310341
state = st.getState();
311342
}
312343
return "WatchDog state=" + state;
344+
} finally {
345+
lock.unlock();
313346
}
314347
}
315348

0 commit comments

Comments
 (0)