All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kolibrifx.plovercrest.server.internal.ThreadedEventDispatcher Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
 */

package com.kolibrifx.plovercrest.server.internal;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import org.apache.log4j.Logger;

/**
 * Event dispatcher that runs on a separate thread.
 * 
 * The thread is started automatically (from the constructor). It can be stopped either by
 * interrupting the thread or calling shutDown(). The latter makes sure that all queued events are
 * dispatched before the thread stops.
 * 
 * For performance reasons, events may be dropped if several events of the same type (listener,
 * type, table) is queued. This is currently possible because of how FoldListener reacts to events,
 * so think twice before changing this.
 */
public class ThreadedEventDispatcher extends Thread implements EventDispatcher {
    private static final Logger log = Logger.getLogger(ThreadedEventDispatcher.class);

    private static class QueuedEvent {
        final TableListener listener;
        final TableEvent event;

        QueuedEvent(final TableListener listener, final TableEvent event) {
            this.listener = listener;
            this.event = event;
        }

        @Override
        public boolean equals(final Object other) {
            if (!(other instanceof QueuedEvent)) {
                return false;
            }
            if (this == other) {
                return true;
            }
            final QueuedEvent otherEvent = (QueuedEvent) other;
            return listener == otherEvent.listener && event.getType() == otherEvent.event.getType()
                    && event.getTableName().equals(otherEvent.event.getTableName());
        }

        @Override
        public int hashCode() {
            if (listener == null) {
                return super.hashCode();
            }
            return listener.hashCode();
        }
    }

    private final LinkedHashSet eventSet;
    private final QueuedEvent shutDownEvent;
    private boolean isThreadWaiting;

    public ThreadedEventDispatcher() {
        eventSet = new LinkedHashSet();
        shutDownEvent = new QueuedEvent(null, null);
        isThreadWaiting = false;
        // set as daemon to prevent thread from keeping JVM alive, maybe find a
        // better way?
        setDaemon(true);
        setName("Plovercrest threaded event dispatcher");
        start();
    }

    private void dispatchEvent(final QueuedEvent e) {
        synchronized (eventSet) {
            eventSet.add(e);
            // for performance reasons, only call notify if the thread is
            // waiting
            if (isThreadWaiting) {
                eventSet.notify();
            }
        }
    }

    @Override
    public void dispatch(final TableListener listener, final TableEvent event) {
        dispatchEvent(new QueuedEvent(listener, event));
    }

    @Override
    public void run() {
        final ArrayList uniqueEvents = new ArrayList();
        boolean shouldQuit = false;
        try {
            while (!shouldQuit) {
                synchronized (eventSet) {
                    if (eventSet.isEmpty()) {
                        isThreadWaiting = true;
                        eventSet.wait();
                        isThreadWaiting = false;
                    }
                    // drain queued events, then release the lock
                    uniqueEvents.addAll(eventSet);
                    eventSet.clear();
                }

                for (final QueuedEvent qe : uniqueEvents) {
                    if (qe == shutDownEvent) {
                        shouldQuit = true;
                    } else {
                        try {
                            qe.listener.handleTableEvent(qe.event);
                        } catch (final Exception e) {
                            log.error(String.format("Exception while processing %s for table %s", qe.event.getType(),
                                                    qe.event.getTableName()), e);
                        }
                    }
                }
                uniqueEvents.clear();
            }
            log.trace("Dispatcher thread shutting down");
        } catch (final InterruptedException e) {
            log.info("Dispatcher thread interrupted");
        }
    }

    /**
     * Clean shut down. Joins the thread, making sure all currently queued events are processed
     * first.
     */
    @Override
    public void shutDown() {
        dispatchEvent(shutDownEvent);
        try {
            join();
        } catch (final InterruptedException e) {
            log.error("Interrupted during join", e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy