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

com.ociweb.pronghorn.network.ClientConnection Maven / Gradle / Ivy

The newest version!
package com.ociweb.pronghorn.network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.net.UnknownHostException;
import java.nio.channels.NoConnectionPendingException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ociweb.pronghorn.network.schema.NetPayloadSchema;
import com.ociweb.pronghorn.pipe.Pipe;
import com.ociweb.pronghorn.stage.scheduling.ElapsedTimeRecorder;
import com.ociweb.pronghorn.util.Appendables;


public class ClientConnection extends BaseConnection implements SelectionKeyHashMappable {

	//TODO: limitations with client side calls
	//      only supports 1 struct or 1 JSON parser or 1 set of headers per ClientSession
	//      this may force developers to use interleaving in some cases to solve this 
	//      paths will need to be pre-defined and given identifications.  (future feature)
	
	public static int resolveWithDNSTimeoutMS = 10_000; //  §6.1.3.3 of RFC 1123 suggests a value not less than 5 seconds.
	private static final long MAX_HIST_VALUE = 40_000_000_000L;
	private static final Logger logger = LoggerFactory.getLogger(ClientConnection.class);
	private static final byte[] EMPTY = new byte[0];

	public static boolean logDisconnects = false;
	public static boolean logLatencyData = false;
	
	private SelectionKey key; //only registered after handshake is complete.

	private final byte[] connectionGUID;
	private final int    connectionGUIDLength;
	
	private final int pipeIdx;
	
	private long requestsSent;
	private long responsesReceived;
	
	///////////////////////
	//TODO: Store the JSON Extractor here so we can apply it when the results come in??
	///////////////////////
	
	public final int sessionId;
	public final String host;
	public final int port;
	public final int hostId;
		  
	private long closeTimeLimit = Long.MAX_VALUE;
	private long TIME_TILL_CLOSE = 10_000;
	private ElapsedTimeRecorder histRoundTrip = new ElapsedTimeRecorder();

	private final static int maxInFlightBits  = 18;//256K  about 3MB per client connection
	public  final static int maxInFlight      = 1<1_000_000_000) {
					logger.info("warning slow DNS took {} sec to resolve {} to {} ",d/1_000_000_000,host,ipAddresses);
				}
			} catch (UnknownHostException unknwnHostEx) {
				failureDetected = true;
			}
		} while (null == ipAddresses && System.currentTimeMillis()>8);
    	target[pos++] = (byte)(port);
    	
    	target[pos++] = (byte)(userId);
    	target[pos++] = (byte)(userId>>8);
    	target[pos++] = (byte)(userId>>16);
    	target[pos++] = (byte)(userId>>24);
    	
    	return pos;
	}
	
	public byte[] GUID() {
		return connectionGUID;
	}
	public int GUIDLength() {
		return connectionGUIDLength;
	}
	
	/**
	 * After construction this must be called until it returns true before using this connection. 
	 */
	public boolean isFinishConnect() {
		if (isFinishedConnection) {
			return true;
		} else {
			try {
								
				boolean finishConnect = getSocketChannel().finishConnect();
				isFinishedConnection |= finishConnect;
			    				
			    if (!finishConnect ) {

			    	if (System.nanoTime() > creationTimeNS+(resolveWithDNSTimeoutMS*1000000L)) {
			    		logger.info("connection timeout {} {}ms",this,resolveWithDNSTimeoutMS);
			    		beginDisconnect();
			    	}
			    	
			    } else {
			    	clearWaitingForNetwork();
			    }
			    
				return finishConnect;
				
			} catch (IOException io) {
				//logger.trace("finish connection exception ",io);
				return false;
			} catch (NoConnectionPendingException ncpe) {
				close();
				//logger.trace("no pending connection ",ncpe);
				return false;
			}
		}
	}

	public boolean isRegistered() {
		return this.key!=null;
	}
		
	public void registerForUse(Selector selector, Pipe[] handshakeBegin, boolean isTLS) throws IOException {

		assert(getSocketChannel().finishConnect());
		
		//logger.trace("now finished connection to : {} ",getSocketChannel().getRemoteAddress().toString());
		
		if (isTLS) {

			getEngine().beginHandshake();
			
			HandshakeStatus handshake = getEngine().getHandshakeStatus();
			if (HandshakeStatus.NEED_TASK == handshake) { 				
	             Runnable task;
	             while ((task = getEngine().getDelegatedTask()) != null) {
	                	task.run(); 
	             }
			} else if (HandshakeStatus.NEED_WRAP == handshake) {
								
				
				int c= (int)getId()%handshakeBegin.length;				
				
				int j = handshakeBegin.length;
				while (--j>=0) {
						
					final Pipe pipe = handshakeBegin[c];
					assert(null!=pipe);
					
					if (Pipe.hasRoomForWrite(pipe)) {
					
						//Warning the follow on calls should be low level...
						//logger.warn("Low-Level ClientConnection request wrap for id {} to pipe {}",getId(), pipe, new Exception());
						
						////////////////////////////////////
						//NOTE: must repeat this until the handshake is finished.
						///////////////////////////////////
						final int size = Pipe.addMsgIdx(pipe, NetPayloadSchema.MSG_PLAIN_210);
						Pipe.addLongValue(getId(), pipe);
						Pipe.addLongValue(System.currentTimeMillis(), pipe);
						Pipe.addLongValue(SSLUtil.HANDSHAKE_POS, pipe);
						Pipe.addByteArray(EMPTY, 0, 0, pipe);
						
						Pipe.confirmLowLevelWrite(pipe, size);
						Pipe.publishWrites(pipe);
						
						break;
					} 
					
					if (--c<0) {
						c = handshakeBegin.length-1;
					}
					
				}				
				
				
				
				if (j<0) {
					throw new UnsupportedOperationException("unable to wrap handshake no pipes are avilable.");
				}				
				
			}			
						
		}
		isValid = true;
		//logger.info("is now valid connection {} ",this.id);
		this.key = getSocketChannel().register(selector, SelectionKey.OP_READ, this); 
		
	}

	public boolean isValid() {

		SocketChannel socketChannel = getSocketChannel();
		if (!socketChannel.isConnected()) {
			if (logDisconnects) {
				logger.info("{}:{} session {} is no longer connected. It was opened {} ago.",
						host,port,sessionId,
						Appendables.appendNearestTimeUnit(new StringBuilder(), System.nanoTime()-creationTimeNS).toString()
					);
			}
			return false;
		}
		return isValid;
	}


	public boolean isDisconnecting() {
		return isDisconnecting;
	}
	
	public void beginDisconnect() {

		if (logLatencyData) {
			logger.info("closing connection, latencies for {}:{}\n{}",this.host,this.port,histRoundTrip);
		}
		
		try {
			 isDisconnecting = true;
			 if (isTLS) {
				 SSLEngine eng = getEngine();
				 if (null!=eng) {
					 eng.closeOutbound();
				 }
			 }
		} catch (Throwable e) {
			logger.warn("Error closing connection ",e);
			close();
		}
	}


	
	public ElapsedTimeRecorder histogram() {
		return histRoundTrip;
	}

	public void recordSentTime(long time) {
		inFlightTimes[++inFlightTimeSentPos & maxInFlightMask] = time;		
	}

	//important method to determine if the network was dropped while call was outstanding
	public long outstandingCallTime(long time) {
		if (inFlightRoutesRespPos == inFlightTimeSentPos) {
			return -1;
		} else {			
			long sentTime = inFlightTimes[1+inFlightTimeRespPos & maxInFlightMask];
			if (sentTime>0) {		
				return time - sentTime;
			} else {
				return -1;//we read the value while it was being sent so discard
			}
		}
	}	
	
	//returns latency
	public long recordArrivalTime(long time) {
		
		assert(inFlightTimes[1+inFlightTimeRespPos & maxInFlightMask]>0);		
		
		long value = time - inFlightTimes[++inFlightTimeRespPos & maxInFlightMask];
			
		if (value>=0 && value




© 2015 - 2025 Weber Informatics LLC | Privacy Policy