/*
* 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.core.Jvm;
import net.openhft.chronicle.core.io.Closeable;
import net.openhft.chronicle.core.threads.EventHandler;
import net.openhft.chronicle.core.threads.EventLoop;
import net.openhft.chronicle.core.threads.HandlerPriority;
import net.openhft.chronicle.core.threads.InvalidEventHandlerException;
import net.openhft.chronicle.core.util.ThrowingFunction;
import net.openhft.chronicle.network.connection.TcpChannelHub;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.AlreadyConnectedException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import static net.openhft.chronicle.network.NetworkStatsListener.*;
public class RemoteConnector implements Closeable {
@NotNull
private final ThrowingFunction<NetworkContext, TcpEventHandler, IOException> tcpHandlerSupplier;
private final Integer tcpBufferSize;
private volatile boolean closed;
@Nullable
private volatile List<Closeable> closeables = new ArrayList<>();
public RemoteConnector(@NotNull final ThrowingFunction<NetworkContext, TcpEventHandler, IOException> tcpEventHandlerFactory) {
this.tcpBufferSize = Integer.getInteger("tcp.client.buffer.size", TcpChannelHub.TCP_BUFFER);
this.tcpHandlerSupplier = tcpEventHandlerFactory;
}
private static void closeSocket(SocketChannel socketChannel) {
Closeable.closeQuietly(socketChannel);
}
public void connect(@NotNull final String remoteHostPort,
@NotNull final EventLoop eventLoop,
@NotNull NetworkContext nc,
final long retryInterval) {
final InetSocketAddress address = TCPRegistry.lookup(remoteHostPort);
@NotNull final RCEventHandler handler = new RCEventHandler(
remoteHostPort,
nc,
eventLoop,
address, retryInterval);
eventLoop.addHandler(handler);
}
@Override
public void close() {
if (closed)
return;
closed = true;
@Nullable final List<Closeable> closeables = this.closeables;
this.closeables = null;
closeables.forEach(Closeable::closeQuietly);
}
private SocketChannel openSocketChannel(InetSocketAddress socketAddress) throws IOException {
final SocketChannel result = SocketChannel.open(socketAddress);
result.configureBlocking(false);
Socket socket = result.socket();
socket.setTcpNoDelay(true);
socket.setReceiveBufferSize(tcpBufferSize);
socket.setSendBufferSize(tcpBufferSize);
socket.setSoTimeout(0);
socket.setSoLinger(false, 0);
return result;
}
private class RCEventHandler implements EventHandler, Closeable {
@NotNull
@Override
public HandlerPriority priority() {
return HandlerPriority.BLOCKING;
}
private final InetSocketAddress address;
private final AtomicLong nextPeriod = new AtomicLong();
private final String remoteHostPort;
private final NetworkContext nc;
private final EventLoop eventLoop;
private final long retryInterval;
private volatile boolean closed;
RCEventHandler(String remoteHostPort,
NetworkContext nc,
EventLoop eventLoop,
InetSocketAddress address, long retryInterval) {
this.remoteHostPort = remoteHostPort;
this.nc = nc;
this.eventLoop = eventLoop;
this.address = address;
this.retryInterval = retryInterval;
}
@Override
public boolean action() throws InvalidEventHandlerException, InterruptedException {
if (closed)
throw new InvalidEventHandlerException();
final long time = System.currentTimeMillis();
if (time > nextPeriod.get())
nextPeriod.set(time + retryInterval);
else
return false;
final SocketChannel sc;
final TcpEventHandler eventHandler;
try {
sc = RemoteConnector.this.openSocketChannel(address);
if (sc == null)
return false;
nc.socketChannel(sc);
nc.isAcceptor(false);
notifyHostPort(sc, nc.networkStatsListener());
eventHandler = tcpHandlerSupplier.apply(nc);
} catch (AlreadyConnectedException e) {
Jvm.debug().on(getClass(), e);
throw new InvalidEventHandlerException();
} catch (IOException e) {
nextPeriod.set(System.currentTimeMillis() + retryInterval);
return false;
}
eventLoop.addHandler(eventHandler);
@Nullable final List<Closeable> closeables = RemoteConnector.this.closeables;
if (closeables == null)
// we have died.
Closeable.closeQuietly(eventHandler);
else
closeables.add(() -> closeSocket(sc));
throw new InvalidEventHandlerException();
}
@NotNull
@Override
public String toString() {
return getClass().getSimpleName() + "{"
+ "remoteHostPort=" + remoteHostPort
+ ", closed=" + closed +
"}";
}
@Override
public void close() {
closed = true;
}
@Override
public void notifyClosing() {
closed = true;
}
}
}