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

prerna.tcp.client.NativePySocketClient Maven / Gradle / Ivy

The newest version!
package prerna.tcp.client;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

import javax.ws.rs.core.StreamingOutput;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.ToNumberPolicy;

import prerna.auth.User;
import prerna.om.Insight;
import prerna.om.InsightStore;
import prerna.reactor.job.JobReactor;
import prerna.sablecc2.PixelRunner;
import prerna.sablecc2.PixelStreamUtility;
import prerna.sablecc2.comm.PixelJobManager;
import prerna.sablecc2.om.execptions.SemossPixelException;
import prerna.tcp.PayloadStruct;
import prerna.tcp.TCPLogMessage;
import prerna.tcp.client.workers.NativePyEngineWorker;
import prerna.util.Constants;
import prerna.util.Utility;

public class NativePySocketClient extends SocketClient implements Runnable, Closeable  {

	private static final Logger classLogger = LogManager.getLogger(NativePySocketClient.class);

	private String HOST = null;
	private int PORT = -1;
	private boolean SSL = false;

	//Map requestMap = new HashMap();
	//Map responseMap = new HashMap();

	private boolean ready = false;
	private boolean connected = false;
	private AtomicInteger count = new AtomicInteger(0);
	private long averageMillis = 200;
	private boolean killall = false; // use this if the server is dead or it has crashed
	private User user;

	private Socket clientSocket = null;
	//InputStream is = null;
	//OutputStream os = null;
	//SocketClientHandler sch = new SocketClientHandler();
	private Gson gson = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();

	/**
	 * 
	 */
	public void connect(final String HOST, final int PORT, final boolean SSL) {
		this.HOST = HOST;
		this.PORT = PORT;
		this.SSL = SSL;
	}

	@Override
	public void close() {
		if(this.requestMap != null) {
			this.requestMap.clear();
		}
		closeStream(this.os);
		closeStream(this.is);
		closeStream(this.clientSocket);
		this.connected = false;
		this.killall = true;
	}

	/**
	 * 
	 */
	public void run()	
	{
		// there is 2 portions to the run
		// one is before connect
		// one is after. The reason this is done is to avoid an extra handler for information

		// Configure SSL.git
		if(!connected && !killall)
		{
			int attempt = 1;
			int SLEEP_TIME = 800;
			if(Utility.getDIHelperProperty("SLEEP_TIME") != null) {
				try {
					SLEEP_TIME = Integer.parseInt(Utility.getDIHelperProperty("SLEEP_TIME"));
				} catch(NumberFormatException e) {
					classLogger.warn("Invalid property value for SLEEP_TIME");
					classLogger.error(Constants.STACKTRACE, e);
				}
			}

			classLogger.info("Trying with the sleep time of " + SLEEP_TIME);
			while(!connected && attempt < 6) // I do an attempt here too hmm.. 
			{
				try {
					clientSocket =  new Socket(this.HOST, this.PORT);
					// pick input and output stream and start the threads
					this.is = clientSocket.getInputStream();
					this.os = clientSocket.getOutputStream();
					classLogger.info("CLIENT Connection complete !!!!!!!");

					Thread.sleep(100); // sleep some before executing command
					// prime it 
					//classLogger.info("First command.. Prime" + executeCommand("2+2"));
					connected = true;
					ready = true;
					killall = false;
					synchronized(this) {
						this.notifyAll();
					}
				} catch(Exception ex) {
					attempt++;
					classLogger.info("Attempting Number " + attempt);
					// see if sleeping helps ?
					try {
						// sleeping only for 1 second here
						// but the py executor sleeps in 2 second increments
						Thread.sleep(attempt*SLEEP_TIME);
					} catch(Exception ex2) {
						// ignored
					}
				}
			}

			if(attempt >= 6) {
				classLogger.error("CLIENT Connection Failed !!!!!!!");
				killall = true;
				connected = false;
				ready = false;
				synchronized(this) {
					this.notifyAll();
				}
				throw new IllegalArgumentException("Failed to connect to your isolated analytics engine");
			}
		}

		// this is the read portion
		if(connected)
		{
			StringBuilder partialAssimilator = new StringBuilder("");
			StringBuilder outputAssimilator = new StringBuilder("");
			while (!killall) 
			{
				classLogger.debug("Starting new read iteration in run() loop");
				try {
				    String threadName = Thread.currentThread().getName();
				    long threadId = Thread.currentThread().getId();
				    classLogger.debug("Socket read thread [{}:{}] attempting to read next message", threadName, threadId);
				    
				    byte[] length = new byte[4];
				    classLogger.debug("Socket read thread [{}:{}] blocking on read()", threadName, threadId);
				    
				    // required read to populate the length buffer before wrapping
				    @SuppressWarnings("unused")  // bytesRead is necessary for proper socket reading
				    int bytesRead = is.read(length);
				    
				    int size = ByteBuffer.wrap(length).getInt();
				    classLogger.debug("Socket read thread [{}:{}] completed read of size header: {} bytes", threadName, threadId, size);
					//System.err.println("Incoming data is of size " + size);

					if(size > 0)
					{
						byte[] msg = new byte[size];
						int size_read = 0;
						classLogger.debug("Starting to read message of size {}", size);
						while(size_read < size)
						{
							int to_read = size - size_read;
							byte [] newMsg = new byte[to_read];
							int cur_size = is.read(newMsg);
			                classLogger.debug("Read chunk of {} bytes, total so far: {}/{}", cur_size, size_read + cur_size, size);
							System.arraycopy(newMsg, 0, msg, size_read, cur_size);
							size_read = size_read + cur_size;
							//System.out.println("incoming size " + size + "  read size.. " + size_read);
						}

						String message = new String(msg);
						classLogger.debug("Raw message from Python: {}", message);
						//System.err.print(message);
						PayloadStruct ps = gson.fromJson(message, PayloadStruct.class);
			            classLogger.debug("Parsed message - epoc: {}, operation: {}, response: {}, interim: {}", 
			                    ps.epoc, ps.operation, ps.response, ps.interim);

						PayloadStruct lock = (PayloadStruct) requestMap.get(ps.epoc);
						classLogger.debug("incoming payload " + ps);
			            classLogger.debug("Found lock for epoc {}: {}", ps.epoc, lock != null);

						// std out no questions
						if(ps.operation == PayloadStruct.OPERATION.STDOUT && ps.payload != null && !ps.response)
						{
							//classLogger.info(ps.payload[0]);
							//classLogger.info("Standard output");
							String logMessage = (String) ps.payload[0];
							outputAssimilator.append(ps.payload[0]);

							if (logMessage.startsWith("SMSS_PYTHON_LOGGER<==<>==>")) {

								String logMessagePayload = logMessage.split("<==<>==>")[1];
								TCPLogMessage tcpLogMessage = gson.fromJson(logMessagePayload, TCPLogMessage.class);

								String py_log = tcpLogMessage.name + ":" + tcpLogMessage.lineNumber + " " + tcpLogMessage.message;

								switch(tcpLogMessage.stack) {
								case "FRONTEND":
									exposeLog(logMessage, lock.insightId);
									break;
								case "BACKEND":
									switch(tcpLogMessage.levelName) {
									case "DEBUG":
										// if python enabled debug level, then just add the debug flag to info
										classLogger.info("DEBUG " + py_log);
										break;
									case "INFO":
										classLogger.info(py_log);
										break;
									case "WARNING":
										classLogger.warn(py_log);
										break;
									case "ERROR":
									case "CRITICAL":
										classLogger.error(py_log);
										break;
									}
									break;
								}	    						
							} else if (lock != null) {
								exposeLog(logMessage, lock.insightId);
							} 
						}

						// need some way to say this is the output from the actual python vs. something that is a classLogger
						// this is done through interim and operations
						// partial stdout
						// i.e. response is true and it is being sent as a stdout
						else if(ps.response && ps.operation == PayloadStruct.OPERATION.STDOUT)
						{
							//classLogger.info("Partial Response from the py");
							// need to return output here
							if(ps.payload != null && !((String)ps.payload[0]).equalsIgnoreCase("NONE"))
							{
								partialAssimilator.append(ps.payload[0] + "");
								if(lock != null && lock.insightId != null) {
									PixelJobManager.getManager().addPartialOut(lock.insightId, ps.payload[0]+"");
								}
							}
							if(!ps.interim)
							{
								//System.err.println("Final message.. ");
								classLogger.info("FINAL PARTIALs OUTPUT <<<<<<<" + partialAssimilator + ">>>>>>>>>>>>");
								// re-initialize it

							}
						}
						// this is the response.. i.e. the full response
						else if(ps.response)
						{
							//classLogger.info("Response from the py");
							//System.err.println("This is working as designed");
							if(ps != null 
									&& ps.payload !=null 
									&& ps.payload.length > 0 
									&& ps.payload[0] != null 
									&& ps.payload[0].toString().equalsIgnoreCase("None")
									) {
								if(partialAssimilator.length() > 0)
									ps.payload = new String[] {partialAssimilator.toString()};
								else 
									ps.payload = new String[] {outputAssimilator.toString()};
							}
							ps.epoc = ps.epoc.trim();
			                classLogger.debug("Processing response for epoc: {}", ps.epoc);
							lock = (PayloadStruct)requestMap.remove(ps.epoc);
			                classLogger.debug("Found and removed request lock for epoc {}: {}", ps.epoc, lock != null);


							// try to convert it into a full object
							// need to check if it is primitive before converting
							// try to convert it into a full object
							try {
								if(ps.payload[0] != null) { 
									if(ps.payload[0] instanceof String) {
										Object obj = gson.fromJson((String)ps.payload[0], Object.class);
										ps.payload[0] = obj;
									}
								}
							} catch(Exception ignored) {
								classLogger.warn("Ignoring unable to gson.fromJson() of " + ps.payload[0]);
							}
							// put it in response
							responseMap.put(ps.epoc, ps);
			                classLogger.debug("Added response to responseMap for epoc: {}", ps.epoc);

							if(lock != null)
							{
								synchronized(lock)
								{
			                        classLogger.debug("About to notify waiters for epoc: {}", ps.epoc);
									lock.notifyAll();
			                        classLogger.debug("Notified waiters for epoc: {}", ps.epoc);

								}
							}
							outputAssimilator = new StringBuilder("");
							partialAssimilator = new StringBuilder("");
						}
						// this is a request for a reactor
						else if(ps.operation == PayloadStruct.OPERATION.REACTOR) {
							final PayloadStruct finalPs = ps;
							// I'm creating a new thread to run the pixel
						    new Thread(() -> {
						        classLogger.debug("Starting reactor operation for epoc: {}", finalPs.epoc);
						        ByteArrayOutputStream output = new ByteArrayOutputStream();
						        try {
						            String insightId = finalPs.insightId;
						            Insight insight = InsightStore.getInstance().get(insightId);
						            if(insight == null) {
						            	throw new IllegalArgumentException("Could not find the insight id");
						            }
						            String pixelOp = (String) finalPs.payload[0];
						            if(!(pixelOp=pixelOp.trim()).endsWith(";")) {
						                pixelOp+=";";
						            }
						            PixelRunner pixelRunner = insight.runPixel(pixelOp);
						            StreamingOutput streamedOutput = PixelStreamUtility.collectPixelData(pixelRunner, null);
						            streamedOutput.write(output);
						            JsonElement json = JsonParser.parseString(new String(output.toByteArray(),"UTF-8"));
						            finalPs.payload = new Object[] {json};
						            finalPs.response = true;
						            executeCommand(finalPs);
						        } catch(Exception e) {
					                classLogger.error(Constants.STACKTRACE, e);
						        	finalPs.response = true;
						        	finalPs.ex = "An error occurred running the pixel";
						            executeCommand(finalPs);
						        } finally {
						            try {
						                output.close();
						            } catch(IOException e) {
						                classLogger.error(Constants.STACKTRACE, e);
						            }
						        }
						    }).start();
						}
						// this is a request
						else if(ps.operation == PayloadStruct.OPERATION.ENGINE)
						{
							//classLogger.info("reverse request for data");
							// this is a request we need to process
							// need a way here to also push the payload classes
							// will come to it in a bit
							// clean up the payload struct a little
							ps = convertPayloadClasses(ps);
							processEngineRequest(ps);
						}
						// unhandled pieces.. nothing we can do here.. just give the response back
						// so we dont choke the thread
						else
						{
							classLogger.info("message not handled by py server");
							lock = (PayloadStruct)requestMap.remove(ps.epoc);
							responseMap.put(ps.epoc, ps);
							if(lock != null)
							{
								synchronized(lock)
								{
									lock.notifyAll();
								}
							}
						}
					}
					else
					{
						killall = true;
						break;
					}
				} catch (SocketException ex1) {
					classLogger.error(Constants.STACKTRACE, ex1);
					crash();
					break;
				} catch (Exception ex) {
					classLogger.error(Constants.STACKTRACE, ex);
				}
			}
			connected = false;
			System.err.println("NativePySocketClient is disconnected");
			classLogger.warn("NativePySocketClient is disconnected");
		}
	}

	private void processEngineRequest(PayloadStruct ps)
	{
		String insightId = ps.insightId;
		if(insightId == null || (insightId=insightId.trim()).isEmpty() || insightId.equals("${i}")) {
			ps.response = true;
			ps.ex = "Insight Id is undefined or null";
			// return the error
			executeCommand(ps);
			return;
		}
		Insight insight = InsightStore.getInstance().get(insightId);
		if(insight == null) {
			ps.response = true;
			ps.ex = "Could not find the insight id";
			// return the error
			executeCommand(ps);
			return;
		}
		user = insight.getUser();
		if(user == null) {
			ps.response = true;
			ps.ex = "There is no user associated with this insight id";
			// return the error
			executeCommand(ps);
			return;
		} else {
			NativePyEngineWorker worker = new NativePyEngineWorker(this.getUser(), ps, insight);
			worker.run();
			executeCommand(worker.getOutput());
		}
	}

	private PayloadStruct convertPayloadClasses(PayloadStruct input)
	{
		if(input.payloadClassNames != null)
		{
			input.payloadClasses = new Class[input.payloadClassNames.length];
			for(int classIndex = 0;classIndex < input.payloadClassNames.length;classIndex++)
			{
				try {
					String className = input.payloadClassNames[classIndex];
					input.payloadClasses[classIndex] = Class.forName(className);
					if(input.payloadClasses[classIndex] == Insight.class)
					{
						String insightId = input.insightId;
						Insight insight = InsightStore.getInstance().get(insightId);
						input.payload[classIndex] = insight;
					}
				} catch (ClassNotFoundException e) {
					classLogger.error(Constants.STACKTRACE, e);
				}
			}
		}
		return input;
	}

	// this is the method that pushes to the front end
	// when output happens
	private void exposeLog(String data, String insightId) {
		if(insightId != null && data != null) {
			Insight insight = InsightStore.getInstance().get(insightId);
			String jobId =  insight.getVarStore().get(JobReactor.JOB_KEY).getValue().toString();
			PixelJobManager.getManager().addStdOut(jobId, data);
		}
	}

	public Object executeCommand(PayloadStruct ps)
	{
	    String threadName = Thread.currentThread().getName();
	    long threadId = Thread.currentThread().getId();
	    classLogger.debug("Entering executeCommand for epoc: {} on thread: {} (ID: {})", ps.epoc, threadName, threadId);
		if(killall) {
			throw new SemossPixelException("Analytic engine is no longer available. This happened because you exceeded the memory limits provided or performed an illegal operation. Please relook at your recipe");
		}

		if(!connected) {
			throw new SemossPixelException("Your micro-process is not available. Please logout and try again. !");
		}

		String id = ps.epoc;
		if(!ps.response || id == null)
		{
			id = "ps"+ count.getAndIncrement();
			ps.epoc = id;
		}
		ps.longRunning = true;

		synchronized(ps) // going back to single threaded .. earlier it was ps
		{	
	        classLogger.debug("Inside synchronized block for epoc: {} on thread: {} (ID: {})", ps.epoc, threadName, threadId);

			//if(ps.hasReturn)
			// put it into request map
			if(!ps.response) {
				requestMap.put(id, ps);
			}
			writePayload(ps);
			classLogger.debug("outgoing payload " + ps.epoc);

			// send the message
			// time to wait = average time * 10
			// if this is a request wait for it
		
			if(!ps.response) // this is a response to something the socket has asked
			{
				int pollNum = 1; // 1 second
				while(!responseMap.containsKey(ps.epoc) && (pollNum <  10 || ps.longRunning) && !killall)
				{
			        classLogger.debug("Thread {} waiting for response to epoc: {} (poll #{})", 
			                Thread.currentThread().getId(), ps.epoc, pollNum);
					//classLogger.info("Checking to see if there was a response");
					try {
						classLogger.debug("I'm looking for epoc{}", ps.epoc);
						if(pollNum < 10) {
							ps.wait(averageMillis);
						} else { //if(ps.longRunning) // this is to make sure the kill all is being checked
							classLogger.debug("Im about to wait eternally for epoc{}", ps.epoc);
							ps.wait(); // wait eternally - we dont know how long some of the load operations would take besides, I am not sure if the null gets us anything
						}
						pollNum++;
					} catch (InterruptedException e) {
						classLogger.error(Constants.STACKTRACE, e);
					}
				}
				if(!responseMap.containsKey(ps.epoc) && ps.hasReturn)
				{
					classLogger.info("Timed out for epoc " + ps.epoc + " " + ps.methodName);
				}
			}

			// after 10 seconds give up
			//printUnprocessed();
			return responseMap.remove(ps.epoc);
		}
	}

	private void writePayload(PayloadStruct ps)
	{
		classLogger.debug("Starting writePayload for epoc: " + ps.epoc);
		// nulling the classes so they dont screw up json
		ps.payloadClasses = null;
		try
		{
			String jsonPS = gson.toJson(ps);
			byte [] psBytes = pack(jsonPS, ps.epoc);
			try {
				classLogger.debug("About to write to output stream for epoc: " + ps.epoc);
				os.write(psBytes);
				classLogger.debug("Successfully wrote to output stream for epoc: " + ps.epoc);
			} catch(IOException ex) {
				classLogger.info("Failed writing to output stream for epoc: " + ps.epoc, ex);
				classLogger.error(Constants.STACKTRACE, ex);
				//crash();
			}
		}catch(Exception ex)
		{
			classLogger.error(Constants.STACKTRACE, ex);
		}
	}

	public byte[] pack(String message, String epoc) {
		byte[] psBytes = message.getBytes(StandardCharsets.UTF_8);

		// get the length
		int length = psBytes.length;

		//System.err.println("Packing with length " + length);

		// make this into array
		byte[] lenBytes = ByteBuffer.allocate(4).putInt(length).array();

		byte[] epocBytes = ByteBuffer.allocate(20).put(epoc.getBytes(StandardCharsets.UTF_8)).array();


		// pack both of these
		byte[] finalByte = new byte[psBytes.length + lenBytes.length + epocBytes.length];

		for (int lenIndex = 0; lenIndex < lenBytes.length; lenIndex++)
			finalByte[lenIndex] = lenBytes[lenIndex];

		// write the epoc bytes
		for (int lenIndex = 0; lenIndex < epocBytes.length; lenIndex++)
			finalByte[lenIndex + lenBytes.length] = epocBytes[lenIndex];

		for (int lenIndex = 0; lenIndex < psBytes.length; lenIndex++)
			finalByte[lenIndex + lenBytes.length + epocBytes.length] = psBytes[lenIndex];

		return finalByte;

	}

	public void writeReleaseAllPayload() {
		PayloadStruct ps = new PayloadStruct();
		ps.epoc=Utility.getRandomString(8);
		ps.methodName = "RELEASE_ALL";
		writePayload(ps);
	}


    /**
     * 
     * @return
     */
    public boolean stopServer() {
		try {
			if(isConnected()) {
				ExecutorService executor = Executors.newSingleThreadExecutor();

				Callable callableTask = () -> {
					PayloadStruct ps = new PayloadStruct();
					ps.epoc = "stop_all";
					ps.hasReturn = false;
					ps.methodName = "CLOSE_ALL_LOGOUT";
					ps.payload = new String[] { "CLOSE_ALL_LOGOUT"};
					ps.operation = PayloadStruct.OPERATION.CMD;
					writePayload(ps);
					return true;
				};

				Future future = executor.submit(callableTask);
				try {
					// wait 1 minute at most
					boolean result = future.get(60, TimeUnit.SECONDS);
					classLogger.info("Stop PyServe result = " + result);
					return result;
				} catch (TimeoutException e) {
					classLogger.warn("Not able to release the payload structs within a timely fashion");
					future.cancel(true);
					return false;
				} catch (InterruptedException | ExecutionException e) {
					classLogger.error(Constants.STACKTRACE, e);
					return false;
				} finally {
					executor.shutdown();
				}
			} else {
				return true;
			}
		} finally {
			// always call close on the IO
			close();
		}
	}

	/**
	 * 
	 */
	public void crash() {
		// this happens when the client has completely crashed
		// make the connected to be false
		// take everything that is waiting on it
		// go through request map and start pushing

		// run as executor since it is synchronized
		// and dont want to get stuck if an issue occurs and the notify never happens
		// we will close and kill process anyway
		ExecutorService executor = Executors.newSingleThreadExecutor();

		Callable callableTask = () -> {
			try {
				for(Object k : this.requestMap.keySet()) {
					PayloadStruct ps = (PayloadStruct) this.requestMap.get(k);
					classLogger.debug("Releasing <" + k + "> <" + ps.methodName + ">");
					ps.ex = "Server has crashed. This happened because you exceeded the memory limits provided or performed an illegal operation. Please relook at your recipe";
					synchronized(ps) {
						ps.notifyAll();
					}
				}
			} catch(Exception e) {
				classLogger.error(Constants.STACKTRACE, e);
			}
			return "Successfully released the payload structs";
		};

		Future future = executor.submit(callableTask);
		try {
			// wait 1 minute at most
			String result = future.get(60, TimeUnit.SECONDS);
			classLogger.info(result);
		} catch (TimeoutException e) {
			classLogger.warn("Not able to release the payload structs within a timely fashion");
			future.cancel(true); 
		} catch (InterruptedException | ExecutionException e) {
			classLogger.error(Constants.STACKTRACE, e);
		} finally {
			executor.shutdown();
		}

		this.close();
		throw new SemossPixelException("Analytic engine is no longer available. This happened because you exceeded the memory limits provided or performed an illegal operation. Please relook at your recipe");
	}

	/**
	 * 
	 * @param closeThis
	 */
	private void closeStream(Closeable closeThis) {
		if(closeThis != null) {
			try {
				closeThis.close();
			} catch (IOException e) {
				classLogger.error(Constants.STACKTRACE, e);
			}
		}
	}

	/**
	 * 
	 * @param user
	 */
	public void setUser(User user) {
		this.user = user;
	}

	/**
	 * 
	 * @return
	 */
	public User getUser() {
		return this.user;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public boolean isConnected() {
		return this.connected;
	}

	/**
	 * 
	 * @return
	 */
	@Override
	public boolean isKillAll() {
		return killall;
	}
	
	/**
	 * 
	 * @return
	 */
	@Override
	public boolean isReady() {
		return this.ready;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy