/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jafka.network; import io.jafka.api.RequestKeys; import io.jafka.mx.SocketServerStats; import io.jafka.utils.Closer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.EOFException; import java.io.IOException; import java.net.Socket; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import static java.lang.String.format; /** * Thread that processes all requests from a single connection. There are N * of these running in parallel each of which has its own selectors * * @author adyliu (imxylz@gmail.com) * @since 1.0 */ public class Processor extends AbstractServerThread { private final BlockingQueue<SocketChannel> newConnections; private final Logger requestLogger = LoggerFactory.getLogger("jafka.request.logger"); private RequestHandlerFactory requesthandlerFactory; private SocketServerStats stats; private int maxRequestSize; /** * creaet a new thread processor * * @param requesthandlerFactory request handler factory * @param stats jmx state statics * @param maxRequestSize max request package size * @param maxCacheConnections max cache connections for self-protected */ public Processor(RequestHandlerFactory requesthandlerFactory, // SocketServerStats stats, int maxRequestSize,// int maxCacheConnections) { this.requesthandlerFactory = requesthandlerFactory; this.stats = stats; this.maxRequestSize = maxRequestSize; this.newConnections = new ArrayBlockingQueue<SocketChannel>(maxCacheConnections); } public void run() { startupComplete(); while (isRunning()) { try { // setup any new connections that have been queued up configureNewConnections(); final Selector selector = getSelector(); int ready = selector.select(500); if (ready <= 0) continue; Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); while (iter.hasNext() && isRunning()) { SelectionKey key = null; try { key = iter.next(); iter.remove(); if (key.isReadable()) { read(key); } else if (key.isWritable()) { write(key); } else if (!key.isValid()) { close(key); } else { throw new IllegalStateException("Unrecognized key state for processor thread."); } } catch (EOFException eofe) { Socket socket = channelFor(key).socket(); logger.debug(format("connection closed by %s:%d.", socket.getInetAddress(), socket.getPort())); close(key); } catch (InvalidRequestException ire) { Socket socket = channelFor(key).socket(); logger.info(format("Closing socket connection to %s:%d due to invalid request: %s", socket.getInetAddress(), socket.getPort(), ire.getMessage())); close(key); } catch (Throwable t) { Socket socket = channelFor(key).socket(); final String msg = "Closing socket for %s:%d becaulse of error %s"; if (logger.isDebugEnabled()) { logger.error(format(msg, socket.getInetAddress(), socket.getPort(), t.getMessage()), t); } else { logger.info(format(msg, socket.getInetAddress(), socket.getPort(), t.getMessage())); } close(key); } } } catch (IOException e) { logger.error(e.getMessage(), e); } } // logger.debug("Closing selector while shutting down"); closeSelector(); shutdownComplete(); } private SocketChannel channelFor(SelectionKey key) { return (SocketChannel) key.channel(); } private void close(SelectionKey key) { SocketChannel channel = (SocketChannel) key.channel(); if (logger.isDebugEnabled()) { logger.debug("Closing connection from " + channel.socket().getRemoteSocketAddress()); } Closer.closeQuietly(channel.socket()); Closer.closeQuietly(channel); key.attach(null); key.cancel(); } private void write(SelectionKey key) throws IOException { Send response = (Send) key.attachment(); SocketChannel socketChannel = channelFor(key); int written = response.writeTo(socketChannel); stats.recordBytesWritten(written); if (response.complete()) { key.attach(null); key.interestOps(SelectionKey.OP_READ); } else { key.interestOps(SelectionKey.OP_WRITE); getSelector().wakeup(); } } private void read(SelectionKey key) throws IOException { SocketChannel socketChannel = channelFor(key); Receive request = null; if (key.attachment() == null) { request = new BoundedByteBufferReceive(maxRequestSize); key.attach(request); } else { request = (Receive) key.attachment(); } int read = request.readFrom(socketChannel); stats.recordBytesRead(read); if (read < 0) { close(key); } else if (request.complete()) { Send maybeResponse = handle(key, request); key.attach(null); // if there is a response, send it, otherwise do nothing if (maybeResponse != null) { key.attach(maybeResponse); key.interestOps(SelectionKey.OP_WRITE); } } else { // more reading to be done key.interestOps(SelectionKey.OP_READ); getSelector().wakeup(); if (logger.isTraceEnabled()) { logger.trace("reading request not been done. " + request); } } } /** * Handle a completed request producing an optional response */ private Send handle(SelectionKey key, Receive request) { final short requestTypeId = request.buffer().getShort(); final RequestKeys requestType = RequestKeys.valueOf(requestTypeId); if (requestLogger.isTraceEnabled()) { if (requestType == null) { throw new InvalidRequestException("No mapping found for handler id " + requestTypeId); } String logFormat = "Handling %s request from %s"; requestLogger.trace(format(logFormat, requestType, channelFor(key).socket().getRemoteSocketAddress())); } RequestHandler handlerMapping = requesthandlerFactory.mapping(requestType, request); if (handlerMapping == null) { throw new InvalidRequestException("No handler found for request"); } long start = System.nanoTime(); Send maybeSend = handlerMapping.handler(requestType, request); stats.recordRequest(requestType, System.nanoTime() - start); return maybeSend; } private void configureNewConnections() throws ClosedChannelException { while (newConnections.size() > 0) { SocketChannel channel = newConnections.poll(); if (logger.isDebugEnabled()) { logger.debug("Listening to new connection from " + channel.socket().getRemoteSocketAddress()); } channel.register(getSelector(), SelectionKey.OP_READ); } } public void accept(SocketChannel socketChannel) { newConnections.add(socketChannel); getSelector().wakeup(); } }