package com.zillabyte.motherbrain.flow.operations.multilang; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import net.sf.json.JSONNull; import net.sf.json.JSONObject; import org.apache.log4j.Logger; import org.javatuples.Pair; import com.zillabyte.motherbrain.benchmarking.Benchmark; import com.zillabyte.motherbrain.flow.MapTuple; import com.zillabyte.motherbrain.flow.collectors.OutputCollector; import com.zillabyte.motherbrain.flow.operations.Operation; import com.zillabyte.motherbrain.flow.operations.OperationException; import com.zillabyte.motherbrain.relational.DefaultStreamException; import com.zillabyte.motherbrain.universe.Config; import com.zillabyte.motherbrain.utils.JSONUtil; import com.zillabyte.motherbrain.utils.Utils; public class MultiLangProcessTupleObserver implements MultiLangMessageHandler { private MultiLangProcess _proc; private LinkedBlockingQueue<Object> _queue; public static final Object DONE_MARKER = new Object(); //used to mark 'done' in the queue public static final Object END_CYCLE_MARKER = new Object(); //used to mark the end of a cycle private Operation _operation; private final long NEXT_TUPLE_TIMEOUT_CHECK = Config.getOrDefault("multilang.next.tuple.timeout.check", Utils.valueOf(1000L * 60 * 10)).longValue(); // 1000L * 30 private boolean _watching; static Logger _log = Logger.getLogger(MultiLangProcessTupleObserver.class); public MultiLangProcessTupleObserver(MultiLangProcess proc, Operation op) { _proc = proc; _operation = op; } private void init() { _queue = new LinkedBlockingQueue<>(); } public LinkedBlockingQueue<Object> queue() { return _queue; } public void startWatching() { init(); _watching = true; _proc.addMessageListener(this); } /*** * * @param collector * @throws InterruptedException */ @Deprecated public MultiLangProcessTupleObserver collectTuplesUntilDone(OutputCollector collector) throws OperationException, InterruptedException { // Collect while( collectNextTuple(collector) ) {} // Done return this; } /**** * @param collector * @return T if we have more tuples, F otherwise * @throws InterruptedException */ public boolean collectNextTuple(OutputCollector collector) throws OperationException, InterruptedException { // Collect if (_watching == false) throw new OperationException(_operation, "not watching"); Object p = this.takeNextTuple(); if (p == DONE_MARKER) { return false; } else if (p instanceof Pair) { final Pair<?, ?> pp = (Pair<?, ?>) p; final Object v0 = pp.getValue0(); final Object v1 = pp.getValue1(); if (v0 instanceof String && v1 instanceof MapTuple) { collector.emit( (String)v0, (MapTuple) v1); //_log.info(v1.toString()); return true; } } else if (p instanceof Exception) { throw (OperationException) new OperationException(_operation, (Exception)p).setAllMessages("An error occurred while emitting a tuple."); } throw (OperationException) new OperationException(_operation).setAllMessages("The operation emitted data of uninterpretable type: "+p.getClass().getName()+"."); } /** * @throws InterruptedException * @throws OperationException * */ public MultiLangProcessTupleObserver waitForDoneMessageWithoutCollecting() throws InterruptedException, OperationException { // Collect if (_watching == false) throw new OperationException(_operation, "not watching"); while(this.takeNextTuple() != DONE_MARKER) { // Do nothing. wait for null. throwUnhandledErrors(); } // Done return this; } public MultiLangProcessTupleObserver stopWatching() { _watching = false; _proc.removeMessageListener(this); return this; } @Override public void handleMessage(String line) throws OperationException { Benchmark.markBegin("multilang.observer.handle_message"); try { // Sanity if (line == null) return; if (line.equals("end")) return; if (line.length() == 0) return; // Init JSONObject obj = JSONUtil.parseObj(line); // No command? if (obj.has("command") == false) return; // Emits? if (obj.getString("command").equalsIgnoreCase("emit")) { // Get the stream String streamName = obj.optString("stream", null); if (streamName == null || streamName.equals("null")) { // <- wierd json parse thing/bug try { streamName = _operation.defaultStream(); } catch (DefaultStreamException e) { throw (OperationException) new OperationException(_operation, e).setAllMessages("Attempted to emit to non-existent default stream."); } } // Extract the tuple JSONObject jsonTuple = obj.getJSONObject("tuple"); // Build the final tuple MapTuple mapTuple = new MapTuple(); // Get the values from the output json record for(Object field : jsonTuple.keySet()) { final String key = (String) field; if (key == null) { continue; } final Object object = jsonTuple.get(field); if (object == null) { final JSONNull nullObject = JSONNull.getInstance(); assert (nullObject != null); mapTuple.add(key, nullObject); } else { mapTuple.add(key, object); } } // Done //_log.debug("queuing up tuple " + mapTuple + " to stream: " + streamName); _queue.add(new Pair<>(streamName, mapTuple)); } // End Cycle? if (obj.getString("command").equalsIgnoreCase("end_cycle")) { //_log.debug("end_cycle message received"); _queue.add(END_CYCLE_MARKER); } // DONE? if (obj.getString("command").equalsIgnoreCase("done")) { //_log.debug("'done' message received"); _queue.add(DONE_MARKER); } // Failure? if (obj.getString("command").equalsIgnoreCase("fail")) { _queue.add( new MultiLangProcessException(_proc) .setUserMessage(obj.getString("msg")) .setInternalMessage(obj.getString("msg")) ); } } finally { Benchmark.markEnd("multilang.observer.handle_message"); } } /** * @return Next tuple; Null if 'done' has been processed * @throws InterruptedException */ public Object takeNextTuple(long timeout, TimeUnit unit) throws InterruptedException { Benchmark.markBegin("multilang.observer.take_next_tuple.poll"); try { return this._queue.poll(timeout, unit); } finally { Benchmark.markEnd("multilang.observer.take_next_tuple.poll"); } } public Object takeNextTuple() throws InterruptedException, OperationException { if (_watching == false) throw new OperationException(_operation, "not watching"); Object p; do { // Get the next value, waiting up to NEXT_TUPLE_TIMEOUT if necessary p = takeNextTuple(NEXT_TUPLE_TIMEOUT_CHECK, TimeUnit.MILLISECONDS); throwUnhandledErrors(); } while (p == null && this._proc.isAlive()); // If we timeout above and not in paused state, something is likely wrong. if (p == null) { throw (OperationException) new OperationException(_operation).setAllMessages("Timeout waiting for multilang process to emit a tuple or DONE signal."); } // Done throwUnhandledErrors(); return p; } private void throwUnhandledErrors() throws OperationException { // Do we have any unreported errors? for(MultiLangErrorHandler eh : this._proc.getErrorListeners()) { eh.maybeThrowNextError(); } } }