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

com.kolibrifx.plovercrest.server.internal.protocol.StreamServer 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.protocol;

import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import com.kolibrifx.plovercrest.client.PlovercrestException;
import com.kolibrifx.plovercrest.server.internal.EngineAdapter;
import com.kolibrifx.plovercrest.server.security.AccessControlFilter;

public class StreamServer {
    private static final Logger log = Logger.getLogger(StreamServer.class);

    private final EngineAdapter adapter;
    private int port;
    private final Collection connections =
            Collections.synchronizedCollection(new LinkedList());
    private ServerSocketChannel channel;
    private final ScheduledExecutorService executor;

    private final AccessControlFilter accessControlFilter;
    private volatile boolean isShuttingDown = false;
    private volatile boolean stopped = false;
    private TimeoutProvider timeoutProvider = null;

    public StreamServer(final EngineAdapter engine, final int port, final AccessControlFilter accessControlFilter) {
        this.adapter = engine;
        this.port = port;
        this.accessControlFilter = accessControlFilter;
        this.executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            @Override
            public Thread newThread(final Runnable r) {
                final Thread t = new Thread(r, "Plovercrest stream server");
                t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {

                    @Override
                    public void uncaughtException(final Thread t, final Throwable e) {
                        log.fatal("Uncaught exception", e);
                    }
                });
                return t;
            }
        });
    }

    public void start() {
        stopped = false;
        isShuttingDown = false;
        timeoutProvider = new TimeoutProvider();
        try {
            channel = ServerSocketChannel.open();
            channel.socket().bind(new InetSocketAddress(port));
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (!isShuttingDown) {
                            final SocketChannel ch = channel.accept();
                            if (isShuttingDown) {
                                // somewhat ugly hack to stop accepting new connections
                                try {
                                    ch.close();
                                } catch (final IOException e) {
                                    log.error("Failed to close incoming connection while shutting down", e);
                                }
                                if (log.isInfoEnabled()) {
                                    log.info("Shutting down, exiting accept loop");
                                }
                                break;
                            }
                            ch.setOption(StandardSocketOptions.TCP_NODELAY, true);
                            final InetSocketAddress remoteSocketAddress = (InetSocketAddress) ch.getRemoteAddress();
                            if (log.isInfoEnabled()) {
                                log.info("New stream client connected: " + remoteSocketAddress);
                            }

                            while (!ch.finishConnect()) {

                            }

                            final StreamConnection sc =
                                    new StreamConnection(StreamServer.this, adapter, ch, remoteSocketAddress,
                                                         accessControlFilter, timeoutProvider);
                            connections.add(sc);
                            // TODO: handle isShuttingDown here...?
                            sc.start();
                        }
                    } catch (final ClosedChannelException e) {
                        // Triggered by stop()
                        log.info("Server channel closed, exiting normally");
                    } catch (final IOException e) {
                        log.error(e.getMessage(), e);
                        throw new RuntimeException(e);
                    } finally {
                        // TODO: await clean shutdown before close?
                        try {
                            if (channel.isOpen()) {
                                channel.close();
                            }
                        } catch (final IOException e) {
                            log.error(e.getMessage(), e);
                        }
                    }
                }
            });
        } catch (final IOException e) {
            throw new PlovercrestException("Failed to open socket", e);
        }
    }

    private void stop() {
        if (stopped) {
            return;
        }
        log.debug("Stopping server");
        try {
            if (channel.isOpen()) {
                channel.close();
            }
        } catch (final IOException e) {
            log.error(e.getMessage(), e);
        }
        executor.shutdown();
        try {
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                log.error("Timed out while awaiting termination");
            }
        } catch (final InterruptedException e) {
            log.error(e.getMessage(), e);
        }
        final ArrayList unsynchronizedConnections = new ArrayList();
        synchronized (connections) {
            unsynchronizedConnections.addAll(connections);
            connections.clear();
        }
        for (final StreamConnection sc : unsynchronizedConnections) {
            sc.doStop(); // redundant?
            try {
                sc.join(); // do not hold connections lock here
            } catch (final InterruptedException e) {
                log.error("Interrupted while joining stream connection thread");
                Thread.currentThread().interrupt();
                break;
            }
        }
        stopped = true;
        timeoutProvider.close();
    }

    void startCleanShutdown() {
        isShuttingDown = true;
        synchronized (connections) {
            if (connections.isEmpty()) {
                log.info("No clients connected, shutting down immediately");
                stop();
            } else {
                if (log.isInfoEnabled()) {
                    log.info(connections.size() + " clients connected, sending shutdown events");
                }
                for (final StreamConnection sc : connections) {
                    sc.startCleanShutdown();
                }
            }
        }
    }

    boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
        return executor.awaitTermination(timeout, unit);
    }

    public boolean shutdown(final long timeout, final TimeUnit unit) throws InterruptedException {
        startCleanShutdown();
        return executor.awaitTermination(timeout, unit);
    }

    public boolean awaitTermination() throws InterruptedException {
        return executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
    }

    public void shutdownNow() {
        stop();
    }

    /**
     * Returns the port on which the server is listening. Normally the same as given in the
     * constructor, unless port 0 has been specified.
     */
    public int getOpenPort() {
        if (channel == null) {
            throw new IllegalStateException("The server has not been started.");
        }
        return channel.socket().getLocalPort();
    }

    void deregister(final StreamConnection sc) {
        synchronized (connections) {
            connections.remove(sc);
            if (isShuttingDown && connections.isEmpty()) {
                stop();
            }
        }
    }

    int getOpenClientsCount() {
        return connections.size();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy