URingPaxos-master/src/java/ch/usi/da/paxos/ring/LearnerRole.java
package ch.usi.da.paxos.ring;
/* 
 * Copyright (c) 2015 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 java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.log4j.Logger;

import ch.usi.da.paxos.api.ConfigKey;
import ch.usi.da.paxos.api.Learner;
import ch.usi.da.paxos.api.PaxosRole;
import ch.usi.da.paxos.message.Message;
import ch.usi.da.paxos.message.MessageType;
import ch.usi.da.paxos.message.Value;
import ch.usi.da.paxos.storage.Decision;

/**
 * Name: LearnerRole<br>
 * Description: <br>
 * 
 * Creation date: Aug 12, 2012<br>
 * $Id$
 * 
 * @author Samuel Benz benz@geoid.ch
 */
public class LearnerRole extends Role implements Learner {

	private final static Logger logger = Logger.getLogger(LearnerRole.class);
		
	private final RingManager ring;
	
	private final Map<String,Value> learned = new ConcurrentHashMap<String,Value>();
	
	private final LinkedList<Decision> delivery = new LinkedList<Decision>();
	
	private final BlockingQueue<Decision> values = new LinkedBlockingQueue<Decision>();
	
	// at most once delivery for the most recent 500k Values
	Set<String> delivered = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>(){
		private static final long serialVersionUID = -5679181663800465934L;
		protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) {
	        return size() > 500000;
	    }
	});
		
	private long next_instance = 1; // only needed to optimize linked list insert
	
	private long delivered_instance = 0;
	
	private long safe_instance = 0;
	
	private long highest_online_instance = 0;
	
	private boolean auto_trim = false; // for testing purpose (safe_instance = delivered_instance)
	
	private boolean recovery = false;
	
	private volatile boolean recovered = false;

	public volatile boolean terminate = false;
			
	public long deliver_count = 0;
	
	public long batch_count = 0;
	
	public long deliver_bytes = 0;
	
	/**
	 * @param ring
	 */
	public LearnerRole(RingManager ring) {
		this.ring = ring;
		if(ring.getConfiguration().containsKey(ConfigKey.learner_recovery)){
			if(ring.getConfiguration().get(ConfigKey.learner_recovery).equals("1")){
				recovery = true;
			}
			logger.info("Learner recovery: " + (recovery ? "enabled" : "disabled"));
		}
		if(ring.getConfiguration().containsKey(ConfigKey.auto_trim)){
			if(ring.getConfiguration().get(ConfigKey.auto_trim).equals("1")){
				auto_trim = true;
			}
			logger.info("Learner auto_trim: " + (auto_trim ? "enabled" : "disabled"));
		}
	}

	@Override
	public void run() {		
		ring.getNetwork().registerCallback(this);
		Thread t = new Thread(new LearnerStatsWriter(ring,this));
		t.setName("LearnerStatsWriter");
		t.start();
		while(!terminate){
			try{
				Decision head = delivery.peek();
				// initial recovering
				if(!recovered && recovery && head != null){
					Message m = new Message(0,ring.getNodeID(),PaxosRole.Leader,MessageType.Safe,0,0,new Value("","0".getBytes()));
					m.setVoteCount(1);
					logger.debug("Send safe message to recover highest_online_instance. (" + recovered + "," + delivery.isEmpty() + ")");
					ring.getNetwork().send(m);
				}
				// re-request missing decisions
				else if(head != null && head.getInstance() > delivered_instance){
					for(long i=delivered_instance+1;i<head.getInstance();i++){
						if(highest_online_instance == 0 || i >= highest_online_instance){
							Message m = new Message(i,ring.getNodeID(),PaxosRole.Leader,MessageType.Relearn,0,0,null);
							logger.warn("Learner re-request missing instance " + i);
							ring.getNetwork().receive(m);
						}else{
							recovered = false;
						}
					}
				}
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				break;				
			}
		}
	}
	
	public synchronized void deliver(RingManager fromRing,Message m){
		/*if(logger.isDebugEnabled()){
			logger.debug("learner " + ring.getNodeID() + " received " + m);
		}*/
		if(m.getType() == MessageType.Decision){
			Decision d = null;
			if(learned.get(m.getValue().getID()) != null){
				// value was previously learned with an other message
				d = new Decision(fromRing.getRingID(),m.getInstance(),m.getBallot(),learned.get(m.getValue().getID()));
				learned.remove(m.getValue().getID());
			}else{
				d = new Decision(fromRing.getRingID(),m.getInstance(),m.getBallot(),m.getValue());
			}
			if(d != null && d.getValue().getValue().length > 0){
				if(!recovered && !recovery){
					next_instance = d.getInstance();
					delivered_instance = d.getInstance()-1;
					logger.info("Learner start from instance " + d.getInstance() + " without recovery");
				}
				if(d.getInstance() == next_instance){
					delivery.add(d);
					next_instance++;
				}else{
					int pos = findPos(d.getInstance());
					if(pos >= 0){
						delivery.add(pos,d);
						if(pos == delivery.size()-1){
							next_instance = d.getInstance().longValue()+1;
						}
					}
				}
				// deliver sorted instances
				Decision head = null;
				while((head = delivery.peek()) != null && head.getInstance()-1 <= delivered_instance){
					if(head.getInstance()-1 == delivered_instance){
						recovered = true;
						Decision de = delivery.poll();
						delivered_instance = de.getInstance();
						if(auto_trim) { safe_instance = delivered_instance; }
						deliver_bytes = deliver_bytes + de.getValue().getValue().length;
						if(de.getValue().isBatch()){
							batch_count++;
							ByteBuffer buffer = ByteBuffer.wrap(de.getValue().getValue());
							while(buffer.remaining() > 0){
								try {
									Message n = Message.fromBuffer(buffer);
									// decision from a batch will contain same instance number for all entries !!!
									Decision bd = new Decision(fromRing.getRingID(),m.getInstance(),m.getBallot(),n.getValue());
									deliver_count++;
									if(!delivered.contains(bd.getValue().getID())){
										values.add(bd);
										delivered.add(bd.getValue().getID());
									}
								} catch (Exception e) {
									logger.error("Learner could not de-serialize batch message!" + e);
								}
							}
						}else{
							deliver_count++;
							if(!delivered.contains(de.getValue().getID())){
								values.add(de);
								delivered.add(de.getValue().getID());
							}
						}
					}else{
						delivery.poll(); // remove duplicate
					}
				}
			}else{
				logger.error("Learner received decision without value! " + m);
			}
		}else if(m.getType() == MessageType.Safe){
			Value v = null;
			if(m.getValue().getValue().length == 0){
				v = new Value(m.getValue().getID(),String.valueOf(safe_instance).getBytes());
			}else{
				String s = new String(m.getValue().getValue());
				v = new Value(m.getValue().getID(),(s + ";" + String.valueOf(safe_instance)).getBytes());
			}
			Message n = new Message(m.getInstance(),m.getSender(),m.getReceiver(),m.getType(),m.getBallot(),m.getBallot(),v);
			n.setVoteCount(m.getVoteCount()+1);
			ring.getNetwork().send(n);
		}else if(m.getType() == MessageType.Trim){
			highest_online_instance = m.getInstance()+1;
			if(!recovered){ // recover only what is available
				delivered_instance = highest_online_instance == 0 ? highest_online_instance : highest_online_instance-1;
				recovered = true;
			}
			logger.debug("Learner notified last highest_online_instance: " + highest_online_instance);
		}else{
			if(learned.get(m.getValue().getID()) == null){
				learned.put(m.getValue().getID(),m.getValue());
			}
		}		
	}
	
	@Override
	public BlockingQueue<Decision> getDecisions(){
		return values;
	}
	
	public Map<String, Value> getLearned(){
		return Collections.unmodifiableMap(learned);
	}
	
	@Override
	public void setSafeInstance(Integer ring,Long instance){
		if(instance <= delivered_instance){
			safe_instance = instance.longValue();
		}else{
			logger.error("Learner setSafeInstance() for not delivered instance number?!");
		}
	}
	
	public void close(){
		terminate = true;
	}
	
	private int findPos(long instance) {
		int pos = 0;
		Iterator<Decision> i = delivery.iterator();
		while(i.hasNext()){
			Decision d = i.next();
			if(instance == d.getInstance().longValue()){
				return -1;
			}else if(instance < d.getInstance().longValue()){
				return pos;
			}
			pos++;
		}
		return pos;
	}
}
