
net.openhft.chronicle.network.TcpEventHandler Maven / Gradle / Ivy
/*
* Copyright 2016 higherfrequencytrading.com
*
* 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.
*/
package net.openhft.chronicle.network;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.Maths;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.io.IORuntimeException;
import net.openhft.chronicle.core.io.IOTools;
import net.openhft.chronicle.core.threads.EventHandler;
import net.openhft.chronicle.core.threads.HandlerPriority;
import net.openhft.chronicle.core.threads.InvalidEventHandlerException;
import net.openhft.chronicle.core.util.Time;
import net.openhft.chronicle.network.api.TcpHandler;
import net.openhft.chronicle.network.api.session.SessionDetailsProvider;
import net.openhft.chronicle.network.connection.TcpChannelHub;
import net.openhft.chronicle.wire.WireIn;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import static java.nio.ByteBuffer.allocateDirect;
import static net.openhft.chronicle.network.ServerThreadingStrategy.serverThreadingStrategy;
/**
* Created by peter.lawrey on 22/01/15.
*/
public class TcpEventHandler implements EventHandler, Closeable, TcpEventHandlerManager {
private static final int CAPACITY = Integer.getInteger("TcpEventHandler.capacity", TcpChannelHub.BUFFER_SIZE);
static final int TCP_BUFFER = Integer.getInteger("TcpEventHandler.tcpBufferSize", Math.max(64 << 10, CAPACITY / 8));
private static final Logger LOG = LoggerFactory.getLogger(TcpEventHandler.class);
@NotNull
private final SocketChannel sc;
private final NetworkContext nc;
private final SessionDetailsProvider sessionDetails;
@NotNull
private final WriteEventHandler writeEventHandler;
@NotNull
private final NetworkLog readLog, writeLog;
@NotNull
private final ByteBuffer inBB = allocateDirect(CAPACITY);
@NotNull
private final Bytes inBBB;
@NotNull
private final ByteBuffer outBB = allocateDirect(CAPACITY);
@NotNull
private final Bytes outBBB;
private int oneInTen;
private volatile boolean isCleaned;
@Nullable
private volatile TcpHandler tcpHandler;
private long lastTickReadTime = Time.tickTime();
private volatile boolean closed;
public TcpEventHandler(@NotNull NetworkContext nc) {
final boolean unchecked = nc.isUnchecked();
this.writeEventHandler = new WriteEventHandler();
this.sc = nc.socketChannel();
this.nc = nc;
try {
sc.configureBlocking(false);
sc.socket().setTcpNoDelay(true);
sc.socket().setReceiveBufferSize(TCP_BUFFER);
sc.socket().setSendBufferSize(TCP_BUFFER);
} catch (IOException e) {
LOG.info("", e);
}
// there is nothing which needs to be written by default.
this.sessionDetails = new VanillaSessionDetails();
try {
sessionDetails.clientAddress((InetSocketAddress) sc.getRemoteAddress());
} catch (IOException e) {
throw new IORuntimeException(e);
}
// allow these to be used by another thread.
// todo check that this can be commented out
// inBBB.clearThreadAssociation();
// outBBB.clearThreadAssociation();
inBBB = Bytes.wrapForRead(inBB.slice()).unchecked(unchecked);
outBBB = Bytes.wrapForWrite(outBB.slice()).unchecked(unchecked);
// must be set after we take a slice();
outBB.limit(0);
readLog = new NetworkLog(this.sc, "read");
writeLog = new NetworkLog(this.sc, "write");
}
@NotNull
@Override
public HandlerPriority priority() {
switch (serverThreadingStrategy()) {
case SINGLE_THREADED:
return HandlerPriority.HIGH;
case MULTI_THREADED_BUSY_WAITING:
return HandlerPriority.BLOCKING;
default:
throw new UnsupportedOperationException("todo");
}
}
@Override
public void tcpHandler(TcpHandler tcpHandler) {
nc.onHandlerChanged(tcpHandler);
this.tcpHandler = tcpHandler;
}
@Override
public synchronized boolean action() throws InvalidEventHandlerException {
final HeartbeatListener heartbeatListener = nc.heartbeatListener();
if (tcpHandler == null)
return false;
if (!sc.isOpen()) {
tcpHandler.onEndOfConnection(false);
Closeable.closeQuietly(nc);
// clear these to free up memory.
throw new InvalidEventHandlerException();
} else if (closed) {
Closeable.closeQuietly(nc);
throw new InvalidEventHandlerException();
}
boolean busy = false;
if (oneInTen++ >= 8) {
oneInTen = 0;
try {
busy |= writeEventHandler.action();
} catch (Exception e) {
LOG.error("", e);
}
}
try {
int start = inBB.position();
int read = inBB.remaining() > 0 ? sc.read(inBB) : Integer.MAX_VALUE;
if (read > 0) {
WanSimulator.dataRead(read);
tcpHandler.onReadTime(System.nanoTime());
lastTickReadTime = Time.tickTime();
// if (Jvm.isDebug())
// System.out.println("Read: " + read + " start: " + start + " pos: " + inBB
// .position());
readLog.log(inBB, start, inBB.position());
// inBB.position() where the data has been read() up to.
busy |= invokeHandler();
return busy;
}
if (read < 0) {
closeSC();
throw new InvalidEventHandlerException();
//return false;
}
readLog.idle();
if (nc.heartbeatTimeoutMs() == 0)
return busy;
long tickTime = Time.tickTime();
if (tickTime > lastTickReadTime + nc.heartbeatTimeoutMs()) {
if (heartbeatListener != null)
nc.heartbeatListener().onMissedHeartbeat();
closeSC();
throw new InvalidEventHandlerException();
}
} catch (ClosedChannelException e) {
closeSC();
throw new InvalidEventHandlerException();
} catch (IOException e) {
handleIOE(e, tcpHandler.hasClientClosed(), nc.heartbeatListener());
throw new InvalidEventHandlerException();
} catch (InvalidEventHandlerException e) {
throw e;
} catch (Exception e) {
LOG.error("", e);
}
return busy;
}
private synchronized void clean() {
if (isCleaned)
return;
isCleaned = true;
final long usedDirectMemory = Jvm.usedDirectMemory();
IOTools.clean(inBB);
IOTools.clean(outBB);
if (usedDirectMemory == Jvm.usedDirectMemory())
LOG.error("nothing cleaned");
}
boolean invokeHandler() throws IOException {
boolean busy = false;
inBBB.readLimit(inBB.position());
outBBB.writePosition(outBB.limit());
long lastInBBBReadPosition;
do {
lastInBBBReadPosition = inBBB.readPosition();
tcpHandler.process(inBBB, outBBB);
// did it write something?
if (outBBB.writePosition() > outBB.limit() || outBBB.writePosition() >= 4) {
outBB.limit(Maths.toInt32(outBBB.writePosition()));
busy |= tryWrite();
break;
}
} while (lastInBBBReadPosition != inBBB.readPosition());
// TODO Optimise.
// if it read some data compact();
if (inBBB.readPosition() > 0) {
inBB.position((int) inBBB.readPosition());
inBB.limit((int) inBBB.readLimit());
inBB.compact();
inBBB.readPosition(0);
busy = true;
}
return busy;
}
/*
private void ensureReadCapactity() {
// ensure that we always have 1024bytes head room
if (inBBB.writePosition() + 1024 > inBBB.realCapacity()) {
inBBB.ensureCapacity(inBBB.realCapacity() * 2);
inBB = inBBB.underlyingObject();
System.out.println("inBBB.realCapacity()=" + inBBB.realCapacity());
}
}
*/
private void handleIOE(@NotNull IOException e, final boolean clientIntentionallyClosed,
@Nullable HeartbeatListener heartbeatListener) {
try {
if (clientIntentionallyClosed)
return;
if (e.getMessage() != null && e.getMessage().startsWith("Connection reset by peer"))
LOG.trace("", e.getMessage());
else if (e.getMessage() != null && e.getMessage().startsWith("An existing connection " +
"was forcibly closed"))
LOG.warn(e.getMessage());
else if (!(e instanceof ClosedByInterruptException))
LOG.error("", e);
// The remote server has sent you a RST packet, which indicates an immediate dropping of the connection,
// rather than the usual handshake. This bypasses the normal half-closed state transition.
// I like this description: "Connection reset by peer" is the TCP/IP equivalent
// of slamming the phone back on the hook.
if (heartbeatListener != null)
heartbeatListener.onMissedHeartbeat();
} finally {
closeSC();
}
}
@Override
public void close() {
closed = true;
closeSC();
clean();
}
private void closeSC() {
try {
tcpHandler.close();
} catch (Exception ignored) {
}
try {
sc.close();
} catch (IOException ignored) {
}
Closeable.closeQuietly(nc);
}
private boolean tryWrite() throws IOException {
if (outBB.remaining() <= 0)
return false;
int start = outBB.position();
long writeTickTime = Time.tickTime();
long writeTime = System.nanoTime();
assert !sc.isBlocking();
int wrote = sc.write(outBB);
tcpHandler.onWriteTime(writeTime);
writeLog.log(outBB, start, outBB.position());
if (wrote < 0) {
closeSC();
} else if (wrote > 0) {
lastTickReadTime = writeTickTime;
outBB.compact().flip();
outBBB.writeLimit(outBB.capacity());
outBBB.writePosition(outBB.limit());
return true;
}
return false;
}
public static class Factory implements MarshallableFunction {
private Factory(WireIn wireIn) {
System.out.println(wireIn);
}
public Factory() {
}
@Override
public TcpEventHandler apply(NetworkContext nc) {
return new TcpEventHandler(nc);
}
}
private class WriteEventHandler implements EventHandler {
@Override
public boolean action() throws InvalidEventHandlerException {
if (!sc.isOpen()) throw new InvalidEventHandlerException();
boolean busy = false;
try {
// get more data to write if the buffer was empty
// or we can write some of what is there
int remaining = outBB.remaining();
busy = remaining > 0;
if (busy)
tryWrite();
if (outBB.remaining() == remaining) {
busy |= invokeHandler();
if (!busy)
busy = tryWrite();
}
} catch (ClosedChannelException cce) {
closeSC();
} catch (IOException e) {
if (!closed)
handleIOE(e, tcpHandler.hasClientClosed(), nc.heartbeatListener());
}
return busy;
}
public HandlerPriority priority() {
return HandlerPriority.CONCURRENT;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy