Package com.sleepycat.je.rep.utilint

Source Code of com.sleepycat.je.rep.utilint.ServiceDispatcher$QueuingService

package com.sleepycat.je.rep.utilint;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.rep.impl.RepImpl;
import com.sleepycat.je.rep.impl.TextProtocol;
import com.sleepycat.je.rep.impl.TextProtocol.RequestMessage;
import com.sleepycat.je.rep.impl.TextProtocol.ResponseMessage;
import com.sleepycat.je.rep.impl.node.NameIdPair;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.StoppableThread;

/**
* ServiceDispatcher listens on a specific socket for service requests
* and dispatches control to the service that is being requested. A service
* request message has the format:
*
* Service:<one byte ServiceName.length><ServiceName>
*
* The format of the message is binary, with all text being encoded in ascii.
*
* Upon receipt of service request message, the new SocketChannel is queued for
* processing by the service in the Queue associated with the service. The
* SocketChannel is the responsibility of the service after this point. It can
* configure the channel to best suit the requirements of the specific service.
*
* The dispatcher returns a single byte to indicate success or failure. The
* byte value encodes a ServiceDispatcher.Response enumerator.
*
*/
public class ServiceDispatcher extends StoppableThread {

    /* The socket on which the dispatcher is listening */
    private final InetSocketAddress socketAddress;

    /*
     * The selector that watches for accept events on the server socket and
     * on subsequent read events.
     */
    private final Selector selector;

    /* The server socket channel */
    private final ServerSocketChannel serverChannel;

    /* Determines whether new connections should be accepted. */
    private boolean processAcceptRequests = true;

    /* Maintains the error count, used primarily for testing. */
    private int errorCount = 0;

    /*
     * Maps the service name to the queue of sockets processed by the
     * service.
     */
    private final Map<String, Service> serviceMap =
        new ConcurrentHashMap<String, Service>();

    /* The thread pool used to manage the threads used by services */
    private final ExecutorService pool = Executors.newCachedThreadPool();

    private final Logger logger;
    private final Formatter formatter;

    /* The prefix for a service request. */
    private static final String REQUEST_PREFIX = "Service:";
    private static final byte[] REQUEST_PREFIX_BYTES;

    /*
     * A reference to a replicated environment, only used for error
     * propagation when this dispatcher has been created for a replicated
     * node.
     */
    private final RepImpl repImpl;

    /**
     * The response to a service request.
     *
     * Do not rearrange the order of the enumerators, since their ordinal
     * values are currently used in messages.
     */
    public static enum Response {

        OK, BUSY, FORMAT_ERROR, UNKNOWN_SERVICE ;

        ByteBuffer byteBuffer() {
            ByteBuffer buffer = ByteBuffer.allocate(1);
            buffer.put((byte)ordinal());
            buffer.flip();
            return buffer;
        }

        static Response get(int ordinal) {
            if (ordinal < values().length) {
                return values()[ordinal];
            }
            return null;
        }
    }

    static {
        try {
            REQUEST_PREFIX_BYTES = REQUEST_PREFIX.getBytes("US-ASCII");
        } catch (UnsupportedEncodingException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        }
    }

    /*
     * The initial size is the prefix plus the byte that holds the length of
     * the service name.
     */
    private static final int INITIAL_BUFFER_SIZE =
        REQUEST_PREFIX_BYTES.length+1;

    /**
     * Create a ServiceDispatcher listening on a specific socket for service
     * requests. This service dispatcher has been created on behalf of a
     * replicated environment, and the node will be informed of any unexpected
     * failures seen by the dispatcher.
     *
     * @param socketAddress the socket on which it listens for service requests
     *
     * @throws IOException if the socket could not be bound.
     */
    public ServiceDispatcher(InetSocketAddress socketAddress,
                             RepImpl repImpl)
        throws IOException {

        super(repImpl, "ServiceDispatcher-" + socketAddress.getHostName() +
                       ":" + socketAddress.getPort());

        this.repImpl = repImpl;
        this.socketAddress = socketAddress;
        serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        ServerSocket acceptSocket = serverChannel.socket();
        /* No timeout */
        acceptSocket.setSoTimeout(0);
        acceptSocket.bind(socketAddress);
        if (repImpl == null) {
            logger = LoggerUtils.getLoggerFormatterNeeded(getClass());
        } else {
            logger = LoggerUtils.getLogger(getClass());
        }
        NameIdPair nameIdPair =
            (repImpl == null) ? NameIdPair.NULL : repImpl.getNameIdPair();
        formatter = new ReplicationFormatter(nameIdPair);
    }

    /**
     * Convenience overloading for when the dispatcher is created without a
     * replicated environment, e.g. when used by the Monitor, and in unit test
     * situations.
     *
     * @see #ServiceDispatcher(InetSocketAddress, RepImpl)
     */
    public ServiceDispatcher(InetSocketAddress socketAddress)
        throws IOException {

        this(socketAddress, null /* repImpl */);
    }

    /**
     * Stop accepting new connections, while the individual services quiesce
     * and shut themselves down.
     */
    public void preShutdown() {
        processAcceptRequests = false;
    }

    /**
     * Shuts down the dispatcher, so that it's no longer listening for service
     * requests. The port is freed up upon return and the thread used to
     * listen on the port is shutdown.
     */
    public void shutdown() {
        if (shutdownDone()) {
            return;
        }

        LoggerUtils.logMsg(logger, repImpl, formatter, Level.INFO,
                           "ServiceDispatcher shutdown starting. HostPort=" +
                           socketAddress.getHostName() + ":" +
                           + socketAddress.getPort() +
                           " Registered services: " + serviceMap.keySet());

        shutdownThread(logger);

        for (String serviceName : serviceMap.keySet()) {
            cancel(serviceName);
        }

        /* Shutdown any executing and queued service requests. */
        pool.shutdownNow();
        try {
            serverChannel.socket().close();
            selector.close();
        } catch (IOException e) {
            LoggerUtils.logMsg
                (logger, repImpl, formatter, Level.WARNING,
                 "Ignoring I/O error during close: " + e.getMessage());
        }
        LoggerUtils.logMsg(logger, repImpl, formatter, Level.INFO,
                           "ServiceDispatcher shutdown completed." +
                           " HostPort=" + socketAddress.getHostName() +
                           ":" + socketAddress.getPort());
    }

    @Override
    protected int initiateSoftShutdown() {
        selector.wakeup();
        return 0;
    }

    /**
     * @see StoppableThread#getLogger
     */
    @Override
    protected Logger getLogger() {
        return logger;
    }

    /**
     * Builds a service request suitable for sending over to a
     * ServiceDispatcher.
     *
     * @param serviceName the service that is being requested.
     *
     * @return the byte encoding of the service request message
     */
    private static byte[] serviceRequestMessage(String serviceName) {
        byte[] serviceNameBytes;
        try {
            serviceNameBytes = serviceName.getBytes("US-ASCII");
        } catch (UnsupportedEncodingException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        }
        int length = REQUEST_PREFIX_BYTES.length + 1 +
                      serviceNameBytes.length;
        ByteBuffer buffer = ByteBuffer.allocate(length);
        buffer.put(REQUEST_PREFIX_BYTES).
               put((byte)serviceNameBytes.length).
               put(serviceNameBytes);
        return buffer.array();
    }

    /**
     * Used by the client to establish an output stream for the service on the
     * socket. It sends out the request for the service and interprets the
     * response to determine if it was successful.
     *
     * @param socket the connected socket that will be the basis for the stream
     * @param serviceName the service running on the stream
     *
     * @return the output stream ready for subsequent output
     *
     * @throws IOException if the output stream could not be established
     * @throws ServiceConnectFailedException if the connection could not be
     * made.
     */
    static public OutputStream getServiceOutputStream(Socket socket,
                                                      String serviceName)
        throws IOException, ServiceConnectFailedException {

        assert socket.isConnected();
        byte[] message = ServiceDispatcher.serviceRequestMessage(serviceName);
        OutputStream out = socket.getOutputStream();
        out.write(message);
        out.flush();
        InputStream in = socket.getInputStream();
        int result = in.read();
        if (result < 0) {
            throw new IOException("No service response byte: " + result);
        }
        Response response = Response.get(result);
        if (response == null) {
            throw new IOException("Unexpected read response byte: " + result);
        }
        if (response != Response.OK) {
            throw new ServiceConnectFailedException(serviceName, response);
        }
        return out;
    }

    /**
     * A variation on the method above. It's used by the client to setup a
     * channel for the service. It performs the initial handshake requesting
     * the service and interpreting the response to determine if it was
     * successful.
     *
     * @param channel the channel that is the basis for the service
     * @param serviceName the service running on the channel
     *
     * @throws ServiceConnectFailedException if the connection could not be
     * made.
     */
    static public void doServiceHandshake(SocketChannel channel,
                                          String serviceName)
        throws IOException, ServiceConnectFailedException {

        ByteBuffer message =
            ByteBuffer.wrap(ServiceDispatcher.serviceRequestMessage(serviceName));
        while (message.remaining() > 0) {
            channel.write(message);
        }
        ByteBuffer buffer = ByteBuffer.allocate(1);
        while (buffer.remaining() > 0) {
            if (channel.read(buffer) < 0) {
                throw new IOException("EOF in response to service request:" +
                                      serviceName);
            }
        }
        int result = channel.read(buffer);
        if (result < 0) {
            throw new IOException("No service response byte: " + result);
        }
        buffer.flip();
        Response response = Response.get(buffer.get());
        if (response == null) {
            throw new IOException("Unexpected read response byte: " + result);
        }
        if (response != Response.OK) {
            throw new ServiceConnectFailedException(serviceName, response);
        }
    }

    /**
     * Returns the next socketChannel created in response to a request for the
     * service. The socketChannel and the associated socket is configured as
     * requested in the arguments.
     *
     * @param serviceName the service for which the channel must be created.
     * @param blocking true if the channel must be configured to block
     * @param soTimeout the timeout for the underlying socket
     * @return the configured channel or null if there are no more channels,
     * because the service has been shut down.
     * @throws InterruptedException
     */
    public SocketChannel takeChannel(String serviceName,
                                     boolean blocking,
                                     int soTimeout)
        throws InterruptedException {

        while (true) {
            Service service = serviceMap.get(serviceName);
            if (service == null) {
                throw EnvironmentFailureException.unexpectedState
                ("Service: " + serviceName + " was not registered");
            }
            if (! (service instanceof QueuingService)) {
                throw EnvironmentFailureException.unexpectedState
                ("Service: " + serviceName + " is not a queuing service");
            }
            Socket socket = null;
            SocketChannel channel = null;
            try {
                channel = ((QueuingService)service).take();
                assert channel != null;

                if (channel == RepUtils.CHANNEL_EOF_MARKER) {
                    /* A pseudo channel to indicate EOF, return null */
                    return null;
                }

                if (service.simulateIOException()) {
                    throw new IOException("Simulated test IO exception");
                }
                channel.configureBlocking(blocking);
                socket = channel.socket();
                socket.setSoTimeout(soTimeout);
                return channel;
            } catch (IOException e) {
                LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                                   "Unable to configure channel " +
                                   "for service: " + serviceName + "\n" +
                                   e.getMessage());
                try {
                    channel.close();
                } catch (IOException e1) {
                    LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINEST,
                                       "Cleanup failed for service: " +
                                       serviceName + "\n" + e.getMessage());
                }
                /* Wait for the next request. */
                continue;
            }
        }
    }

    /**
     * Returns the socket associated with the dispatcher
     */
    public InetSocketAddress getSocketAddress() {
        return socketAddress;
    }

    /**
     * Registers a service queue with the ServiceDispatcher. Requests for a
     * service result in a new SocketChannel being created on which the service
     * can communicate with the requester of the service.
     *
     * @param serviceName the name of the service being requested
     * @param serviceQueue the queue that will be used to hold channels
     * established for the service.
     */
    public void register(String serviceName,
                         BlockingQueue<SocketChannel> serviceQueue) {
        if (serviceName == null) {
            throw EnvironmentFailureException.unexpectedState
                ("The serviceName argument must not be null");
        }
        if (serviceMap.containsKey(serviceName)) {
            throw EnvironmentFailureException.unexpectedState
                ("Service: " + serviceName + " is already registered");
        }
        if (serviceQueue == null) {
            throw EnvironmentFailureException.unexpectedState
                ("The serviceQueue argument must not be null");
        }
        serviceMap.put(serviceName,
                       new QueuingService(serviceName, serviceQueue));
    }

    public void register(Service service) {
        if (service == null) {
            throw EnvironmentFailureException.unexpectedState
                ("The service argument must not be null");
        }

        if (serviceMap.containsKey(service.name)) {
            throw EnvironmentFailureException.unexpectedState
                ("Service: " + service.name + " is already registered");
        }
        LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINE,
                           "Service: " + service.name + " registered.");
        serviceMap.put(service.name, service);
    }

    public boolean isRegistered(String serviceName) {
        if (serviceName == null) {
            throw EnvironmentFailureException.unexpectedState
                ("The serviceName argument must not be null");
        }
        return serviceMap.containsKey(serviceName);
    }

    public void setSimulateIOException(String serviceName,
                                       boolean simulateException) {

        Service service = serviceMap.get(serviceName);
        if (service == null) {
            throw new IllegalStateException
                ("Service: " + serviceName + " is not registered");
        }

        service.setSimulateIOException(simulateException);
    }

    /**
     * Cancels the registration of a service. Subsequent attempts to access the
     * service will be ignored and the channel will be closed and will not be
     * queued.
     *
     * @param serviceName the name of the service being cancelled
     */
    public void cancel(String serviceName) {
        if (serviceName == null) {
            throw EnvironmentFailureException.unexpectedState
                ("The serviceName argument must not be null.");
        }
        Service service = serviceMap.remove(serviceName);

        if (service == null) {
            throw EnvironmentFailureException.unexpectedState
                ("Service: " + serviceName + " was not registered.");
        }
        service.cancel();
        LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINE,
                           "Service: " + serviceName + " shut down.");
    }

    /**
     * Processes an accept event on the server socket. As a result of the
     * processing a new socketChannel is created, and the selector is
     * registered with the new channel so that it can process subsequent read
     * events.
     */
    private void processAccept() {

        SocketChannel socketChannel = null;
        try {
            socketChannel = serverChannel.accept();
            if (!processAcceptRequests) {
                closeChannel(socketChannel);
                return;
            }
            socketChannel.configureBlocking(false);
            socketChannel.register
                (selector,
                 SelectionKey.OP_READ,
                 ByteBuffer.allocate(INITIAL_BUFFER_SIZE));
        } catch (IOException e) {
            LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                               "Server accept exception: " + e.getMessage());
            closeChannel(socketChannel);
        }
    }

    /**
     * Processes read events on newly established socket channels. Input on the
     * channel is verified to ensure that it is a service request. The read is
     * accomplished in two parts, a read for the fixed size prefix and the name
     * length byte, followed by a read of the variable length name itself.
     *
     * Errors result in the channel being closed(with the key being canceled
     * as a result) and a null value being returned.
     *
     * @param readKey the read key associated with the channel.
     *
     * @return the ServiceName or null if there was insufficient input, or an
     * error was encountered.
     */
    private String processRead(SelectionKey readKey) {
        SocketChannel socketChannel = null;
        try {
            ByteBuffer readBuffer = (ByteBuffer) readKey.attachment();
            socketChannel = (SocketChannel) readKey.channel();
            int readBytes = socketChannel.read(readBuffer);
            if (readBytes < 0 ) {
                /* Premature EOF */
                errorCount++;
                LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                                   "Premature EOF on channel: " +
                                   socketChannel + " read() returned: " +
                                   readBytes);
                socketChannel.close();
                return null;
            }
            if (readBuffer.remaining() == 0) {
                readBuffer.flip();
                if (readBuffer.capacity() == INITIAL_BUFFER_SIZE) {
                    String prefix = new String(readBuffer.array(),
                                               0, REQUEST_PREFIX.length(),
                                               "US-ASCII");
                    if (!prefix.equals(REQUEST_PREFIX)) {
                        errorCount++;
                        LoggerUtils.logMsg
                            (logger, repImpl, formatter, Level.WARNING,
                             "Malformed service request: " + prefix);
                        socketChannel.write
                            (Response.FORMAT_ERROR.byteBuffer());
                        socketChannel.close();
                        return null;
                    }
                    /* Enlarge the buffer to read the service name as well */
                    int nameLength = readBuffer.get(INITIAL_BUFFER_SIZE-1);
                    if (nameLength <= 0) {
                        errorCount++;
                        LoggerUtils.logMsg
                            (logger, repImpl, formatter, Level.WARNING,
                             "Bad service service name length: " + nameLength);
                        socketChannel.write
                            (Response.FORMAT_ERROR.byteBuffer());
                        socketChannel.close();
                        return null;
                    }
                    ByteBuffer buffer = ByteBuffer.allocate
                        (INITIAL_BUFFER_SIZE + nameLength);
                    buffer.put(readBuffer);
                    readKey.attach(buffer);

                    return processRead(readKey);
                }
                String request = new String(readBuffer.array(), "US-ASCII");
                readKey.cancel();
                return request.substring(REQUEST_PREFIX.length()+1);
            }
            /* Buffer not full as yet, keep reading */
            return null;
        } catch (IOException e) {
            LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                               "Exception during read: " + e.getMessage());
            closeChannel(socketChannel);
            return null;
        }
    }

    /**
     * Closes the channel, logging any resulting exceptions.
     *
     * @param channel the channel being closed
     */
    private void closeChannel(Channel channel) {
        if (channel != null) {
            try {
                channel.close();
            } catch (IOException e1) {
                LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                                   "Exception during cleanup: " +
                                   e1.getMessage());
            }
        }
    }

    /**
     * The central run method. It dispatches to the "accept" and "read" event
     * processing methods. Upon a completed read, it verifies the validity of
     * the service name and queues the channel for subsequent consumption
     * by the service.
     *
     */
    @Override
    public void run() {
        LoggerUtils.logMsg(logger, repImpl, formatter, Level.INFO,
                           "Started ServiceDispatcher. HostPort=" +
                           socketAddress.getHostName() + ":" +
                           socketAddress.getPort());
        try {
            while (true) {
                try {
                    int result = selector.select();
                    if (isShutdown()) {
                        return;
                    }
                    if (result == 0) {
                        continue;
                    }
                } catch (IOException e) {
                    LoggerUtils.logMsg
                        (logger, repImpl, formatter, Level.SEVERE,
                         "Server socket exception " + e.getMessage());
                    throw EnvironmentFailureException.unexpectedException(e);
                }
                Set<SelectionKey> skeys = selector.selectedKeys();
                for (SelectionKey key : skeys) {
                    switch (key.readyOps()) {

                        case SelectionKey.OP_ACCEPT:
                            processAccept();
                            break;

                        case SelectionKey.OP_READ:
                            String serviceName = processRead(key);
                            if (serviceName == null) {
                                break;
                            }
                            key.cancel();
                            processService((SocketChannel)key.channel(),
                                           serviceName);
                            break;

                        default:
                            throw EnvironmentFailureException.unexpectedState
                                ("Unexpected ops bit set: " + key.readyOps());
                    }
                }
                /* All keys have been processed clear them. */
                skeys.clear();
            }
        } finally {
            closeChannel(serverChannel);
            cleanup();
        }
    }

    /**
     * Performs the guts of the work underlying a service request. It validates
     * the service request and writes an appropriate response to the channel.
     * @param channel
     * @param serviceName
     */
    private void processService(SocketChannel channel, String serviceName) {
        final Service service = serviceMap.get(serviceName);
        try {
            if (service == null) {
                errorCount++;
                channel.write(Response.UNKNOWN_SERVICE.byteBuffer());
                closeChannel(channel);
                /*
                 * Not unexpected in a distributed app due to calls being made
                 * before a service is actually registered.
                 */
                LoggerUtils.logMsg(logger, repImpl, formatter, Level.INFO,
                                   "Request for unknown Service: " +
                                   serviceName + " Registered services: " +
                                   serviceMap.keySet());
                return;
            }
            Response response = service.isBusy() ? Response.BUSY : Response.OK;
            LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINE,
                               "Service response: " + response +
                               " for service: " + service.name);

            if (channel.write(response.byteBuffer()) == 0) {
                throw EnvironmentFailureException.unexpectedState
                    ("Failed to write byte. Send buffer size: " +
                     channel.socket().getSendBufferSize());
            }
            if (response == Response.OK) {
                service.requestDispatch(channel);
            }
        } catch (IOException e) {
            closeChannel(channel);
            LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                               "IO error writing to channel for " +
                               "service: " +  serviceName + e.getMessage());
        }
    }

    /**
     * The abstract class underlying all services.
     */
    static private abstract class Service {

        /* The name associated with the service. */
        final String name;

        private boolean simulateIOException = false;

        public Service(String name) {
            super();
            if (name == null) {
                throw EnvironmentFailureException.unexpectedState
                    ("Service name was null");
            }
            this.name = name;
        }

        /**
         * Informs the service of a new request. The implementation of the
         * method must not block.
         *
         * @param channel the channel on which the request was made
         */
        abstract void requestDispatch(SocketChannel channel);

        /**
         * Used to limit a particular type of service to avoid excess load.
         */
        public boolean isBusy() {
            return false;
        }

        /**
         * Used during unit testing to simulate communications problems.
         */
        public boolean simulateIOException() {
            return simulateIOException;
        }

        public void setSimulateIOException(boolean simulateIOException) {
            this.simulateIOException = simulateIOException;
        }

        /**
         * Cancel the service as part of the registration being canceled.
         */
        abstract void cancel();
    }

    /**
     * A service where requests are simply added to the supplied queue. It's
     * the responsibility of the service creator to drain the queue. This
     * service is used when the service carries out a long-running dialog with
     * the service requester. For example, a Feeder service.
     */
    public class QueuingService extends Service {
        /* Holds the queue of pending requests, one per channel */
        private final BlockingQueue<SocketChannel> queue;

        QueuingService(String serviceName,
                       BlockingQueue<SocketChannel> queue) {
            super(serviceName);
            this.queue = queue;
        }

        SocketChannel take() throws InterruptedException {
            return queue.take();
        }

        @Override
        void requestDispatch(SocketChannel channel) {
            if (!queue.add(channel)) {
                throw EnvironmentFailureException.unexpectedState
                    ("request queue overflow");
            }
        }

        @Override
        void cancel() {
            /*
             * Drain any existing pending requests. It's safe to just iterate
             * since the service dispatcher has already stopped accepting new
             * requests for the service.
             */
            for (SocketChannel channel : queue) {
                try {
                    channel.close();
                } catch (IOException e) {
                    // Ignore it, it's only cleanup
                }
            }
            queue.add(RepUtils.CHANNEL_EOF_MARKER);
        }
    }

    /**
     * A queuing service that starts the thread that services the requests
     * lazily, upon first request and terminates the thread when the service is
     * unregistered. The thread must be "interrupt aware" and must exit when
     * it receives an interrupt.
     *
     * This type of service is suitable for services that are used
     * infrequently.
     */
    public class LazyQueuingService extends QueuingService {

        private final Thread serviceThread;

        public LazyQueuingService(String serviceName,
                                  BlockingQueue<SocketChannel> queue,
                                  Thread serviceThread) {

            super(serviceName, queue);
            this.serviceThread = serviceThread;
        }

        @Override
        void requestDispatch(SocketChannel channel) {

            switch (serviceThread.getState()) {

                case NEW:
                    serviceThread.start();
                    LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINE,
                                       "Thread started for service: " + name);
                    break;

                case RUNNABLE:
                case TIMED_WAITING:
                case WAITING:
                case BLOCKED:
                    /* Was previously activated. */
                    LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINE,
                                       "Thread started for service: " + name);
                    break;

                default:
                    RuntimeException e =
                        EnvironmentFailureException.unexpectedState
                            ("Thread for service:" + name +
                             "is in state:" + serviceThread.getState());
                    LoggerUtils.logMsg(logger, repImpl, formatter,
                                       Level.WARNING, e.getMessage());
                    throw e;
            }
            super.requestDispatch(channel);
        }

        @Override
        /**
         * Interrupts the thread to cause it to exit.
         */
        void cancel() {
            if (serviceThread.isAlive()) {
                serviceThread.interrupt();
                try {
                    serviceThread.join();
                } catch (InterruptedException e) {
                    /* Ignore it on shutdown. */
                }
            }
            super.cancel();
        }
    }

    /**
     * A service that is run immediately in a thread allocated to it. Subtypes
     * implement the getRunnable() method which provides the runnable object
     * for the service. This service frees up the caller from managing the the
     * threads associated with the service. The runnable must manage interrupts
     * so that it can be shut down by the underlying thread pool.
     */
    static public abstract class ExecutingService extends Service {
        final private ServiceDispatcher dispatcher;

        public ExecutingService(String serviceName,
                                ServiceDispatcher dispatcher) {
            super(serviceName);
            this.dispatcher = dispatcher;
        }

        public abstract Runnable getRunnable(SocketChannel socketChannel);

        @Override
        void requestDispatch(SocketChannel channel) {
            dispatcher.pool.execute(getRunnable(channel));
        }

        @Override
        void cancel() {
            /* Nothing to do */
        }
    }

    @SuppressWarnings("serial")
    static public class ServiceConnectFailedException extends Exception {
        final Response response;
        final String serviceName;

        ServiceConnectFailedException(String serviceName,
                                      Response response) {
            assert(response != Response.OK);
            this.response = response;
            this.serviceName = serviceName;
        }

        public Response getResponse() {
            return response;
        }

        @Override
        public String getMessage() {
            switch (response) {
                case FORMAT_ERROR:
                    return "Bad message format, for service:" + serviceName;

                case UNKNOWN_SERVICE:
                    return "Unknown service request:" + serviceName;

                case BUSY:
                    return "Service was busy";

                case OK:
                    /*
                     * Don't expect an OK response to provoke an exception.
                     * Fall through.
                     */
                default:
                    throw EnvironmentFailureException.unexpectedState
                        ("Unexpected response:" + response +
                         " for service:" + serviceName);
            }
        }
    }

    abstract public static class ExecutingRunnable implements Runnable {
        protected final SocketChannel channel;
        protected final TextProtocol protocol;
        protected final boolean expectResponse;

        public ExecutingRunnable(SocketChannel channel,
                                 TextProtocol protocol,
                                 boolean expectResponse) {
            this.channel = channel;
            this.protocol = protocol;
            this.expectResponse = expectResponse;
        }

        /* Read request and send out response. */
        public void run() {
            try {
                channel.configureBlocking(true);
                RequestMessage request = protocol.getRequestMessage(channel);
                if (request == null) {
                    return;
                }
                ResponseMessage response = getResponse(request);
                if (expectResponse && response != null) {
                    PrintWriter out = new PrintWriter
                        (channel.socket().getOutputStream(), true);
                    out.println(response.wireFormat());
                } else {
                    assert (response == null);
                }
            } catch (IOException e) {
                logMessage("IO error on socket: " + e.getMessage());
                return;
            } finally {
                if (channel.isOpen()) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        logMessage("IO error on socket: " + e.getMessage());
                         return;
                    }
                }
            }
        }

        /* Get the response for a request. */
        abstract protected ResponseMessage getResponse(RequestMessage request)
            throws IOException;

        /* Log the message. */
        abstract protected void logMessage(String message);
    }
}
TOP

Related Classes of com.sleepycat.je.rep.utilint.ServiceDispatcher$QueuingService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.