package org.dcache.pool.repository.v5; import com.google.common.util.concurrent.MoreExecutors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import org.dcache.pool.repository.EntryChangeEvent; import org.dcache.pool.repository.StateChangeEvent; import org.dcache.pool.repository.StateChangeListener; import org.dcache.pool.repository.StickyChangeEvent; class StateChangeListeners { private static final Logger _log = LoggerFactory.getLogger(StateChangeListeners.class); private final List<StateChangeListener> _listeners = new CopyOnWriteArrayList<>(); /** * Background thread for event processing. It is on purpose that * this is a single thread: The point is not to process events * quickly but rather to process them sequentially and * independently from the thread that triggered the event. */ private final ExecutorService _executorService = Executors.newSingleThreadExecutor(); private Executor _executor; public StateChangeListeners() { _executor = _executorService; } public void setSynchronousNotification(boolean value) { _executor = value ? MoreExecutors.directExecutor() : _executorService; } public void add(StateChangeListener listener) { _listeners.add(listener); } public void remove(StateChangeListener listener) { _listeners.remove(listener); } public void stateChanged(final StateChangeEvent event) { try { _executor.execute(() -> { for (StateChangeListener listener: _listeners) { try { listener.stateChanged(event); } catch (RuntimeException e) { /* State change notifications are * important for proper functioning of the * pool and we cannot risk a problem in an * event handler causing other event * handlers not to be called. We therefore * catch, log and ignore these problems. */ _log.error("Unexpected failure during state change notification", e); } } }); } catch (RejectedExecutionException e) { // Happens when executor is already shut down _log.debug("Dropping repository state change notification: {}", e.getMessage()); } } public void accessTimeChanged(final EntryChangeEvent event) { try { _executor.execute(() -> { for (StateChangeListener listener: _listeners) { try { listener.accessTimeChanged(event); } catch (RuntimeException e) { /* State change notifications are * important for proper functioning of the * pool and we cannot risk a problem in an * event handler causing other event * handlers not to be called. We therefore * catch, log and ignore these problems. */ _log.error("Unexpected failure during state change notification", e); } } }); } catch (RejectedExecutionException e) { // Happens when executor is already shut down _log.debug("Dropping repository access time change notification: {}", e.getMessage()); } } public void stickyChanged(final StickyChangeEvent event) { try { _executor.execute(() -> { for (StateChangeListener listener: _listeners) { try { listener.stickyChanged(event); } catch (RuntimeException e) { /* State change notifications are * important for proper functioning of the * pool and we cannot risk a problem in an * event handler causing other event * handlers not to be called. We therefore * catch, log and ignore these problems. */ _log.error("Unexpected failure during state change notification", e); } } }); } catch (RejectedExecutionException e) { // Happens when executor is already shut down _log.debug("Dropping repository stick flag change notification: {}", e.getMessage()); } } public void stop() { _executorService.shutdown(); try { _executorService.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }