/*
* Copyright 2011 Peter Lawrey
*
* 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 vanilla.java.chronicle.tcp;
import vanilla.java.chronicle.Chronicle;
import vanilla.java.chronicle.Excerpt;
import vanilla.java.chronicle.impl.IndexedChronicle;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A Chronicle as a service to be replicated to any number of clients. Clients can restart from where ever they are up to.
* <p/>
* Can be used ad a component or run as a stand alone service.
*
* @author peter.lawrey
*/
public class ChronicleSource<C extends Chronicle> implements Closeable {
private final C chronicle;
private final ServerSocketChannel server;
private final int delayNS;
private final String name;
private final ExecutorService service;
private final Logger logger;
private volatile boolean closed = false;
public ChronicleSource(C chronicle, int port, int delayNS) throws IOException {
this.chronicle = chronicle;
this.delayNS = delayNS;
server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(port));
name = chronicle.name() + "@" + port;
logger = Logger.getLogger(getClass().getName() + "." + name);
service = Executors.newCachedThreadPool(new NamedThreadFactory(name));
service.execute(new Acceptor());
}
public static void main(String... args) throws IOException {
if (args.length < 2) {
System.err.println("Usage: java " + ChronicleSource.class.getName() + " {chronicle-base-path} {port} [delayNS]");
System.exit(-1);
}
int dataBitsHintSize = Integer.getInteger("dataBitsHintSize", 24);
String def = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? "Big" : "Little";
ByteOrder byteOrder = System.getProperty("byteOrder", def).equalsIgnoreCase("Big") ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
String basePath = args[0];
int port = Integer.parseInt(args[1]);
int delayNS = 5 * 1000 * 1000;
if (args.length > 2)
delayNS = Integer.parseInt(args[2]);
IndexedChronicle ic = new IndexedChronicle(basePath, dataBitsHintSize, byteOrder);
ChronicleSource cs = new ChronicleSource(ic, port, delayNS);
}
class Acceptor implements Runnable {
@Override
public void run() {
Thread.currentThread().setName(name + "-acceptor");
try {
while (!closed) {
SocketChannel socket = server.accept();
service.execute(new Handler(socket));
}
} catch (IOException e) {
if (!closed)
logger.log(Level.SEVERE, "Acceptor dying", e);
}
}
}
class Handler implements Runnable {
private final SocketChannel socket;
public Handler(SocketChannel socket) {
this.socket = socket;
}
@Override
public void run() {
try {
long index = readIndex(socket);
Excerpt excerpt = chronicle.createExcerpt();
ByteBuffer bb = TcpUtil.createBuffer(1, chronicle);
while (!closed) {
while (!excerpt.index(index))
pause(delayNS);
int size = excerpt.capacity();
int capacity = size + TcpUtil.HEADER_SIZE;
if (capacity > bb.capacity())
bb = TcpUtil.createBuffer(capacity, chronicle);
bb.clear();
bb.putLong(index);
bb.putLong(size);
excerpt.read(bb);
bb.flip();
while (bb.remaining() > 0 && socket.write(bb) > 0) ;
if (bb.remaining() > 0) throw new EOFException("Failed to send index=" + index);
}
} catch (IOException e) {
if (!closed)
logger.log(Level.INFO, "Connect " + socket + " died", e);
}
}
private long readIndex(SocketChannel socket) throws IOException {
ByteBuffer bb = ByteBuffer.allocate(8);
while (bb.remaining() > 0 && socket.read(bb) > 0) ;
if (bb.remaining() > 0) throw new EOFException();
return bb.getLong(0);
}
}
protected void pause(int delayNS) {
if (delayNS < 1) return;
if (delayNS < 20000)
Thread.yield();
else
LockSupport.parkNanos(delayNS);
}
@Override
public void close() throws IOException {
closed = true;
service.shutdown();
try {
service.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
chronicle.close();
}
}