All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jadex.platform.service.message.streams.OutputConnectionHandler Maven / Gradle / Ivy

package jadex.platform.service.message.streams;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;

import jadex.bridge.IComponentStep;
import jadex.bridge.IInternalAccess;
import jadex.bridge.service.annotation.Timeout;
import jadex.commons.SUtil;
import jadex.commons.Tuple2;
import jadex.commons.future.CounterResultListener;
import jadex.commons.future.DelegationResultListener;
import jadex.commons.future.Future;
import jadex.commons.future.IFuture;
import jadex.commons.future.IResultListener;
import jadex.platform.service.message.MessageService;

//import javax.swing.JPanel;
//import javax.swing.JTextField;
//import javax.swing.Timer;

/**
 *  The output connection handler. 
 */
public class OutputConnectionHandler extends AbstractConnectionHandler implements IOutputConnectionHandler
{
	//-------- attributes --------
	
	/** The data sent (not acknowledged). */
	protected Map sent;
	
	/** The data to send. */
	protected List>> tosend;

	/** The current sequence number. */
	protected int seqnumber;
	
	/** The max number of packets that can be sent without an ack is received. */
	protected int maxsend;
	
	/** The max number of messages that can be sending concurrently (i.e. passed to message service but sending not yet completed). */
	protected int maxqueued;

	/** The number of received elements after which an ack is sent. */
	protected int ackcnt;
	
	/** The number of sending messages (i.e. passed to message service but sending not yet completed). */
	protected int queuecnt;
	
	/** The acknowledgement timer. */
	protected TimerTask acktimer;
	
	
	/** Flag if multipackets should be used. */
	protected boolean multipackets;
	
	/** The packet size to collect (in bytes). */
	protected int mpmaxsize;
	
	/** The collected data for a packet. */
	protected List multipacket;
	
	/** The current multipacket size. */
	protected int mpsize;
	
	/** The current multipacket future (shared by all write requests that put data in the same multi packet). */
	protected Future	mpfut;
	
	/** The max delay before a multipacket is sent (even if not full). */
	protected long mpsendtimeout;

	/** The multipacket send timer. */
	protected TimerTask mpsendtimer;
	
	
	/** Close request flag (when a closereq message was received). */
	protected boolean closereqflag;

	/** Stop flag (is sent in ack from input side) to signal that the rceiver is flooded with data). */
	protected Tuple2 stopflag;
	
	/** Flag if close was already sent. */
	protected boolean closesent;
	
	/** Future used in waitForReady(). */
	protected Future readyfuture;
	
	//-------- constructors --------
	
	/**
	 *  Create a new handler.
	 */
	public OutputConnectionHandler(MessageService ms, Map nonfunc)
	{
		super(ms, nonfunc);//, maxresends, acktimeout, leasetime);
		this.tosend = new ArrayList>>();
		this.sent = new LinkedHashMap();
		this.seqnumber = 0;
		
		this.maxsend = 200;
		this.maxqueued = 4;
		this.ackcnt = 10;
		
		this.multipackets = true;
		this.mpmaxsize = 4500;
		this.multipacket = new ArrayList();
		this.mpsize = 0;
		this.mpsendtimeout = 3000;
		this.stopflag = new Tuple2(Boolean.FALSE, -1);
		
//		createDataTimer();
	}
	
	//-------- methods called from message service --------
	
	/**
	 *  Received a request to close the connection.
	 */
	public void closeRequestReceived()
	{
		scheduleStep(new IComponentStep()
		{
			public IFuture execute(IInternalAccess ia)
			{
				closereqflag = true;
				checkClose();
				sendTask(createTask(StreamSendTask.ACKCLOSEREQ, null, null, nonfunc));
				return IFuture.DONE;
			}
		});
	}
	
	/**
	 *  Called from message service.
	 *  
	 *  Uses: sent, lastack
	 */
	public void ackDataReceived(final AckInfo ackinfo)
	{
		scheduleStep(new IComponentStep()
		{
			public IFuture execute(IInternalAccess ia)
			{
				// Update stop if newer sequence number
				if(stopflag.getSecondEntity().intValue()(ackinfo.isStop()? Boolean.TRUE: Boolean.FALSE, Integer.valueOf(ackinfo.getEndSequenceNumber()));
				
				// remove all acked packets
				for(int i=ackinfo.getStartSequenceNumber(); i<=ackinfo.getEndSequenceNumber(); i++)
				{
					DataSendInfo tup = sent.remove(Integer.valueOf(i));
					if(tup!=null)
					{
						tup.getFuture().setResult(null);
					}
				}
					
//				System.out.println("ack "+System.currentTimeMillis()+": seq="+seqnumber+" stop="+ackinfo.isStop()+" startack="+ackinfo.getStartSequenceNumber()+" endack="+ackinfo.getEndSequenceNumber()+" sent="+sent.size());
//				System.out.println(sent);
				
				// Trigger resend of unacknowledged messages, if necessary.
				checkResend();
				
				// Try to send stored messages after some others have been acknowledged
				sendStored();
				
				// Check ready state.
				checkWaitForReady();
				
				// Try to close if close is requested.
				checkClose();
			
				return IFuture.DONE;
			}
		});
	}
	
	//-------- methods called from connection ---------
	
	/**
	 * 
	 */
	public void notifyInited()
	{
		scheduleStep(new IComponentStep()
		{
			public IFuture execute(IInternalAccess ia)
			{
				checkWaitForReady();
				sendStored();
				checkClose();
				return IFuture.DONE;
			}
		});
	}
	
	/**
	 *  Called from connection.
	 *  Initiates closing procedure (is different for initiator and participant).
	 */
	public IFuture doClose()
	{
		final Future ret = new Future();

		scheduleStep(new IComponentStep()
		{
			public IFuture execute(IInternalAccess ia)
			{
//				System.out.println("do close output side");
				closereqflag = true;
				checkClose();
				
				return IFuture.DONE;
			}
		}).addResultListener(new IResultListener()
		{
			public void resultAvailable(Void result)
			{
			}
			public void exceptionOccurred(Exception exception)
			{
				con.setClosed();
				ret.setException(exception);
			}
		});
		
		return ret;
	}
	
	/**
	 *  Called from connection.
	 *  
	 *  Uses: sent, tosend
	 */
	public IFuture send(final byte[] dat)
	{
		// Todo: need to copy dat in case user uses array otherwise...
		
		final Future ret = new Future();
//		ret.addResultListener(new IResultListener()
//		{
//			public void resultAvailable(Void result)
//			{
//				System.out.println("send end");
//			}
//			public void exceptionOccurred(Exception exception)
//			{
//				System.out.println("send end ex");
//			}
//		});

		scheduleStep(new IComponentStep()
		{
			public IFuture execute(IInternalAccess ia)
			{
//				System.out.println("called send: "+sent.size());
				
				sendStored();

				if(multipackets)
				{
					addMultipacket(dat).addResultListener(new DelegationResultListener(ret));
				}
				else
				{
					StreamSendTask task = (StreamSendTask)createTask(StreamSendTask.DATA, dat, getNextSequenceNumber(), nonfunc);
					doSendData(task).addResultListener(new DelegationResultListener(ret));
				}
				
				// Check ready state.
				checkWaitForReady();
				
				return IFuture.DONE;
			}
		});
	
		return ret;
	}
	
	/**
	 *  Flush the data.
	 */
	public void flush()
	{
		scheduleStep(new IComponentStep()
		{
			public IFuture execute(IInternalAccess ia)
			{
				if(multipackets)
				{
					sendAcknowledgedMultiPacket();
				}
				sendStored();
				checkWaitForReady();
				return IFuture.DONE;
			}
		});
	}
	
	/**
	 *  Wait until the connection is ready for the next write.
	 *  @return Calls future when next data can be written. Provides a value of how much data should be given to the connection for best performance.
	 */
	public IFuture waitForReady()
	{
		final Future ret = new Future();
		
		scheduleStep(new IComponentStep()
		{
			public IFuture execute(IInternalAccess ia)
			{
				if(readyfuture!=null)
				{
					ret.setException(new RuntimeException("Must not be called twice without waiting for result."));
				}
				else
				{
//					System.out.println("readyfuture inited");
					readyfuture = ret;
					checkWaitForReady();
				}
				return IFuture.DONE;
			}
		});
		
		return ret;
	}
	
	//-------- internal methods (single threaded) --------
	
	/**
	 * 
	 */
	protected void checkWaitForReady()
	{
		if(readyfuture!=null)
		{
//			System.out.println("waitforready: "+con.isInited()+" "+(maxsend-sent.size())+" "+isStop()+" "+isClosed());
			if(isSendAllowed() && !isClosed())
			{
//				System.out.println("readyfuture fired");
				Future ret = readyfuture;
				readyfuture = null;
//				ret.setResult(Integer.valueOf(mpmaxsize));	// todo: packet size*allowed messages?
				int pa = sent.size()-maxsend;
				ret.setResult(Integer.valueOf(pa>0? pa*mpmaxsize: mpmaxsize));	
			}
			else if(isClosed())
			{
				Future ret = readyfuture;
				readyfuture = null;
				ret.setException(new RuntimeException("Connection closed."));
			}
		}
	}
	
	/**
	 * 
	 */
	protected IFuture doSendData(StreamSendTask task)
	{
		IFuture ret;
		
		if(isSendAllowed())
		{
//			System.out.println("send "+System.currentTimeMillis()+": "+task.getSequenceNumber());
			ret = sendData(task);
		}
		else
		{
//			System.out.println("store "+System.currentTimeMillis()+": "+task.getSequenceNumber());
			ret = new Future();
			tosend.add(new Tuple2>(task, (Future)ret));
		}
		
		return ret;
	}
	
	/**
	 *  Called internally. 
	 * 
	 *  Uses: sent, tosend
	 */
	protected void sendStored()
	{
//		System.out.println("sendStored: sent="+sent.size()+", allowed="+allowed+", tosend="+tosend.size());
		
		// Cannot use just isSendAllowed() as at least one message
		// should be sent in case of stop to provoke acks with continue
		boolean	test	= con.isInited() && sent.size()> tup = tosend.remove(0);
//			System.out.println("send Stored: "+tup.getFirstEntity().getSequenceNumber());
			sendData(tup.getFirstEntity()).addResultListener(new DelegationResultListener(tup.getSecondEntity()));
		
			// Send only one test message if in stop mode.
			test	= false;
		}
	}
	
	/**
	 *  Called internally.
	 * 
	 *  Add data to a multi packet.
	 *  @parm data The data.
	 */
	protected IFuture addMultipacket(byte[] data)
	{
		IFuture ret = new Future();
		
		int start = 0;
		int len = Math.min(mpmaxsize-mpsize, data.length);
		
		Set> futs = new HashSet>();
		while(len>0)
		{
			byte[] part = new byte[len];
			System.arraycopy(data, start, part, 0, len);
			futs.add(addMultiPacketChunk(part));
			start += len;
			len = Math.min(mpmaxsize-mpsize, data.length-start);
		}
		
		if(futs.size()>0)
		{
			CounterResultListener lis = new CounterResultListener(futs.size(), 
				new DelegationResultListener((Future)ret));
			for(IFuture fut: futs)
			{
				fut.addResultListener(lis);
			}
		}
		else
		{
			ret = IFuture.DONE;
		}
		
		return ret;
	}
	
	/**
	 *  Called internally.
	 * 
	 *  Add data chunk.
	 *  @param data The data.
	 */
	protected IFuture addMultiPacketChunk(byte[] data)
	{
		if(mpfut==null)
			mpfut	= new Future();
		IFuture	ret	= mpfut;
		
		// Install send timer on first packet
		if(mpsize==0)
			createMultipacketSendTimer(getSequenceNumber());
		
		multipacket.add(data);
		mpsize += data.length;
		
		if(mpsize==mpmaxsize)
		{
			sendAcknowledgedMultiPacket().addResultListener(new DelegationResultListener(mpfut));
			mpfut	= null;
		}
		
		return ret;
	}
	
	/**
	 *  Called internally.
	 * 
	 *  Send a multi packet.
	 */
	protected IFuture sendAcknowledgedMultiPacket()
	{
		IFuture ret = IFuture.DONE;
		
		if(multipacket.size()>0)
		{
			byte[] target;
			if(multipacket.size()==1)
			{
				target	= multipacket.get(0);
			}
			else
			{
				target = new byte[mpsize];
				int start = 0;
				for(int i=0; i sendData(StreamSendTask task)
	{
		DataSendInfo tup = sent.get(task.getSequenceNumber());
		if(tup==null)
		{
			// First try.
			tup	= new DataSendInfo(task);
			
			// add task to unacknowledged sent list 
			sent.put(task.getSequenceNumber(), tup);
		}
		else
		{
			// Retry -> clone task for resend
			task	= tup.retry();
		}
		
//		System.out.println("send "+System.currentTimeMillis()+": "+task.getSequenceNumber());
		sendTask(task);
		
		queuecnt++;
//		System.out.println("queue: "+queuecnt);
//		final int	seqno	= task.getSequenceNumber();
		task.getFuture().addResultListener(new IResultListener()
		{
			public void resultAvailable(Void result)
			{
//				System.out.println("Sent "+System.currentTimeMillis()+": seq="+seqno);
				sendDone();
			}
			
			public void exceptionOccurred(Exception exception)
			{
//				System.out.println("Not sent "+System.currentTimeMillis()+": seq="+seqno+", "+exception);
//				exception.printStackTrace();
				sendDone();
			}
			
			protected void sendDone()
			{
				scheduleStep(new IComponentStep()
				{
					public IFuture execute(IInternalAccess ia)
					{
						queuecnt--;
						sendStored();
						checkWaitForReady();
						return IFuture.DONE;
					}
				});				
			}
		});
		
		return tup.getFuture();
	}
	
	/**
	 *  Triggers resends of packets if no ack has been received in acktimeout.
	 *  @param id The message id.
	 *  @return The timer.
	 */
	protected TimerTask	createBulkAckTimer(final Object id)
	{
		TimerTask	ret;
		if(acktimeout!=Timeout.NONE)
		{
			// Test if packets have been sent till last timer was inited
			ret	= ms.waitForRealDelay(acktimeout, new IComponentStep()
			{
				public IFuture execute(IInternalAccess ia)
				{
					DataSendInfo tup = sent.get(id);
					if(tup!=null)
					{
						tup.doResend();
					}
					return IFuture.DONE;
				}
			});
		}
		else
		{
			ret	= null;
		}
		
		return ret;
	}
	
	/**
	 * 
	 */
	protected boolean isSendAllowed()
	{
		return con.isInited() && sent.size()()
		{
			public IFuture execute(IInternalAccess ia)
			{
				// Send the packet if it is still the correct one
				if(seqno==getSequenceNumber())
				{
					assert mpfut!=null;
					sendAcknowledgedMultiPacket().addResultListener(new DelegationResultListener(mpfut));
					mpfut	= null;
				}
				return IFuture.DONE;
			}
		});
	}
	
	/**
	 * 
	 */
	protected void checkClose()
	{
//		System.out.println("checkclose0: "+isCloseRequested()+", "+isDataSendFinished()+", "+con.isInited()+", "+!con.isClosed()+", "+con.isClosing()+", "+isDataAckFinished()+", "+closesent);
		
		// Try to close if close is requested.
		if(isCloseRequested() && isDataSendFinished() && con.isInited() && !con.isClosed())
		{
			// If close() was already called on connection directly perform close
			if(con.isClosing())
			{
//				System.out.println("sending close output side");
				// Send close message and wait until it was acked
				sendAcknowledgedMessage(createTask(StreamSendTask.CLOSE, SUtil.intToBytes(seqnumber), null, nonfunc), StreamSendTask.CLOSE)
					.addResultListener(new IResultListener()
				{
					public void resultAvailable(Object result)
					{
//						System.out.println("ack from close output side");
						closesent = true;
						checkClose();
					}
					
					public void exceptionOccurred(Exception exception)
					{
						System.out.println("no ack from close output side: "+exception);
						// Set connection as closed.
						con.setClosed();
//						closesent = true;
//						checkClose();
					}
				});
			}
			else
			{
//				System.out.println("start closing output side");
				close();
			}
			closereqflag = false; // ensure that close is executed only once
		}
		
		// If all data sent and acked and not already closed and close message was acked
		if(isDataSendFinished() && isDataAckFinished() && !con.isClosed() && closesent)
		{
//			System.out.println("close end output side");
			// Set connection as closed.
			con.setClosed();
		}
	}
	
	/**
	 *  Check resending of unacknowledged messages.
	 */
	public void	checkResend()
	{
		// Iterate in insertion order -> oldest first
		for(DataSendInfo tup: sent.values().toArray(new DataSendInfo[0]))
		{
			if(tup.getSequenceNumber()	fut;
		
		/** The try count. */
		protected int	tries;
		
		/** The timer. */
		protected TimerTask	timer;
		
		/** The sequence number during the last sending. */
		protected long	lastsend;
		
		//-------- constructors --------
		
		/**
		 *  Create a send info.
		 */
		public DataSendInfo(StreamSendTask task)
		{
			this.task	= task;
			this.fut	= new Future();
			this.tries	= 1;
			timer	= createBulkAckTimer(task.getSequenceNumber());
			lastsend	= OutputConnectionHandler.this.getSequenceNumber();
		}
		
		//-------- methods --------
		
		/**
		 *  Get the sequence number.
		 */
		public int	getSequenceNumber()
		{
			return task.getSequenceNumber();
		}
		
		/**
		 *  Get the future.
		 */
		public Future	getFuture()
		{
			return fut;
		}
		
		/**
		 *  Retry sending the message.
		 *  @return task	The task for resend.
		 */
		public StreamSendTask	retry()
		{
			if(timer!=null)
				timer.cancel();
			timer	= createBulkAckTimer(task.getSequenceNumber());
			lastsend	= OutputConnectionHandler.this.getSequenceNumber();
			tries++;
			task	= new StreamSendTask(task);
//			System.out.println("Retry: #"+tries+", seq="+task.getSequenceNumber());
			return task;
		}
		
		/**
		 *  Called when the message should be resent.
		 */
		public void	doResend()
		{
			if(tries>=maxresends)
			{
//				System.out.println("Message could not be sent.");
				fut.setException(new RuntimeException("Message could not be sent."));
				sent.remove(task.getSequenceNumber());
				con.close();
			}
			else
			{
				sendData(task);
			}			
		}
		
		/**
		 *  Check, if the message should be resent.
		 */
		public void	checkResend()
		{
			// Resend earlier as time permits when many packets are sent
			if(lastsend=maxsend)
//						cnt[1]++;
//					if(queuecnt>=maxqueued)
//						cnt[2]++;
//					tfwaiting.setText(""+cnt[0]+" "+cnt[1]+" "+cnt[2]);
//				}
//			});
//			t.start();
//			
//			setLayout(new BorderLayout());
//			add(pp, BorderLayout.CENTER);
//		}
//	}
	
}