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

org.firebirdsql.gds.ng.wire.AsynchronousProcessor Maven / Gradle / Ivy

There is a newer version: 6.0.0-beta-1
Show newest version
/*
 * Firebird Open Source JDBC Driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a source control history command.
 *
 * All rights reserved.
 */
package org.firebirdsql.gds.ng.wire;

import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.sql.SQLException;
import java.util.*;

/**
 * Process asynchronous channels for notification of events.
 *
 * @author Mark Rotteveel
 * @since 3.0
 */
public class AsynchronousProcessor {

    private static final Logger log = LoggerFactory.getLogger(AsynchronousProcessor.class);

    /**
     * Initialize on demand holder
     */
    private static class ProcessorHolder {
        private static final AsynchronousProcessor INSTANCE = new AsynchronousProcessor();
    }

    private final AsynchronousChannelListener channelListener = new ProcessorChannelListener();
    private final List newChannels = Collections.synchronizedList(new ArrayList<>());
    private final SelectorTask selectorTask = new SelectorTask();
    private final Selector selector;

    private AsynchronousProcessor() {
        try {
            selector = Selector.open();
        } catch (IOException e) {
            throw new IllegalStateException("Unable to initialize asynchronous processor", e);
        }
        Thread selectorThread = new Thread(selectorTask, "Jaybird asynchronous processing");
        selectorThread.setDaemon(true);
        selectorThread.setUncaughtExceptionHandler(new LogUncaughtExceptionHandler());
        selectorThread.start();
    }

    /**
     * @return Singleton instance
     */
    public static AsynchronousProcessor getInstance() {
        return ProcessorHolder.INSTANCE;
    }

    /**
     * Registers an asynchronous channel with the asynchronous processor.
     *
     * @param channel
     *         The channel to register
     */
    public void registerAsynchronousChannel(FbWireAsynchronousChannel channel) {
        newChannels.add(channel);
        channel.addChannelListener(channelListener);
        selector.wakeup();
    }

    // TODO Reduce visibility or remove entirely?
    public void shutdown() {
        selectorTask.stop();
        selector.wakeup();
    }

    private class ProcessorChannelListener implements AsynchronousChannelListener {
        @Override
        public void channelClosing(FbWireAsynchronousChannel channel) {
            if (!newChannels.remove(channel)) {
                // TODO Replace with map from channel to selectionkey?
                for (SelectionKey key : new ArrayList<>(selector.keys())) {
                    if (key.isValid() && key.attachment() == channel) {
                        key.cancel();
                        break;
                    }
                }
            }
            channel.removeChannelListener(this);
        }

        @Override
        public void eventReceived(FbWireAsynchronousChannel channel, Event event) {
            // Ignore
        }
    }

    private class SelectorTask implements Runnable {

        private volatile boolean running = true;

        @Override
        public void run() {
            while (running && !Thread.currentThread().isInterrupted()) {
                try {
                    synchronized (newChannels) {
                        for (FbWireAsynchronousChannel channel : newChannels) {
                            addChannel(channel);
                        }
                        newChannels.clear();
                    }

                    if (selector.select() == 0) continue;

                    handleReadableKeys(selector.selectedKeys());
                } catch (IOException ex) {
                    // TODO check any other necessary handling
                    log.error("IOException in async event processing", ex);
                }
            }
            try {
                selector.close();
            } catch (IOException e) {
                // ignore
                log.error("IOException closing event selector", e);
            }
        }

        private void handleReadableKeys(final Set selectedKeys) {
            synchronized (selectedKeys) {
                Iterator selectedKeysIterator = selectedKeys.iterator();
                while (selectedKeysIterator.hasNext()) {
                    final SelectionKey selectionKey = selectedKeysIterator.next();
                    selectedKeysIterator.remove();
                    if (!selectionKey.isValid()) continue;

                    handleReadable(selectionKey);
                }
            }
        }

        private void addChannel(FbWireAsynchronousChannel channel) throws ClosedChannelException {
            try {
                channel.getSocketChannel().register(selector, SelectionKey.OP_READ, channel);
            } catch (SQLException ex) {
                // channel closed, remove listener
                channel.removeChannelListener(channelListener);
            }
        }

        private void handleReadable(SelectionKey selectionKey) {
            try {
                if (!selectionKey.isReadable()) {
                    return;
                }
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                FbWireAsynchronousChannel channel = (FbWireAsynchronousChannel) selectionKey.attachment();

                final ByteBuffer eventBuffer = channel.getEventBuffer();
                int count = socketChannel.read(eventBuffer);
                if (count > 0) {
                    channel.processEventData();
                } else if (count < 0) {
                    try {
                        channel.close();
                    } catch (SQLException e) {
                        // ignore
                        log.error("SQLException closing event channel", e);
                    }
                }
            } catch (AsynchronousCloseException e) {
                // Channel closed
                log.debug("AsynchronousCloseException reading from event channel; cancelling key", e);
                selectionKey.cancel();
            } catch (CancelledKeyException e) {
                // ignore; key cancelled as part of close
            } catch (Exception e) {
                log.errorfe("%s reading from event channel; attempting to close async channel",
                        e.getClass().getName(), e);
                FbWireAsynchronousChannel channel = (FbWireAsynchronousChannel) selectionKey.attachment();
                try {
                    channel.close();
                } catch (Exception e1) {
                    log.error("Attempt to close async channel failed", e1);
                }
            }
        }

        private void stop() {
            running = false;
        }
    }

    private static class LogUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            log.errorfe("Jaybird asynchronous processing terminated. Uncaught exception on %s", t.getName(), e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy