package com.maxifier.guice.events; import com.google.inject.Inject; import com.google.inject.Singleton; import gnu.trove.map.hash.THashMap; import gnu.trove.set.hash.THashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.ref.ReferenceQueue; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; @Singleton public class EventDispatcherImpl implements EventDispatcher { private static final Logger LOG = LoggerFactory.getLogger(EventDispatcherImpl.class); private final Map<Class, List<HandlerMethodInstance>> mapping; private final Map<Class, ListenerClassInstance> classInfos; private final Queue<Object> registrationQueue = new LinkedList<Object>(); private final ReferenceQueue queue; private final Lock readLock; private final Lock writeLock; @SuppressWarnings({"unchecked"}) @Inject public EventDispatcherImpl(ListenerRegistrationQueue q) { classInfos = new THashMap(); mapping = new THashMap(); queue = new ReferenceQueue(); final Thread t = new Thread("EventDispatcher reference watcher thread") { @Override public void run() { while (!isInterrupted()) { try { ((ListenerReference<?>) queue.remove()).cleanUp(); } catch (InterruptedException e) { return; } } } }; t.setDaemon(true); t.start(); ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); readLock = rwl.readLock(); writeLock = rwl.writeLock(); q.setDispatcher(this); } private final ThreadLocal<Boolean> isLocked = new ThreadLocal<Boolean>(); private volatile int firingEvents; @Override public Map<Class, List<? extends HandlerMethodInfo>> getHandlersByEventClass() { // noinspection unchecked,RedundantCast return (Map) Collections.unmodifiableMap(mapping); } @SuppressWarnings({"unchecked"}) @Override public Map<Class, List<? extends HandlerMethodInfo>> getHandlersByListenerClass() { Map<Class, List<? extends HandlerMethodInfo>> res = new THashMap(); for (Map.Entry<Class, ListenerClassInstance> entry : classInfos.entrySet()) { // noinspection unchecked res.put(entry.getKey(), entry.getValue().getHandlers()); } return res; } @Override //NOSONAR public void fireEvent(Object event) { boolean handled; synchronized (registrationQueue) { firingEvents++; } try { if (isLocked.get() == null) { readLock.lock(); isLocked.set(Boolean.TRUE); try { handled = fireEvent0(event); } finally { readLock.unlock(); isLocked.remove(); } } else { handled = fireEvent0(event); } } finally { synchronized (registrationQueue) { firingEvents--; if (firingEvents == 0) { while (!registrationQueue.isEmpty()) { register0(registrationQueue.poll()); } } } } if (!handled) { unhandledEvent(event); } } //NOSONAR private boolean fireEvent0(Object event) { Class c = event.getClass(); List<HandlerMethodInstance> l = getHandlerMethodInstances(c); boolean handled = false; for (HandlerMethodInstance<?> method : l) { handled |= method.invokeIfMatched(event); } return handled; } private List<HandlerMethodInstance> getHandlerMethodInstances(Class c) { List<HandlerMethodInstance> l = mapping.get(c); if (l == null) { readLock.unlock(); if (writeLock.tryLock()) { try { l = mapping.get(c); if (l == null) { l = getHandlerMethodInstances0(c); mapping.put(c, l); } } finally { readLock.lock(); writeLock.unlock(); } } else { readLock.lock(); // https://jira.maxifier.com/browse/XGUICE-30 // // If we can't acquire write lock, that might mean that another thread is holding it. // // Example of inverted stack trace: // Thread 1: // -fireEvent - holds readLock // -fireEvent0 // -someHandler - tries to get lock X, but it has to wait for Thread 2. // // Thread 2: // -someMethod - holds lock X // -fireEvent - holds readLock // -fireEvent0 - releases readLock // if event class never met before, it will waits for Thread 1 to acquire writeLock. // // In this case if we want to avoid deadlock, we will calculate list of methods, but not // store it anywhere. Anyway, if intensity of queries is not too high, the cache will be filled. l = getHandlerMethodInstances0(c); } } return l; } // This method doesn't do any caching, don't call it unless you know what you are doing. private List<HandlerMethodInstance> getHandlerMethodInstances0(Class c) { List<HandlerMethodInstance> res = new ArrayList<HandlerMethodInstance>(); for (ListenerClassInstance<?> cls : classInfos.values()) { cls.bindHandlers(res, c); } return res; } protected void unhandledEvent(Object event) { LOG.warn("Event " + event + " of class " + event.getClass() + " was not processed"); } @Override public final <T> void register(T o) throws CyclicFilterAnnotationException { synchronized (registrationQueue) { if (firingEvents == 0) { register0(o); } else { registrationQueue.add(o); } } } @SuppressWarnings({"unchecked"}) private <T> void register0(T o) { Class<T> c = (Class<T>) o.getClass(); //noinspection unchecked ListenerClassInstance<T> listenerClass = classInfos.get(c); if (listenerClass == null) { Class<?> currentClass = c; Set<Class<?>> classes = new THashSet(); List<ListenerClass<?>> lc = new ArrayList<ListenerClass<?>>(classes.size()); do { if (classes.add(currentClass)) { ListenerClass<?> l = EventReflectionParser.getOrCreateClassInfo(currentClass); if (l.hasHandlers()) { lc.add(l); } } for (Class<?> interf : currentClass.getInterfaces()) { if (classes.add(interf)) { ListenerClass<?> l = EventReflectionParser.getOrCreateClassInfo(interf); if (l.hasHandlers()) { lc.add(l); } } } currentClass = currentClass.getSuperclass(); } while (currentClass != null); //noinspection unchecked listenerClass = new ListenerClassInstance<T>(c, queue, lc); listenerClass.bindHandlers(mapping); classInfos.put(c, listenerClass); } listenerClass.addListener(o); } }