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

com.cosylab.epics.caj.impl.CATransport Maven / Gradle / Ivy

/*
 * Copyright (c) 2004 by Cosylab
 *
 * The full license specifying the redistribution, modification, usage and other
 * rights and obligations is included with the distribution of this project in
 * the file "LICENSE-CAJ". If the license is not included visit Cosylab web site,
 * .
 *
 * THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
 * IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
 * _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
 * OR REDISTRIBUTION OF THIS SOFTWARE.
 */

package com.cosylab.epics.caj.impl;

import gov.aps.jca.CAStatus;
import gov.aps.jca.Context;
import gov.aps.jca.event.ContextExceptionListener;
import gov.aps.jca.event.ContextVirtualCircuitExceptionEvent;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.cosylab.epics.caj.CAJContext;
import com.cosylab.epics.caj.impl.reactor.ReactorHandler;
import com.cosylab.epics.caj.impl.reactor.lf.LeaderFollowersThreadPool;
import com.cosylab.epics.caj.impl.requests.EchoRequest;
import com.cosylab.epics.caj.impl.requests.EventsOffRequest;
import com.cosylab.epics.caj.impl.requests.EventsOnRequest;
import com.cosylab.epics.caj.impl.requests.UserNameRequest;
import com.cosylab.epics.caj.util.Timer;

import java.nio.BufferOverflowException;

/**
 * CA transport implementation.
 * @author Matej Sekoranja
 * @version $id$
 */
public class CATransport implements Transport, ReactorHandler, Timer.TimerRunnable {

	// Get Logger
	private static final Logger logger = Logger.getLogger(CATransport.class.getName());
	
	/**
	 * Connection status.
	 */
	private volatile boolean closed = false;

	/**
	 * Context instance.
	 */
	private CAJContext context;

	/**
	 * CA reponse handler.
	 */
	protected ResponseHandler responseHandler = null;

	/**
	 * Corresponding channel.
	 */
	private SocketChannel channel;

	/**
	 * Cached socket address.
	 */
	private InetSocketAddress socketAddress;

	/**
	 * Receive buffer.
	 */
	private ByteBuffer[] receiveBuffer;

	/**
	 * Flow control "buffer full" count limit.
	 */
	private final static int FLOW_CONTROL_BUFFER_FULL_COUNT_LIMIT = 4;

	/**
	 * Flow control status.
	 */
	private boolean flowControlActive = false;
	
	/**
	 * Local receive (socket) buffer.
	 */
	private ByteBuffer socketBuffer;
	
	/**
	 * Send queue.
	 */
	private LinkedList sendQueue;
	
	/**
	 * Remote side transport revision.
	 */
	private short remoteTransportRevision;

	/**
	 * Owners (users) of the transport.
	 */
	private Map owners;
	
	/**
	 * Send lock.
	 */
	private ReentrantLock sendLock = new ReentrantLock();

	/**
	 * Flu8sh pending status.
	 */
	private volatile boolean flushPending = false;
	
	/**
	 * Current active send buffer.
	 */
	private ByteBuffer sendBuffer;
	
	/**
	 * Byte buffer allocator.
	 */
	private CachedByteBufferAllocator bufferAllocator;

	/**
	 * Last active send buffer.
	 * This is used to possibly optimize high frequency flushes, it merges current buffer with the last 
	 * (if it there is enough of space and it is wainting in the sendQueue). 
	 */
	private ByteBuffer lastActiveSendBuffer = null;

	/**
	 * Process priority.
	 */
	protected short priority;
	
	/**
	 * Initial receive buffer size.
	 */
	private static final int INITIAL_RX_BUFFER_SIZE = 1024;

	/**
	 * @param context
	 * @param responseHandler
	 * @param client
	 * @param channel
	 * @param remoteTransportRevision
	 * @param priority
	 */
	public CATransport(CAJContext context, TransportClient client, ResponseHandler responseHandler,
					   SocketChannel channel, short remoteTransportRevision, short priority) {
		this.context = context;
		this.responseHandler = responseHandler;
		this.channel = channel;
		this.remoteTransportRevision = remoteTransportRevision;
		this.priority = priority;

		socketAddress = (InetSocketAddress)channel.socket().getRemoteSocketAddress();
		
		// initialize buffers
		receiveBuffer = new ByteBuffer[] {
							ByteBuffer.allocateDirect(CAConstants.CA_EXTENDED_MESSAGE_HEADER_SIZE),
							ByteBuffer.allocateDirect(INITIAL_RX_BUFFER_SIZE)
						};
		// first limit to a reading of an standard message header
		receiveBuffer[0].limit(CAConstants.CA_MESSAGE_HEADER_SIZE);

		socketBuffer = ByteBuffer.allocateDirect(CAConstants.MAX_TCP_RECV);
		
		// initialize owners list, send queue
		owners = new HashMap();
		acquire(client);

		sendQueue = new LinkedList();
		bufferAllocator = context.getCachedBufferAllocator();
		sendBuffer = bufferAllocator.get();
		
		// read beacon timeout and start timer (watchdog)
		connectionTimeout = (long)(context.getConnectionTimeout() * 1000);
		taskID = context.getTimer().executeAfterDelay(connectionTimeout, this);
		
		// add to registry
		context.getTransportRegistry().put(socketAddress, this);
	}
	
	/** 
	 * Close connection.
	 * @param forced	flag indicating if forced (e.g. forced disconnect) is required 
	 */
	// NOTE: do not call this methods with lock on transport/channels - high deadlock risk possibility!
	public void close(boolean forced) {

		if (closed)
			return;
		
		synchronized (this)
		{
			// already closed check
			if (closed)
				return;
			closed = true;
	
			Timer.cancel(taskID);
			
			// remove from registry
			context.getTransportRegistry().remove(socketAddress, priority);
		}

		// flush first
		if (!forced)
			flushInternal();
		
		freeSendBuffers();

		// NOTE: do not call next two methods with lock on transport/channels - high deadlock risk possibility!
		if (forced)
			closedNotifyContext();
		closedNotifyClients();
		
		context.getLogger().finer("Connection to " + socketAddress + " closed.");

		context.getReactor().unregisterAndClose(channel);
	}

	/**
	 * Free all send buffers (return them to the cached buffer allocator).
	 */
	private void freeSendBuffers() {
		synchronized (sendQueue) {
			sendBuffer = null;
			lastActiveSendBuffer = null;
			while (sendQueue.size() > 0) {
				ByteBuffer buf = (ByteBuffer)sendQueue.removeFirst();
				if(buf.capacity()==CachedByteBufferAllocator.bufferSize)
					bufferAllocator.put(buf);
				// else discard big buffer
			}
		}
	}
	
	/**
	 * Notifies context listeners about forceful disconnect.
	 */
	private void closedNotifyContext() {

		ContextVirtualCircuitExceptionEvent cvcee =
	    	new ContextVirtualCircuitExceptionEvent((Context)context, socketAddress.getAddress(), CAStatus.DISCONN);
	    ContextExceptionListener[] listeners = context.getContextExceptionListeners();
		for (int i = 0; i < listeners.length; i++)
		{
			try
			{
				listeners[i].contextVirtualCircuitException(cvcee);
			}
			catch (Throwable th)
			{
				// TODO remove
				logger.log(Level.SEVERE, "", th);
			}
		}
	}
	
	/**
	 * Notifies clients about disconnect.
	 */
	private void closedNotifyClients() {
		TransportClient[] clients;
		synchronized (owners)
		{
			// check if still acquired
			int refs = owners.size();
			if (refs == 0)
				return;
			
			context.getLogger().fine("Transport to " + socketAddress + " still has " + refs + " client(s) active and closing...");
			clients = new TransportClient[refs];
			owners.keySet().toArray(clients);
			owners.clear();
		}

		// NOTE: not perfect, but holding a lock on owners
		// and calling external method leads to deadlocks
		for (int i = 0; i < clients.length; i++)
		{
			try
			{
				clients[i].transportClosed();
			}
			catch (Throwable th)
			{
				// TODO remove
				logger.log(Level.SEVERE, "", th);
			}
		}
	}

	/** 
	 * Acquires transport.
	 * @param client client (channel) acquiring the transport
	 * @return true if transport was granted, false otherwise.
	 */
	public synchronized boolean acquire(TransportClient client) {

		if (closed)
			return false;
			
		context.getLogger().finer("Acquiring transport to " + socketAddress + ".");

		synchronized (owners)
		{
			if (closed)
				return false;
				
			owners.put(client, null);
		}
		
		return true;
	}

	/** 
	 * Releases transport.
	 * @param client client (channel) releasing the transport
	 */
	public synchronized void release(TransportClient client) {

		if (closed)
			return;
			
		context.getLogger().finer("Releasing transport to " + socketAddress + ".");

		synchronized (owners)
		{
			owners.remove(client);

			// not used anymore
			if (owners.size() == 0)
				close(false);
		}
	}

	/**
	 * @see com.cosylab.epics.caj.impl.Transport#getMinorRevision()
	 */
	public short getMinorRevision() {
		return remoteTransportRevision;
	}

	public void setMinorRevision(short rev) {
		remoteTransportRevision = rev;
	}
	
	/**
	 * Handle IO event.
	 * @see com.cosylab.epics.caj.impl.reactor.ReactorHandler#handleEvent(java.nio.channels.SelectionKey)
	 */
	public void handleEvent(SelectionKey key) {
		if (key.isValid() && key.isReadable())
			processRead();
			
		if (key.isValid() && key.isWritable())
			processWrite();
	}

	/**
	 * Process input (read) IO event.
	 * Alsp handles subscription flow control.
	 */
	protected void processRead() {
		try
		{
			int bufferFullCount = 0;
			
			while (!closed)
			{
				// clear buffer
				socketBuffer.clear();
				
				// read
				int bytesRead = channel.read(socketBuffer);
				if (bytesRead < 0)
				{
					// error (disconnect, end-of-stream) detected
					close (true);
					return; 
				}
				else if (bytesRead == 0)
				{
					// no more data, disable flow control
					bufferFullCount = 0;
					if (flowControlActive)
						disableFlowControl();
					break;
				}
				
				// flow control check
				if (socketBuffer.hasRemaining())
				{
					// buffer not full, disable flow control
					bufferFullCount = 0;
					if (flowControlActive)
						disableFlowControl();
				}
				else
				{
					// buffer full, too many times?
					if (bufferFullCount >= FLOW_CONTROL_BUFFER_FULL_COUNT_LIMIT)
					{
						// enable flow control
						if (!flowControlActive)
							enableFlowControl();
					}
					else
						bufferFullCount++;
				}
				
				// prepare for reading
				socketBuffer.flip();
				
				// read from buffer
				processRead(socketBuffer);
			}
			
		} catch (IOException ioex) {
			// close connection
			close(true);
		}
	}

	/**
	 * Process input.
	 */
	protected void processRead(ByteBuffer socketBuffer)
	{
		while (true)
		{
			final ByteBuffer headerBuffer = receiveBuffer[0];
			ByteBuffer payloadBuffer = receiveBuffer[1];
			
			// are we reading the header
			if (headerBuffer.hasRemaining())
			{
				// TODO can be optimized simply by wrapping...
				readFromByteBuffer(socketBuffer, headerBuffer);
				
				// not done reading the header...
				if (headerBuffer.hasRemaining())
					break;
				
				// peek for payload size (convert unsigned short to signed int) 
				int payloadSize = headerBuffer.getShort(2) & 0xFFFF;
				
				// extended message header
				if (payloadSize == 0xFFFF)
				{
					// already extended
					if (headerBuffer.limit() == CAConstants.CA_EXTENDED_MESSAGE_HEADER_SIZE)
					{
						// peek for extended payload
						payloadSize = headerBuffer.getInt(CAConstants.CA_MESSAGE_HEADER_SIZE);
						// if (payloadSize < 0) { /* this is way too much */ }
					}
					else
					{
						// extend to extended message header and re-read
						headerBuffer.limit(CAConstants.CA_EXTENDED_MESSAGE_HEADER_SIZE);
						continue;
					}
				}

				// check payload buffer capacity
				if (payloadSize > payloadBuffer.capacity()) {
					
					int maxPayloadSize = context.getMaxArrayBytes();
					if (payloadSize > maxPayloadSize)
					{
						// for now we drop connection
						// TODO implement skip message logic
						logger.log(Level.SEVERE,
								"Received payload size (" + payloadSize + 
								") is larger than configured maximum array size (" +
								context.getMaxArrayBytes() + "), disconnecting...");
						close(true);
						return;
						
					}
					
					final int PAGE_SIZE = 4096;
					int newSize = Math.min(maxPayloadSize, (payloadSize & ~(PAGE_SIZE-1)) + PAGE_SIZE);

					receiveBuffer[1] = ByteBuffer.allocateDirect(newSize);
					payloadBuffer = receiveBuffer[1];
				}

				// reset payload buffer
				payloadBuffer.clear();
				payloadBuffer.limit(payloadSize);
			}

			// are we reading the payload
			if (payloadBuffer.limit() == 0)
			{
				try
				{
					// prepare buffer for reading
					headerBuffer.flip();

					// handle response					
					responseHandler.handleResponse(socketAddress, this, receiveBuffer);
				}
				catch (Throwable th)
				{
					// catch all bad code responses...	
					logger.log(Level.SEVERE, "", th);
				}

				// reset header buffer
				headerBuffer.clear();
				headerBuffer.limit(CAConstants.CA_MESSAGE_HEADER_SIZE);
			}
			else if (payloadBuffer.hasRemaining())
			{
				// TODO can be optimized simply by wrapping...
				readFromByteBuffer(socketBuffer, payloadBuffer);
				
				// not done reading the payload...
				if (payloadBuffer.hasRemaining())
					break; 
					
				// prepare buffer for reading
				headerBuffer.flip();

				// prepare buffer for reading
				payloadBuffer.flip();
				
				try
				{
					// handle response
					responseHandler.handleResponse(socketAddress, this, receiveBuffer);
				}
				catch (Throwable th)
				{
					// catch all bad code responses...	
					logger.log(Level.SEVERE, "", th);
				}

				// reset header buffer
				headerBuffer.clear();
				headerBuffer.limit(CAConstants.CA_MESSAGE_HEADER_SIZE);
			}
		}
	}

	/**
	 * Read (copy) to buffer from buffer.
	 * @param socketBuffer
	 * @param payloadBuffer
	 */
	private static final void readFromByteBuffer(ByteBuffer srcBuffer, ByteBuffer destBuffer) {
		int srcBufferPosition = srcBuffer.position();
		int destPosition = destBuffer.position();
		int bytesToRead = Math.min(destBuffer.remaining(), srcBuffer.remaining());
                ByteBuffer toCopy = srcBuffer.slice();
                toCopy.limit(bytesToRead);
                destBuffer.put(toCopy);
		destBuffer.position(destPosition + bytesToRead);
		srcBuffer.position(srcBufferPosition + bytesToRead);
	}

	/**
	 * Process output (write) IO event.
	 */
	protected void processWrite() {
		flushInternal();                
	}

	/**
	 * Sends client username message to the server.
	 * User name is taken from System property "user.name".
	 */
	public void updateUserName()
	{
		try {
			new UserNameRequest(this).submit();
		} catch (IOException e) {
			// TODO remove
			logger.log(Level.SEVERE, "", e);
		}
	}

	/**
	 * Disable flow control (enables events).
	 */
	protected void disableFlowControl()
	{
		try {
			new EventsOnRequest(this).submit();
			flowControlActive = false;
			//System.out.println("disableFlowControl");
		} catch (IOException e) {
			// TODO remove
			logger.log(Level.SEVERE, "", e);
		}
	}
	
	/**
	 * Enable flow control (disables events).
	 */
	protected void enableFlowControl()
	{
		try {
			new EventsOffRequest(this).submit();
			flowControlActive = true;
			//System.out.println("enableFlowControl");
		} catch (IOException e) {
			// TODO remove
			logger.log(Level.SEVERE, "", e);
		}
	}
	
	/**
	 * Send a buffer through the transport.
	 * NOTE: TCP sent buffer/sending has to be synchronized. 
	 * @param buffer	buffer to be sent
	 * @throws IOException 
	 */
	public void send(ByteBuffer buffer, boolean asyncCloseOnError) throws IOException
	{
		sendLock.lock();
		try
		{
			noSyncSend(buffer, asyncCloseOnError);
		}
		finally 
		{
			sendLock.unlock();
		}
	}

	/**
	 * Send a buffer through the transport.
	 * NOTE: TCP sent buffer/sending has to be synchronized. 
	 * @param buffer	buffer to be sent
	 * @throws IOException 
	 */
	// TODO optimize !!!
	private void noSyncSend(ByteBuffer buffer, boolean asyncCloseOnError) throws IOException
	{
		try
		{
			// prepare buffer
			buffer.flip();

			final int SEND_BUFFER_LIMIT = 16000;
			int bufferLimit = buffer.limit();

			// TODO remove?!
			context.getLogger().finest("Sending " + bufferLimit + " bytes to " + socketAddress + ".");

			// limit sending large buffers, split the into parts
			int parts = (buffer.limit()-1) / SEND_BUFFER_LIMIT + 1;
			for (int part = 1; part <= parts; part++)
			{
				if (parts > 1)
				{
					buffer.limit(Math.min(part * SEND_BUFFER_LIMIT, bufferLimit));
					context.getLogger().finest("[Parted] Sending (part " + part + "/" + parts + ") " + (buffer.limit()-buffer.position()) + " bytes to " + socketAddress + ".");
				}
				
				final int TRIES = 10;
				for (int tries = 0; /* tries <= TRIES */ ; tries++)
				{
					
					// send
					int bytesSent = channel.write(buffer);
					if (bytesSent < 0)
						throw new IOException("bytesSent < 0");
					
					// bytesSend == buffer.position(), so there is no need for flip()
					if (buffer.position() != buffer.limit())
					{
						if (closed)
							throw new IOException("transport closed on the client side");
						
						if (tries >= TRIES)
						{
							context.getLogger().warning("Failed to send message to " + socketAddress + " - buffer full, will retry.");

							//if (tries >= 2*TRIES)
							//	throw new IOException("TCP send buffer persistently full, disconnecting!");
							
						}
						
						// flush & wait for a while...
						context.getLogger().finest("Send buffer full for " + socketAddress + ", waiting...");
						channel.socket().getOutputStream().flush();
						try {
							Thread.sleep(Math.min(15000,10+tries*100));
						} catch (InterruptedException e) {
							// noop
						}
						continue;
					}
					else
						break;
				}
			
			}
		}
		catch (IOException ioex) 
		{
			// close connection
			close(true);
			throw ioex;
		}
	}

	/**
	 * Flush task (to be executed by an thread pool).
	 */
	private Runnable flushTask =
		new Runnable() {
	        /**
	    	 * @see java.lang.Runnable#run()
	    	 */
	    	public void run() {
	    		flushInternal();
	    	}
		};

	/**
	 * Flush send buffer.
	 * ... by enabling SelectionKey.OP_WRITE and process in reactor. 
	 * @see com.cosylab.epics.caj.impl.Transport#flush()
	 */
	public synchronized boolean flush()
	{

	    // add to queue and flush
		synchronized(sendQueue)
		{
		    if (closed || sendBuffer == null)
		        return false;
		    
	        // noop check
	        if (sendBuffer.position() == 0)
	            return true;
	        else
	        {
	            // reuse old buffer
	            if (lastActiveSendBuffer != null &&
	                lastActiveSendBuffer.position()+sendBuffer.position() <= lastActiveSendBuffer.capacity())
	            {
	                sendBuffer.flip();
	                lastActiveSendBuffer.put(sendBuffer);
	                sendBuffer.clear();
	                return true;
	            }
	            
	            sendQueue.add(sendBuffer);

	            // acquire new buffer
	            lastActiveSendBuffer = sendBuffer;
	    		sendBuffer = bufferAllocator.get();
	        }
	        
		    if (flushPending)
		        return true;
		    // NOTE: must be sure that code below will not fail
		    flushPending = true;
		}
	    
	    return spawnFlushing();
	}
	
	/**
     * @return success flag.
     */
    private boolean spawnFlushing()
    {
        LeaderFollowersThreadPool lftp = context.getLeaderFollowersThreadPool();
	    if (lftp != null)
	    {
		    // reuse LF threadpool to do async flush
	        lftp.execute(flushTask);
	        return true;
	    }
	    else
	    {
		    // enable SelectionKey.OP_WRITE via reactor (this will also enable OP_READ, but its OK)
		    context.getReactor().setInterestOps(channel, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
		    return true;
	    }
    }

    /**
	 * Flush send buffer (blocks until flushed).
	 * @return success flag.
	 * @see com.cosylab.epics.caj.impl.Transport#flush()
	 */
	public boolean flushInternal()
	{
	    // tricky closed check
	    if (sendBuffer == null)
	        return false;
	    
		try
		{
			while (sendQueue.size() > 0)
			{
				ByteBuffer buf;
				// dont want to block while sending...
				synchronized (sendQueue)
				{
					if (sendQueue.size() == 0)
						return true;
						
					buf = (ByteBuffer)sendQueue.removeFirst();
					// 'deactivate' lastActiveSendBuffer
					if (buf == lastActiveSendBuffer)
					    lastActiveSendBuffer = null;
				}
				
				try {
				    send(buf, false);
				}
				finally {
				    // return back to the cache
					if(buf.capacity()==CachedByteBufferAllocator.bufferSize)
						bufferAllocator.put(buf);
					// else drop big buf
				}
			}
			
			return true;
		}
		catch (IOException ioex)
		{
			// close connection
			close(true);
			return false;
		}
		finally
		{
			synchronized (sendQueue)
			{
			    // ack
			    flushPending = false;
			    
			    // possible race condition check
				if (!closed && sendQueue.size() > 0)
				    spawnFlushing();
			}
		}
	}

	/**
	 * @see com.cosylab.epics.caj.impl.Transport#submit(com.cosylab.epics.caj.impl.Request)
	 */
	public void submit(Request requestMessage) throws IOException {
		ByteBuffer message = requestMessage.getRequestMessage();
			
		// empty message
		if (message.capacity() == 0)
			return;
				
		// send or enqueue
		if (requestMessage.getPriority() == Request.SEND_IMMEDIATELY_PRIORITY)
		{
			try
			{
				if (sendLock.tryLock(100, TimeUnit.MILLISECONDS))
				{
					try
					{
						noSyncSend(message, true);
						return;
					}
					finally
					{
						sendLock.unlock();
					}
				}
			}
			catch (InterruptedException ie) {
				// noop
			}
		}
		
		
		
		{
			message.flip();

			synchronized (sendQueue) {

			    if (sendBuffer == null)
				    throw new IllegalStateException("transport closed");
			    
			    // forced flush check
				if (message.limit()+sendBuffer.position() >= sendBuffer.capacity())
				    flush();

				try {
					sendBuffer.put(message);
				} catch(BufferOverflowException ex) {
					try {
						logger.fine("Expending sendBuffer for "+Integer.toString(message.limit()));
						sendBuffer.flip();
						ByteBuffer bigbuf = ByteBuffer.allocateDirect(sendBuffer.remaining()+message.limit());
						bigbuf.put(sendBuffer);
						bigbuf.put(message);
						if(sendBuffer.capacity()==CachedByteBufferAllocator.bufferSize)
							bufferAllocator.put(sendBuffer);
						sendBuffer = bigbuf;
					} catch(Exception ex2) {
						System.out.println("XX "+Integer.toString(sendBuffer.remaining())+" "+Integer.toString(message.limit()));
						throw new RuntimeException("Message exceeds write buffer size (com.cosylab.epics.caj.impl.CachedByteBufferAllocator.buffer_size)", ex2);
					}
				}
			}
		}
	}

	/**
	 * @see com.cosylab.epics.caj.impl.Transport#getContext()
	 */
	public CAContext getContext() {
		return context;
	}

	/**
	 * @see com.cosylab.epics.caj.impl.Transport#getRemoteAddress()
	 */
	public InetSocketAddress getRemoteAddress() {
		return socketAddress;
	}

	/**
	 * @see com.cosylab.epics.caj.impl.Transport#getPriority()
	 */
	public short getPriority() {
		return priority;
	}

	/* ********************* [ Beacons ] ************************ */
	
	/**
	 * Probe response (state-of-health message) sent and waiting for response.
	 */
	private boolean probeResponsePending = false;
	
	/**
	 * Probe response (state-of-health message) did not respond - timeout.
	 */
	private boolean probeTimeoutDetected = false;
			
	/**
	 * Probe lock.
	 */
	private Object probeLock = new Object();

	/**
	 * Beacon anomaly flag.
	 */
	private long connectionTimeout;

	/**
	 * Unresponsive transport flag.
	 */
	private boolean unresponsiveTransport = false;

	/**
	 * Timer task node.
	 */
	private Object taskID;

	/**
	 * Beacon arrival.
	 */
	public void beaconArrivalNotify()
	{
		if (!probeResponsePending)
			rescheduleTimer(connectionTimeout);
	}

	/*
	 * Message arrival (every message send to transport). 
	 *//* this is called to oftnen (e.g. first this and then immediately beaconArrivalNotify)
	private void messageArrivalNotify()
	{
		synchronized(probeLock)
		{
			if (!probeResponsePending) 
				rescheduleTimer(connectionTimeout);
		}
	}*/

	/**
	 * Rechedule timer for timeout ms.
	 * @param timeout	timeout in ms.
	 */
	private void rescheduleTimer(long timeout)
	{
		Timer.cancel(taskID);
		if (!closed)
			taskID = context.getTimer().executeAfterDelay(timeout, this);
	}
	
	/**
	 * Beacon timer.
	 * @see com.cosylab.epics.caj.util.Timer.TimerRunnable#timeout(long)
	 */
	public void timeout(long timeToRun)
	{
		synchronized(probeLock)
		{
			if (probeResponsePending)
			{
				probeTimeoutDetected = true;
				unresponsiveTransport();
			}
			else
			{
				sendEcho();
			}
		}
	}

	/**
	 * Called when echo request (state-of-health message) was responed.
	 */
	public void echoNotify()
	{
		synchronized(probeLock)
		{
			if (probeResponsePending)
			{
				if (probeTimeoutDetected)
				{
					// try again
					sendEcho();
				}
				else
				{
					// transport is responsive
					probeTimeoutDetected = false;
					probeResponsePending = false;
					responsiveTransport();
					rescheduleTimer(connectionTimeout);
				}
			}
		}
	}
	
	/**
	 * Sends echo (state-of-health message)
	 */
	private void sendEcho() {
		synchronized(probeLock)
		{
			probeTimeoutDetected = false;
			probeResponsePending = remoteTransportRevision >= 3;
			try
			{
				new EchoRequest(this).submit();
			}
			catch (IOException ex)
			{
				probeResponsePending = false;
			}
			rescheduleTimer(CAConstants.CA_ECHO_TIMEOUT);
		}
	}

	/**
	 * Responsive transport notify. 
	 */
	private void responsiveTransport()
	{
		if (unresponsiveTransport)
		{
		    unresponsiveTransport = false;
		    
		    TransportClient[] clients;
			synchronized (owners)
			{
				clients = new TransportClient[owners.size()];
				owners.keySet().toArray(clients);
			}

			// NOTE: not perfect, but holding a lock on owners
			// and calling external method leads to deadlocks
			for (int i = 0; i < clients.length; i++)
			{
				try
				{
					clients[i].transportResponsive(this);
				}
				catch (Throwable th)
				{
					// TODO remove
					logger.log(Level.SEVERE, "", th);
				}
			}
			
		}
	}

	/**
	 * Unresponsive transport notify. 
	 */
	private void unresponsiveTransport()
	{
		if (!unresponsiveTransport)
		{
		    unresponsiveTransport = true;

		    ContextVirtualCircuitExceptionEvent cvcee =
		    	new ContextVirtualCircuitExceptionEvent((Context)context, socketAddress.getAddress(), CAStatus.UNRESPTMO);
		    ContextExceptionListener[] listeners = context.getContextExceptionListeners();
			for (int i = 0; i < listeners.length; i++)
			{
				try
				{
					listeners[i].contextVirtualCircuitException(cvcee);
				}
				catch (Throwable th)
				{
					// TODO remove
					logger.log(Level.SEVERE, "", th);
				}
			}
		    
			TransportClient[] clients;
			synchronized (owners)
			{
				clients = new TransportClient[owners.size()];
				owners.keySet().toArray(clients);
			}
			
			// NOTE: not perfect, but holding a lock on owners
			// and calling external method leads to deadlocks
			for (int i = 0; i < clients.length; i++)
			{
				try
				{
					clients[i].transportUnresponsive();
				}
				catch (Throwable th)
				{
					// TODO remove
					logger.log(Level.SEVERE, "", th);
				}
			}
		}
	}


	/**
	 * Changed transport (server restarted) notify. 
	 */
	public void changedTransport()
	{
		TransportClient[] clients;
		synchronized (owners)
		{
			clients = new TransportClient[owners.size()];
			owners.keySet().toArray(clients);
		}
		
		// NOTE: not perfect, but holding a lock on owners
		// and calling external method leads to deadlocks
		for (int i = 0; i < clients.length; i++)
		{
			try
			{
				clients[i].transportChanged();
			}
			catch (Throwable th)
			{
				// TODO remove
				logger.log(Level.SEVERE, "", th);
			}
		}
		
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy