package ch.usi.da.dlog;
/*
* Copyright (c) 2014 Università della Svizzera italiana (USI)
*
* This file is part of URingPaxos.
*
* URingPaxos is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* URingPaxos is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with URingPaxos. If not, see <http://www.gnu.org/licenses/>.
*/
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.log4j.Logger;
import ch.usi.da.dlog.message.Command;
import ch.usi.da.dlog.message.CommandType;
import ch.usi.da.dlog.message.Message;
import ch.usi.da.dlog.transport.ABListener;
import ch.usi.da.dlog.transport.RawABListener;
import ch.usi.da.dlog.transport.Receiver;
import ch.usi.da.dlog.transport.UDPSender;
import ch.usi.da.paxos.Util;
import ch.usi.da.paxos.ring.RingDescription;
/**
* Name: Server<br>
* Description: <br>
*
* Creation date: Apr 07, 2014<br>
* $Id$
*
* @author Samuel Benz benz@geoid.ch
*/
public class Server implements Receiver {
static {
// get hostname and pid for log file name
String host = "localhost";
try {
Process proc = Runtime.getRuntime().exec("hostname");
BufferedInputStream in = new BufferedInputStream(proc.getInputStream());
byte [] b = new byte[in.available()];
in.read(b);
in.close();
host = new String(b).replace("\n","");
} catch (IOException e) {
}
int pid = 0;
try {
pid = Integer.parseInt((new File("/proc/self")).getCanonicalFile().getName());
} catch (NumberFormatException | IOException e) {
}
System.setProperty("logfilename", host + "-" + pid + ".log");
}
private final static Logger logger = Logger.getLogger(Server.class);
private final List<RingDescription> rings;
private final UDPSender udp;
private final ABListener ab;
private long next_position = 0;
private final int read_size = 5;
private SeekableByteChannel log;
private final ByteBuffer buffer = ByteBuffer.allocate(200*1024*1024); // 200 MB
private final int compact_size = (int)buffer.capacity()/5; // 20%
private long buffer_offset = next_position;
@SuppressWarnings("unchecked")
private final TreeMap<Long,Long>[] index = new TreeMap[20]; // max ringID = 20
private long last_trim = -1;
public Server(int serverID, List<RingDescription> rings, String zoo_host) throws Exception {
this.rings = rings;
udp = new UDPSender();
ab = new RawABListener(serverID,zoo_host,rings);
String path = "/tmp";
String db_path = System.getenv("DLOG");
if(db_path != null){
path = db_path;
}
Path file = Paths.get(path + "/dlog-0.bin");
log = Files.newByteChannel(file, EnumSet.of(CREATE, TRUNCATE_EXISTING, WRITE, READ));
for(RingDescription ring : rings){
index[ring.getRingID()] = new TreeMap<Long,Long>();
}
}
public void start(){
ab.registerReceiver(this);
Thread t = new Thread((Runnable) ab);
t.setName("ABListener");
t.start();
}
public void close(){
ab.close();
}
@Override
public synchronized void receive(Message m) {
logger.debug("Server received ring " + m.getRing() + " instnace " + m.getInstnce() + " (" + m + ")");
if(m.isSkip()){
return;
}
List<Command> cmds = new ArrayList<Command>();
for(Command c : m.getCommands()){
Command cmd;
switch(c.getType()){
case APPEND:
case MULTIAPPEND:
// index
index[m.getRing()].put(next_position,m.getInstnce());
// file
try {
log.position(next_position - last_trim);
log.write(ByteBuffer.wrap(c.getValue()));
} catch (IOException e) {
logger.error("Error writing to seekable byte channel!",e);
}
// buffer
buffer.position((int)(next_position - buffer_offset));
if(buffer.remaining() < c.getValue().length){ // compact if no space
logger.debug("Buffer compaction required!");
buffer_offset += compact_size;
buffer.position(compact_size);
buffer.compact();
}
buffer.position((int)(next_position - buffer_offset));
buffer.put(c.getValue());
cmd = new Command(c.getID(),CommandType.RESPONSE,next_position,new byte[0]);
cmds.add(cmd);
next_position += c.getValue().length;
break;
case READ:
if(c.getPosition() < last_trim){
return;
}
byte[] b = new byte[0];
int to_read = read_size;
if(c.getValue().length > 0){
to_read = Integer.parseInt(new String(c.getValue()));
}else{
to_read = read_size;
}
if((c.getPosition() + to_read) >= next_position){
to_read = (int) (next_position - c.getPosition());
}
if(to_read > 0){
b = new byte[to_read];
}else{
logger.error("read_size is <= 0!");
return;
}
// file
if(c.getPosition() < buffer_offset){
logger.warn("Read position from disk!");
try {
log.position(c.getPosition()-last_trim);
log.read(ByteBuffer.wrap(b));
} catch (IOException e) {
logger.error("error reading from seekable byte channel!",e);
}
}else{ // buffer
int buffer_position = (int)(c.getPosition()-buffer_offset);
if((buffer_position + to_read) <= buffer.capacity()){
buffer.position((int)(c.getPosition()-buffer_offset));
buffer.get(b);
}else{
logger.error("requested position is bigger than buffer!");
}
}
cmd = new Command(c.getID(),CommandType.RESPONSE,c.getPosition(),b);
cmds.add(cmd);
break;
case TRIM:
try {
Path file = Paths.get("/tmp/dlog-" + c.getPosition() + ".bin");
SeekableByteChannel log2 = Files.newByteChannel(file, EnumSet.of(CREATE, TRUNCATE_EXISTING, WRITE, READ));
((FileChannel)log).transferTo(c.getPosition()+1,log.size(),log2);
log.close(); // maybe also delete?
log = log2;
for(RingDescription ring : rings){
System.err.println(index[ring.getRingID()]);
long inc_pos = index[ring.getRingID()].floorKey(c.getPosition());
Entry<Long, Long> pos = index[ring.getRingID()].floorEntry(inc_pos-1);
long instance = pos.getValue();
if(instance > 0){
ab.safe(ring.getRingID(),instance);
logger.debug("set safe ring " + ring.getRingID() + " to instance " + instance);
}
index[ring.getRingID()] = new TreeMap<Long,Long>(index[ring.getRingID()].tailMap(pos.getKey()));
}
last_trim = c.getPosition();
cmd = new Command(c.getID(),CommandType.RESPONSE,c.getPosition(),new byte[0]);
cmds.add(cmd);
} catch (Exception e) {
logger.error("Error trimming file channel!",e);
}
break;
default:
System.err.println("Receive RESPONSE as Command!"); break;
}
}
logger.debug("offset: " + next_position + " buffer_pos: " + buffer.position() + " buffer_offset: " + buffer_offset);
int msg_id = new String(m.getInstnce() + "-" + next_position).hashCode();
Message msg = new Message(msg_id,rings.get(0).toString(),m.getFrom(),cmds);
udp.send(msg);
}
/**
* @param args
*/
public static void main(String[] args) {
String zoo_host = "127.0.0.1:2181";
if (args.length > 2) {
zoo_host = args[2];
}
if (args.length < 2) {
System.err.println("Plese use \"Server\" \"serverID\" \"ring ID:roles[;ringID:roles]\" (eg. 1 1:PAL) [zookeeper host]");
} else {
try {
int serverID = Integer.parseInt(args[0]);
final Server server = new Server(serverID, Util.parseRingsArgument(args[1]),zoo_host);
Runtime.getRuntime().addShutdownHook(new Thread("ShutdownHook"){
@Override
public void run(){
server.close();
}
});
server.start();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
in.readLine();
server.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}