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

org.rapidoid.net.impl.RapidoidWorker Maven / Gradle / Ivy

The newest version!
/*-
 * #%L
 * rapidoid-net
 * %%
 * Copyright (C) 2014 - 2018 Nikolche Mihajlovski and contributors
 * %%
 * Licensed 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.
 * #L%
 */

package org.rapidoid.net.impl;

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.buffer.BufGroup;
import org.rapidoid.buffer.BufUtil;
import org.rapidoid.buffer.IncompleteReadException;
import org.rapidoid.collection.Coll;
import org.rapidoid.config.Conf;
import org.rapidoid.config.ConfigUtil;
import org.rapidoid.ctx.Ctxs;
import org.rapidoid.expire.ExpirationCrawlerThread;
import org.rapidoid.expire.Expire;
import org.rapidoid.log.Log;
import org.rapidoid.net.NetworkingParams;
import org.rapidoid.net.Protocol;
import org.rapidoid.pool.Pool;
import org.rapidoid.pool.Pools;
import org.rapidoid.u.U;
import org.rapidoid.util.SimpleList;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.Socket;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;


@Authors("Nikolche Mihajlovski")
@Since("2.0.0")
public class RapidoidWorker extends AbstractEventLoop implements NetWorker {

	public static int MAX_IO_WORKERS = 1024;

	public static boolean EXTRA_SAFE = false;

	private static final ExpirationCrawlerThread idleConnectionsCrawler;

	private static final int connTimeout;

	private final Queue connected;

	private final SimpleList done;

	private final Pool connections;

	private final Set allConnections = Coll.concurrentSet();

	final Protocol serverProtocol;

	final RapidoidHelper helper;

	private final int bufSize;

	private final boolean noDelay;

	private final long maxPipeline;

	private final BufGroup bufs;

	private volatile long messagesProcessed;

	private final SSLContext sslContext;

	RapidoidWorker next;

	static {
		int timeoutResolution = Conf.HTTP.entry("timeoutResolution").or(5000);
		connTimeout = Conf.HTTP.entry("timeout").or(30000);

		if (timeoutResolution > 0 && connTimeout > 0) {
			idleConnectionsCrawler = Expire.crawler("idleConnections", timeoutResolution);
		} else {
			idleConnectionsCrawler = null;
		}
	}

	public RapidoidWorker(String name, final RapidoidHelper helper, NetworkingParams net, SSLContext sslContext) {

		super(name);

		this.bufSize = net.bufSizeKB() * 1024;
		this.noDelay = net.noDelay();
		this.bufs = new BufGroup(bufSize, net.syncBufs());

		this.serverProtocol = net.protocol();
		this.helper = helper;
		this.sslContext = sslContext;

		this.maxPipeline = net.maxPipeline();

		final int queueSize = ConfigUtil.micro() ? 1000 : 1000000;
		final int growFactor = ConfigUtil.micro() ? 2 : 10;

		this.connected = new ArrayBlockingQueue(queueSize);
		this.done = new SimpleList(queueSize / 10, growFactor);

		connections = Pools.create("connections", new Callable() {
			@Override
			public RapidoidConnection call() throws Exception {
				return newConnection(false);
			}
		}, 100000);

		if (idleConnectionsCrawler != null) {
			idleConnectionsCrawler.register(allConnections);
		}
	}

	@Override
	public void accept(SocketChannel socketChannel) {
		connected.add(socketChannel);
		selector.wakeup();
	}

	private void configureSocket(SocketChannel socketChannel) throws IOException {
		socketChannel.configureBlocking(false);

		Socket socket = socketChannel.socket();
		socket.setTcpNoDelay(noDelay);
		socket.setReceiveBufferSize(bufSize);
		socket.setSendBufferSize(bufSize);
		socket.setReuseAddress(true);
	}

	@Override
	protected void readOP(SelectionKey key) throws IOException {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		RapidoidConnection conn = (RapidoidConnection) key.attachment();

		readInto(socketChannel, conn);

		process(conn);

		if (conn.closing) {
			close(key);
		}
	}

	private void readInto(SocketChannel socketChannel, RapidoidConnection conn) {
		int read;
		try {

			if (conn.hasTLS) {
				if (conn.tls.netIn.hasRemaining()) {
					read = socketChannel.read(conn.tls.netIn);

				} else {
					read = 0;
				}
			} else {
				read = conn.input.append(socketChannel);
			}

		} catch (Exception e) {
			Log.debug("Connection error", e);
			read = -1;
		}

		if (read == -1) {
			// the connection was closed
			Log.debug("The connection was closed!");
			conn.closing = true;

			if (conn.hasTLS) {
				conn.tls.closeInbound();
			}

		} else {

			if (conn.hasTLS) {
				if (read > 0) {
					boolean success = conn.tls.unwrapInput();
					if (success) wantToWrite(conn);
				}
			}
		}
	}

	@Override
	public void process(RapidoidConnection conn) {
		messagesProcessed += processMsgs(conn);

		conn.completedInputPos = conn.input.position();
	}

	private long processMsgs(RapidoidConnection conn) {
		long reqN = 0;

		while ((reqN < maxPipeline || maxPipeline <= 0) && conn.input().hasRemaining() && processNext(conn, false)) {
			reqN++;
		}

		touch(conn);

		return reqN;
	}

	private boolean processNext(RapidoidConnection conn, boolean initial) {

		long seq;

		if (initial) {
			// conn.log("<< INIT >>");

			seq = 0;
			conn.requestId = -1;
		} else {

			seq = conn.readSeq.incrementAndGet();

			// conn.log("<< PROCESS >>");
			U.must(conn.input().hasRemaining());

			conn.requestId = helper.requestIdGen;
			helper.requestIdGen += MAX_IO_WORKERS;
			helper.requestCounter++;
		}

		// prepare for a rollback in case the message isn't complete yet
		conn.input().checkpoint(conn.input().position());

		int limit = conn.input().limit();
		int osize = conn.output().size();

		BufUtil.doneWriting(conn.input());

		ConnState state = conn.state();
		long stateN = state.n;
		Object stateObj = state.obj;

		try {
			conn.done = false;
			conn.async = false;

			if (EXTRA_SAFE) {
				processNextExtraSafe(conn);
			} else {
				Protocol protocol = conn.getProtocol();

				if (protocol == null) {
					return false;
				}

				protocol.process(conn);
			}

			BufUtil.startWriting(conn.input());

			if (!conn.isAsync()) {
				if (!conn.closed) {
					conn.done();
				}

				conn.processedSeq(seq);
			}

			conn.input().deleteBefore(conn.input().checkpoint());

			// Log.debug("Completed message processing");
			return true;

		} catch (IncompleteReadException e) {

			// Log.debug("Incomplete message");
			conn.log("<< ROLLBACK >>");

			// input not complete, so rollback
			conn.input().position(conn.input().checkpoint());
			conn.input().limit(limit);
			BufUtil.startWriting(conn.input());

			state.n = stateN;
			state.obj = stateObj;

			boolean decreased = conn.readSeq.compareAndSet(seq, seq - 1);
			U.must(decreased, "Error in the request order control! Handle: %s", seq);

		} catch (ProtocolException e) {

			conn.log("<< PROTOCOL ERROR >>");
			Log.warn("Protocol error", "error", e);

			conn.output().deleteAfter(osize);
			conn.write(U.or(e.getMessage(), "Protocol error!"));
			conn.error();

			conn.processedSeq(seq);
			conn.close(true);

		} catch (Throwable e) {

			conn.log("<< ERROR >>");
			Log.error("Failed to process message!", e);

			conn.processedSeq(seq);
			conn.close(true);
		}

		return false;
	}

	private void processNextExtraSafe(RapidoidConnection conn) {
		if (Ctxs.hasContext()) {
			Log.warn("Detected unclosed context before processing message!");
			Ctxs.close();
		}

		try {
			conn.getProtocol().process(conn);
		} finally {
			if (Ctxs.hasContext()) {
				Log.warn("Detected unclosed context after processing message!");
				Ctxs.close();
			}
		}
	}

	@Override
	public void close(RapidoidConnection conn) {
		close(conn.key);
	}

	private void close(SelectionKey key) {
		try {
			if (key != null) {

				Object attachment = key.attachment();

				clearKey(key);

				if (attachment instanceof RapidoidConnection) {
					RapidoidConnection conn = (RapidoidConnection) attachment;

					if (!conn.closed) {
						Log.trace("Closing connection", "connection", conn);
						assert conn.key == key;
						conn.reset();
						connections.release(conn);
					}
				}
			}
		} catch (IOException e) {
			Log.warn("Error while closing connection!", e);
		}
	}

	private void clearKey(SelectionKey key) throws IOException {
		if (key.isValid()) {
			SocketChannel socketChannel = (SocketChannel) key.channel();
			socketChannel.close();
			key.attach(null);
			key.cancel();
		}
	}

	@Override
	protected void writeOP(SelectionKey key) throws IOException {
		RapidoidConnection conn = (RapidoidConnection) key.attachment();
		SocketChannel socketChannel = (SocketChannel) key.channel();

		checkOnSameThread();

		touch(conn);

		try {
			synchronized (conn) {
				synchronized (conn.outgoing) {

					if (conn.hasTLS) {
						synchronized (conn.output) {
							conn.tls.wrapToOutgoing();
						}
					}

					writeOp(key, conn, socketChannel);
				}
			}

		} catch (IOException e) {
			close(conn);

		} catch (CancelledKeyException cke) {
			Log.debug("Tried to write on canceled selector key!");
		}
	}

	private void writeOp(SelectionKey key, RapidoidConnection conn, SocketChannel socketChannel) throws IOException {

		synchronized (conn.outgoing) {
			if (conn.outgoing.hasRemaining()) {
				conn.log("WRITING");
//					conn.log(conn.outgoing.asText());
				BufUtil.startWriting(conn.outgoing);
				int wrote = conn.outgoing.writeTo(socketChannel);
				conn.outgoing.deleteBefore(wrote);
				BufUtil.doneWriting(conn.outgoing);
				conn.log("DONE WRITING");
			}
		}

		boolean finishedWriting, closeAfterWrite;
		synchronized (conn) {
			finishedWriting = conn.finishedWriting();
			closeAfterWrite = conn.closeAfterWrite();
		}

		if (finishedWriting && closeAfterWrite) {
			close(conn);

		} else {
			if (finishedWriting) {
				key.interestOps(SelectionKey.OP_READ);
			} else {
				key.interestOps(SelectionKey.OP_READ + SelectionKey.OP_WRITE);
			}
			conn.wrote(finishedWriting);
		}
	}

	@Override
	public void wantToWrite(RapidoidConnection conn) {
		touch(conn);

		if (onSameThread()) {
			conn.key.interestOps(SelectionKey.OP_WRITE);
		} else {
			wantToWriteAsync(conn);
		}
	}

	private void wantToWriteAsync(RapidoidConnection conn) {
		touch(conn);

		synchronized (done) {
			done.add(conn);
		}

		selector.wakeup();
	}

	@Override
	protected void doProcessing() {

		SocketChannel schannel;

		while ((schannel = connected.poll()) != null) {

			try {
				configureSocket(schannel);
			} catch (IOException e) {
				Log.error("Cannot configure channel!", e);
				continue;
			}

			RapidoidChannel channel = new RapidoidChannel(schannel, false, serverProtocol);

			SocketChannel socketChannel = channel.socketChannel;
			Log.debug("connected", "address", socketChannel.socket().getRemoteSocketAddress());

			try {
				SelectionKey newKey = socketChannel.register(selector, SelectionKey.OP_READ);
				U.notNull(channel.protocol, "protocol");
				RapidoidConnection conn = attachConn(newKey, channel.protocol);

				conn.setClient(channel.isClient);

				try {
					processNext(conn, true);
				} finally {
					conn.setInitial(false);
				}

			} catch (ClosedChannelException e) {
				Log.warn("Closed channel", e);
			}
		}

		synchronized (done) {
			for (int i = 0; i < done.size(); i++) {
				RapidoidConnection conn = done.get(i);
				if (conn.key != null && conn.key.isValid()) {
					conn.key.interestOps(SelectionKey.OP_WRITE);
				}
			}
			done.clear();
		}
	}

	private RapidoidConnection attachConn(SelectionKey key, Protocol protocol) {
		U.notNull(key, "protocol");
		U.notNull(protocol, "protocol");

		assert key.attachment() == null;

		RapidoidConnection conn = connections.get();

		// the connection is reset when closed
		// but a protocol can modify the connection after closing it
		// so it is reset again before reuse
		conn.reset();

		U.must(conn.closed);
		conn.closed = false;

		conn.key = key;
		conn.setProtocol(protocol);

//		if (protocol instanceof CtxListener) {
//			conn.setListener((CtxListener) protocol);
//		}

		key.attach(conn);

		touch(conn);

		return conn;
	}

	private void touch(RapidoidConnection conn) {
		conn.setExpiresAt(approxTime + connTimeout);
	}

	@Override
	protected void failedOP(SelectionKey key, Throwable e) {
		Log.error("Network error", e);
		close(key);
	}

	@Override
	public RapidoidConnection newConnection(boolean client) {
		U.must(!client, "Client connections are not supported by this worker!");
		RapidoidConnection conn = new RapidoidConnection(RapidoidWorker.this, bufs);
		allConnections.add(conn);
		return conn;
	}

	@Override
	public long getMessagesProcessed() {
		return messagesProcessed;
	}

	@Override
	protected synchronized void stopLoop() {
		super.stopLoop();

		done.clear();
		connected.clear();
		connections.clear();
		bufs.clear();
	}

	@Override
	public synchronized RapidoidWorker shutdown() {
		stopLoop();
		waitToStop();
		return this;
	}

	@Override
	public SSLContext sslContext() {
		return sslContext;
	}

	@Override
	public RapidoidHelper helper() {
		return helper;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy