-
Notifications
You must be signed in to change notification settings - Fork 102
[JSPWIKI-1178] Address potential deadlock with JDK 21 Virtual Threads. #307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
arturobernalg
wants to merge
10
commits into
apache:master
Choose a base branch
from
arturobernalg:JSPWIKI-1178
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
e50fdc7
[JSPWIKI-1178] Address potential deadlock with JDK 21 Virtual Threads.
arturobernalg 3a83d94
Use custom Synchronizer class to manage ReentrantLock synchronization
arturobernalg 1fa8bd7
Move ReentrantLock instantiation to class level variables
juanpablo-santos fb83d61
Requested changes on PR and comments on ML
juanpablo-santos 5a34ccd
Requested changes
juanpablo-santos 88a75eb
Synchronize only on delegated getInstance method
juanpablo-santos 8e128cd
requested changes
juanpablo-santos 540ccd2
Move ReentrantLock instantiation to class level variable
juanpablo-santos 0fd016c
IndexWriter can cause sync issues only if its shared across threads, …
juanpablo-santos 15898f0
revert code change on PR to original while condition
juanpablo-santos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,18 +21,21 @@ Licensed to the Apache Software Foundation (ASF) under one | |
|
|
||
| import org.apache.logging.log4j.LogManager; | ||
| import org.apache.logging.log4j.Logger; | ||
| import org.apache.wiki.util.Synchronizer; | ||
|
|
||
| import java.lang.ref.WeakReference; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.Comparator; | ||
| import java.util.ConcurrentModificationException; | ||
| import java.util.HashMap; | ||
| import java.util.Iterator; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.TreeSet; | ||
| import java.util.Vector; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.CopyOnWriteArrayList; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.concurrent.locks.ReentrantLock; | ||
|
|
||
| /** | ||
| * A singleton class that manages the addition and removal of WikiEvent listeners to a event source, as well as the firing of events | ||
|
|
@@ -133,14 +136,25 @@ public final class WikiEventManager { | |
| private static WikiEventListener c_monitor; | ||
|
|
||
| /* The Map of client object to WikiEventDelegate. */ | ||
| private final Map< Object, WikiEventDelegate > m_delegates = new HashMap<>(); | ||
| private final Map< Object, WikiEventDelegate > m_delegates = new ConcurrentHashMap<>(); | ||
|
|
||
| /* The Vector containing any preloaded WikiEventDelegates. */ | ||
| private final Vector< WikiEventDelegate > m_preloadCache = new Vector<>(); | ||
|
|
||
| /* Singleton instance of the WikiEventManager. */ | ||
| private static WikiEventManager c_instance; | ||
|
|
||
| /* | ||
| * Locks used to ensure thread safety when accessing shared resources. | ||
| * This locks provide more flexibility and capabilities than the intrinsic locking mechanism, | ||
| * such as the ability to attempt to acquire a lock with a timeout, or to interrupt a thread | ||
| * waiting to acquire a lock. | ||
| * | ||
| * @see java.util.concurrent.locks.ReentrantLock | ||
| */ | ||
| private static final ReentrantLock instanceLock = new ReentrantLock(); | ||
| private static final ReentrantLock removeWikiEventListenerLock = new ReentrantLock(); | ||
|
|
||
| /** Constructor for a WikiEventManager. */ | ||
| private WikiEventManager() { | ||
| c_instance = this; | ||
|
|
@@ -154,11 +168,13 @@ private WikiEventManager() { | |
| * @return A shared instance of the WikiEventManager | ||
| */ | ||
| public static WikiEventManager getInstance() { | ||
| if( c_instance == null ) { | ||
| synchronized( WikiEventManager.class ) { | ||
| return new WikiEventManager(); | ||
| // start up any post-instantiation services here | ||
| } | ||
| if (c_instance == null) { | ||
| Synchronizer.synchronize(instanceLock, () -> { | ||
| if (c_instance == null) { | ||
| c_instance = new WikiEventManager(); | ||
| // start up any post-instantiation services here | ||
| } | ||
| }); | ||
| } | ||
| return c_instance; | ||
| } | ||
|
|
@@ -238,32 +254,28 @@ public static Set<WikiEventListener> getWikiEventListeners( final Object client | |
| * @return true if the listener was found and removed. | ||
| */ | ||
| public static boolean removeWikiEventListener( final WikiEventListener listener ) { | ||
| boolean removed = false; | ||
| final AtomicBoolean removed = new AtomicBoolean(false); | ||
| // get the Map.entry object for the entire Map, then check match on entry (listener) | ||
| final WikiEventManager mgr = getInstance(); | ||
| final Map< Object, WikiEventDelegate > sources = Collections.synchronizedMap( mgr.getDelegates() ); | ||
| synchronized( sources ) { | ||
| Synchronizer.synchronize(removeWikiEventListenerLock, () -> { | ||
| // get an iterator over the Map.Enty objects in the map | ||
| for( final Map.Entry< Object, WikiEventDelegate > entry : sources.entrySet() ) { | ||
| // the entry value is the delegate | ||
| final WikiEventDelegate delegate = entry.getValue(); | ||
|
|
||
| // now see if we can remove the listener from the delegate (delegate may be null because this is a weak reference) | ||
| if( delegate != null && delegate.removeWikiEventListener( listener ) ) { | ||
| removed = true; // was removed | ||
| removed.set(true); // was removed | ||
| } | ||
| } | ||
| } | ||
| return removed; | ||
| }); | ||
| return removed.get(); | ||
| } | ||
|
|
||
| private void removeDelegates() { | ||
| synchronized( m_delegates ) { | ||
| m_delegates.clear(); | ||
| } | ||
| synchronized( m_preloadCache ) { | ||
| m_preloadCache.clear(); | ||
| } | ||
| m_delegates.clear(); | ||
| m_preloadCache.clear(); | ||
| } | ||
|
|
||
| public static void shutdown() { | ||
|
|
@@ -314,35 +326,33 @@ private Map< Object, WikiEventDelegate > getDelegates() { | |
| * @param client the client Object, or alternately a Class reference | ||
| * @return the WikiEventDelegate. | ||
| */ | ||
| private WikiEventDelegate getDelegateFor( final Object client ) { | ||
| synchronized( m_delegates ) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, converting to |
||
| if( client == null || client instanceof Class ) { // then preload the cache | ||
| final WikiEventDelegate delegate = new WikiEventDelegate( client ); | ||
| m_preloadCache.add( delegate ); | ||
| m_delegates.put( client, delegate ); | ||
| return delegate; | ||
| } else if( !m_preloadCache.isEmpty() ) { | ||
| // then see if any of the cached delegates match the class of the incoming client | ||
| for( int i = m_preloadCache.size()-1 ; i >= 0 ; i-- ) { // start with most-recently added | ||
| final WikiEventDelegate delegate = m_preloadCache.elementAt( i ); | ||
| if( delegate.getClientClass() == null || delegate.getClientClass().equals( client.getClass() ) ) { | ||
| // we have a hit, so use it, but only on a client we haven't seen before | ||
| if( !m_delegates.containsKey( client ) ) { | ||
| m_preloadCache.remove( delegate ); | ||
| m_delegates.put( client, delegate ); | ||
| return delegate; | ||
| } | ||
| private WikiEventDelegate getDelegateFor(final Object client) { | ||
| if (client == null || client instanceof Class) { // then preload the cache | ||
| final WikiEventDelegate delegate = new WikiEventDelegate(client); | ||
| m_preloadCache.add(delegate); | ||
| m_delegates.put(client, delegate); | ||
| return delegate; | ||
| } else if (!m_preloadCache.isEmpty()) { | ||
| // then see if any of the cached delegates match the class of the incoming client | ||
| for (int i = m_preloadCache.size() - 1; i >= 0; i--) { // start with most-recently added | ||
| final WikiEventDelegate delegate = m_preloadCache.elementAt(i); | ||
| if (delegate.getClientClass() == null || delegate.getClientClass().equals(client.getClass())) { | ||
| // we have a hit, so use it, but only on a client we haven't seen before | ||
| if (!m_delegates.containsKey(client)) { | ||
| m_preloadCache.remove(delegate); | ||
| m_delegates.put(client, delegate); | ||
| return delegate; | ||
| } | ||
| } | ||
| } | ||
| // otherwise treat normally... | ||
| WikiEventDelegate delegate = m_delegates.get( client ); | ||
| if( delegate == null ) { | ||
| delegate = new WikiEventDelegate( client ); | ||
| m_delegates.put( client, delegate ); | ||
| } | ||
| return delegate; | ||
| } | ||
| // otherwise treat normally... | ||
| WikiEventDelegate delegate = m_delegates.get(client); | ||
| if (delegate == null) { | ||
| delegate = new WikiEventDelegate(client); | ||
| m_delegates.put(client, delegate); | ||
| } | ||
| return delegate; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -358,7 +368,7 @@ private WikiEventDelegate getDelegateFor( final Object client ) { | |
| private static final class WikiEventDelegate { | ||
|
|
||
| /* A list of event listeners for this instance. */ | ||
| private final ArrayList< WeakReference< WikiEventListener > > m_listenerList = new ArrayList<>(); | ||
| private final CopyOnWriteArrayList< WeakReference< WikiEventListener > > m_listenerList = new CopyOnWriteArrayList<>(); | ||
| private Class< ? > m_class; | ||
|
|
||
| /** | ||
|
|
@@ -386,17 +396,15 @@ private static final class WikiEventDelegate { | |
| * @throws java.lang.UnsupportedOperationException if any attempt is made to modify the Set | ||
| */ | ||
| public Set< WikiEventListener > getWikiEventListeners() { | ||
| synchronized( m_listenerList ) { | ||
| final TreeSet< WikiEventListener > set = new TreeSet<>( new WikiEventListenerComparator() ); | ||
| for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) { | ||
| final WikiEventListener l = wikiEventListenerWeakReference.get(); | ||
| if( l != null ) { | ||
| set.add( l ); | ||
| } | ||
| final TreeSet< WikiEventListener > set = new TreeSet<>( new WikiEventListenerComparator() ); | ||
| for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) { | ||
| final WikiEventListener l = wikiEventListenerWeakReference.get(); | ||
| if( l != null ) { | ||
| set.add( l ); | ||
| } | ||
|
|
||
| return Collections.unmodifiableSet( set ); | ||
| } | ||
|
|
||
| return Collections.unmodifiableSet( set ); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -406,13 +414,11 @@ public Set< WikiEventListener > getWikiEventListeners() { | |
| * @return true if the listener was added (i.e., it was not already in the list and was added) | ||
| */ | ||
| public boolean addWikiEventListener( final WikiEventListener listener ) { | ||
| synchronized( m_listenerList ) { | ||
| final boolean listenerAlreadyContained = m_listenerList.stream() | ||
| .map( WeakReference::get ) | ||
| .anyMatch( ref -> ref == listener ); | ||
| if( !listenerAlreadyContained ) { | ||
| return m_listenerList.add( new WeakReference<>( listener ) ); | ||
| } | ||
| final boolean listenerAlreadyContained = m_listenerList.stream() | ||
| .map( WeakReference::get ) | ||
| .anyMatch( ref -> ref == listener ); | ||
| if( !listenerAlreadyContained ) { | ||
| return m_listenerList.add( new WeakReference<>( listener ) ); | ||
| } | ||
| return false; | ||
| } | ||
|
|
@@ -424,26 +430,21 @@ public boolean addWikiEventListener( final WikiEventListener listener ) { | |
| * @return true if the listener was removed (i.e., it was actually in the list and was removed) | ||
| */ | ||
| public boolean removeWikiEventListener( final WikiEventListener listener ) { | ||
| synchronized( m_listenerList ) { | ||
| for( final Iterator< WeakReference< WikiEventListener > > i = m_listenerList.iterator(); i.hasNext(); ) { | ||
| final WikiEventListener l = i.next().get(); | ||
| if( l == listener ) { | ||
| i.remove(); | ||
| return true; | ||
| } | ||
| for (final Iterator<WeakReference<WikiEventListener>> i = m_listenerList.iterator(); i.hasNext(); ) { | ||
| final WikiEventListener l = i.next().get(); | ||
| if (l == listener) { | ||
| i.remove(); | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if there are one or more listeners registered with this instance. | ||
| */ | ||
| public boolean isListening() { | ||
| synchronized( m_listenerList ) { | ||
| return !m_listenerList.isEmpty(); | ||
| } | ||
| return !m_listenerList.isEmpty(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -452,29 +453,26 @@ public boolean isListening() { | |
| public void fireEvent( final WikiEvent event ) { | ||
| boolean needsCleanup = false; | ||
| try { | ||
| synchronized( m_listenerList ) { | ||
| for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) { | ||
| final WikiEventListener listener = wikiEventListenerWeakReference.get(); | ||
| if( listener != null ) { | ||
| listener.actionPerformed( event ); | ||
| } else { | ||
| needsCleanup = true; | ||
| } | ||
| for( final WeakReference< WikiEventListener > wikiEventListenerWeakReference : m_listenerList ) { | ||
| final WikiEventListener listener = wikiEventListenerWeakReference.get(); | ||
| if( listener != null ) { | ||
| listener.actionPerformed( event ); | ||
| } else { | ||
| needsCleanup = true; | ||
| } | ||
| } | ||
|
|
||
| // Remove all such listeners which have expired | ||
| if( needsCleanup ) { | ||
| for( int i = 0; i < m_listenerList.size(); i++ ) { | ||
| final WeakReference< WikiEventListener > w = m_listenerList.get( i ); | ||
| if( w.get() == null ) { | ||
| m_listenerList.remove(i--); | ||
| } | ||
| // Remove all such listeners which have expired | ||
| if( needsCleanup ) { | ||
| for( int i = 0; i < m_listenerList.size(); i++ ) { | ||
| final WeakReference< WikiEventListener > w = m_listenerList.get( i ); | ||
| if( w.get() == null ) { | ||
| m_listenerList.remove( i-- ); | ||
| } | ||
| } | ||
|
|
||
| } | ||
| } catch( final ConcurrentModificationException e ) { | ||
| // We don't die, we just don't do notifications in that case. | ||
| } catch ( final ConcurrentModificationException e ) { | ||
| // We don't die, we just don't do notifications in that case. | ||
| LOG.info( "Concurrent modification of event list; please report this.", e ); | ||
| } | ||
| } | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.