Package com.odiago.flumebase.exec.local

Source Code of com.odiago.flumebase.exec.local.LocalEnvironment$ShutdownThread

/**
* Licensed to Odiago, Inc. under one or more contributor license
* agreements.  See the NOTICE.txt file distributed with this work for
* additional information regarding copyright ownership.  Odiago, Inc.
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.odiago.flumebase.exec.local;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.antlr.runtime.RecognitionException;

import org.apache.hadoop.conf.Configuration;

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

import com.cloudera.util.Pair;

import com.odiago.flumebase.client.ClientConsoleImpl;

import com.odiago.flumebase.exec.BuiltInSymbolTable;
import com.odiago.flumebase.exec.EventWrapper;
import com.odiago.flumebase.exec.ExecEnvironment;
import com.odiago.flumebase.exec.FlowElement;
import com.odiago.flumebase.exec.FlowId;
import com.odiago.flumebase.exec.FlowInfo;
import com.odiago.flumebase.exec.HashSymbolTable;
import com.odiago.flumebase.exec.OutputElement;
import com.odiago.flumebase.exec.QuerySubmitResponse;
import com.odiago.flumebase.exec.SymbolTable;

import com.odiago.flumebase.flume.EmbeddedFlumeConfig;

import com.odiago.flumebase.lang.AssignFieldLabelsVisitor;
import com.odiago.flumebase.lang.CountStarVisitor;
import com.odiago.flumebase.lang.IdentifyAggregates;
import com.odiago.flumebase.lang.JoinKeyVisitor;
import com.odiago.flumebase.lang.JoinNameVisitor;
import com.odiago.flumebase.lang.ReplaceWindows;
import com.odiago.flumebase.lang.TypeChecker;
import com.odiago.flumebase.lang.VisitException;

import com.odiago.flumebase.parser.ASTGenerator;
import com.odiago.flumebase.parser.SQLStatement;

import com.odiago.flumebase.plan.FlowSpecification;
import com.odiago.flumebase.plan.PlanContext;
import com.odiago.flumebase.plan.PropagateSchemas;

import com.odiago.flumebase.server.SessionId;
import com.odiago.flumebase.server.UserSession;

import com.odiago.flumebase.util.DAG;
import com.odiago.flumebase.util.DAGOperatorException;
import com.odiago.flumebase.util.Ref;
import com.odiago.flumebase.util.StringUtils;

import com.odiago.flumebase.util.concurrent.ArrayBoundedSelectableQueue;
import com.odiago.flumebase.util.concurrent.Select;
import com.odiago.flumebase.util.concurrent.Selectable;
import com.odiago.flumebase.util.concurrent.SelectableQueue;
import com.odiago.flumebase.util.concurrent.SyncSelectableQueue;

/**
* Standalone local execution environment for flows.
*/
public class LocalEnvironment extends ExecEnvironment {

  private static final Logger LOG = LoggerFactory.getLogger(
      LocalEnvironment.class.getName());

  /** Config key specifying whether we automatically watch a flow when we create it or not. */
  public static final String AUTO_WATCH_FLOW_KEY = "flumebase.flow.autowatch";
  public static final boolean DEFAULT_AUTO_WATCH_FLOW = true;

  /** Config key specifying the session id of the submitting user for a query. */
  public static final String SUBMITTER_SESSION_ID_KEY = "flumebase.query.submitter.session.id";

  static class ControlOp {
    enum Code {
      AddFlow,         // A new flow shold be deployed.
      CancelFlow,      // An entire flow should be canceled.
      CancelAll,       // All flows should be canceled.
      ShutdownThread,  // Stop processing anything else, immediately.
      Noop,            // Do no control action; just service data events.
      ElementComplete, // A flow element is complete and should be freed.
      Join,            // Add an object to the list of objects to be notified when a
                       // flow is canceled.
      ListFlows,       // Enumerate the running flows.
      WatchFlow,       // Subscribe to a flow's output.
      UnwatchFlow,     // Unsubscribe from a flow's output.
      GetWatchList,    // Get a list of flows being watched by a session.
      SetFlowName,     // Set the name of the output stream for a flow.
    };

    /** What operation should be performed by the worker thread? */
    private final Code mOpCode;

    /** What add'l data is required to do this operation? */
    private final Object mDatum;

    public ControlOp(Code opCode, Object datum) {
      mOpCode = opCode;
      mDatum = datum;
    }

    public Code getOpCode() {
      return mOpCode;
    }

    public Object getDatum() {
      return mDatum;
    }
  }

  /**
   * A request to watch/unwatch a flow.
   */
  private static class WatchRequest {
    public final boolean mIsWatch; // true for watch, false for unwatch.
    public final SessionId mSessionId;
    public final FlowId mFlowId;

    public WatchRequest(SessionId sessionId, FlowId flowId, boolean isWatch) {
      mIsWatch = isWatch;
      mSessionId = sessionId;
      mFlowId = flowId;
    }
  }

  /**
   * The thread where the active flows in the local environment actually operate.
   */
  private class LocalEnvThread extends Thread {

    /** The set of running flows. */
    private Map<FlowId, ActiveFlowData> mActiveFlows;

    /** Mapping from an input queue to the FlowElement it is feeding values to. */
    private Map<SelectableQueue<Object>, FlowElement> mInputQueues;

    /** The selector that lets us read from multiple producers */
    private Select<Object> mSelect;

    /** Unbounded queue used in-thread to post completion events back to the main loop. */
    private SelectableQueue<Object> mCompletionEventQueue;

    /**
     * Set of queues which should be watched for emptiness; when they transition
     * to empty, notify the associated downstream element of the upstream element's
     * closure.
     */
    private Set<SelectableQueue<Object>> mCloseQueues;

    public LocalEnvThread() {
      mActiveFlows = new HashMap<FlowId, ActiveFlowData>();
      mSelect = new Select<Object>();
      mCompletionEventQueue = new SyncSelectableQueue<Object>();
      mInputQueues = new HashMap<SelectableQueue<Object>, FlowElement>();
      mCloseQueues = new HashSet<SelectableQueue<Object>>();

      setName("LocalEnvWorker");
    }

    private void deployFlow(LocalFlow newFlow) throws IOException, InterruptedException {
      final ActiveFlowData activeFlowData = new ActiveFlowData(newFlow);

      Configuration flowConf = newFlow.getConf();
      if (flowConf.getBoolean(AUTO_WATCH_FLOW_KEY, DEFAULT_AUTO_WATCH_FLOW)) {
        // Subscribe to this flow before running it, so we guarantee the user
        // sees all the results.
        long idNum = flowConf.getLong(SUBMITTER_SESSION_ID_KEY, -1);
        SessionId submitterSessionId = new SessionId(idNum);
        UserSession session = getSession(submitterSessionId);
        if (session != null) {
          activeFlowData.addSession(session);
        } else {
          LOG.warn("Invalid session id number: " + idNum);
        }
      }

      // If we haven't yet started Flume, and the flow requires Flume-based sources,
      // start Flume.
      if (newFlow.requiresFlume() && !mFlumeConfig.isRunning()) {
        mFlumeConfig.start();
      }

      // Open all FlowElements in the flow, in reverse bfs order
      // (so sinks are always ready before sources). Add the output
      // queue(s) from the FlowElement to the set of output queues
      // we monitor for further event processing.
      try {
        newFlow.reverseBfs(new DAG.Operator<FlowElementNode>() {
          public void process(FlowElementNode elemNode) throws DAGOperatorException {
            FlowElement flowElem = elemNode.getFlowElement();

            // All FlowElements that we see will have LocalContext subclass contexts.
            // Get the output queue from this.
            LocalContext elemContext = (LocalContext) flowElem.getContext();
            elemContext.initControlQueue(mCompletionEventQueue);
            elemContext.setFlowData(activeFlowData);

            elemContext.createDownstreamQueues();
            List<SelectableQueue<Object>> elemBuffers = elemContext.getDownstreamQueues();
            if (null != elemBuffers) {
              List<FlowElement> downstreams = elemContext.getDownstream();
              // Bind each queue to its downstream element.
              for (int i = 0; i < elemBuffers.size(); i++) {
                SelectableQueue<Object> elemBuffer = elemBuffers.get(i);
                if (null != elemBuffer) {
                  FlowElement downstream = downstreams.get(i);
                  mInputQueues.put(elemBuffer, downstream);
                  mSelect.add(elemBuffer); // And watch this queue for updates.
                }
              }
            }

            try {
              LOG.debug("Opening flow element of class: " + flowElem.getClass().getName());
              flowElem.open();
            } catch (IOException ioe) {
              throw new DAGOperatorException(ioe);
            } catch (InterruptedException ie) {
              throw new DAGOperatorException(ie);
            }
          }
        });
      } catch (DAGOperatorException doe) {
        // This is a wrapper exception; unpack and rethrow with the appropriate type.
        Throwable cause  = doe.getCause();
        if (cause instanceof IOException) {
          throw (IOException) cause;
        } else if (cause instanceof InterruptedException) {
          throw (InterruptedException) cause;
        } else {
          // Don't know how we got here. In any case, do not consider this
          // flow active.
          LOG.error("Unexpected DAG exception: " + doe);
          return;
        }
      }

      mActiveFlows.put(newFlow.getId(), activeFlowData);
    }

    private void cancelFlowInner(ActiveFlowData flowData) {
      // Close all FlowElements in the flow, and remove their output queues
      // from the set of queues we track.
      LocalFlow flow = flowData.getFlow();
      try {
        flow.rankTraversal(new DAG.Operator<FlowElementNode>() {
          public void process(FlowElementNode elemNode) {
            FlowElement flowElem = elemNode.getFlowElement();
            if (!flowElem.isClosed()) {
              try {
                flowElem.close();
              } catch (IOException ioe) {
                LOG.error("IOException when closing flow element: " + ioe);
              } catch (InterruptedException ie) {
                LOG.error("InterruptedException when closing flow element: " + ie);
              }
            }

            // All FlowElements that we see will have LocalContext subclass contexts.
            // Get the output queue from this, and remove it from the tracking set.
            LocalContext elemContext = (LocalContext) flowElem.getContext();
            List<SelectableQueue<Object>> outQueues = elemContext.getDownstreamQueues();
            if (null != outQueues) {
              for (SelectableQueue<Object> outQueue : outQueues) {
                if (null != outQueue) {
                  mSelect.remove(outQueue);
                  mInputQueues.remove(outQueue);
                  mCloseQueues.remove(outQueue);
                }
              }
            }
          }
        });
      } catch (DAGOperatorException doe) {
        // Shouldn't get here with this operator.
        LOG.error("Unexpected dag op exn: " + doe);
      }

      // Notify external threads that this flow is complete.
      flowData.cancel();
    }

    private void cancelFlow(FlowId id) {
      LOG.info("Closing flow: " + id);
      ActiveFlowData flowData = mActiveFlows.get(id);
      if (null == flowData) {
        LOG.error("Cannot cancel flow: No flow available for id: " + id);
        return;
      }
      cancelFlowInner(flowData);
      mActiveFlows.remove(id);
    }

    /** @return true if 'id' refers to an active flow. */
    private boolean isActive(FlowId id) {
      return mActiveFlows.get(id) != null;
    }

    private void cancelAllFlows() {
      if (mActiveFlows.size() == 0) {
        return;
      }

      LOG.info("Closing all flows");
      Set<Map.Entry<FlowId, ActiveFlowData>> flowSet = mActiveFlows.entrySet();
      Iterator<Map.Entry<FlowId, ActiveFlowData>> flowIter = flowSet.iterator();
      while (flowIter.hasNext()) {
        Map.Entry<FlowId, ActiveFlowData> entry = flowIter.next();
        cancelFlowInner(entry.getValue());
      }

      mActiveFlows.clear();
    }

    /**
     * Populate the provided map with info about all running flows. This map
     * is provided by the user process, so we need to synchronize on it before
     * writing. Furthermore, we must notify the calling thread when it is ready
     * for consumption.
     */
    private void listFlows(Map<FlowId, FlowInfo> outMap) {
      assert outMap != null;
      synchronized (outMap) {
        for (Map.Entry<FlowId, ActiveFlowData> entry : mActiveFlows.entrySet()) {
          FlowId id = entry.getKey();
          ActiveFlowData activeData = entry.getValue();
          outMap.put(id, new FlowInfo(id, activeData.getFlow().getQuery(),
              activeData.getStreamName()));
        }

        // Notify the calling thread when we're done.
        outMap.notify();
      }
    }

    /**
     * Sign up the specified session to watch a given flow.
     */
    private void watch(WatchRequest watchReq) {
      UserSession userSession = getSession(watchReq.mSessionId);
      if (null == userSession) {
        LOG.warn("Cannot watch flow from user session " + watchReq.mSessionId
            + "; no such session");
        return;
      }

      ActiveFlowData flow = mActiveFlows.get(watchReq.mFlowId);
      if (null == flow) {
        LOG.warn("Cannot watch flow from user session " + watchReq.mSessionId
            + "; no such flow");
        return;
      }

      if(watchReq.mIsWatch) {
        flow.addSession(userSession);
      } else {
        flow.removeSession(userSession);
      }
    }

    /**
     * Populate the specified flowList with a list of FlowIds that are
     * being watched by the specified sessionId.
     * The flowList is provided by a client in another thread; synchronize
     * on it and notify the other thread when the request is complete.
     */
    private void getWatchList(SessionId sessionId, List<FlowId> flowList) {
      synchronized(flowList) {
        UserSession session = getSession(sessionId);
        if (null != session) {
          for (ActiveFlowData activeFlow : mActiveFlows.values()) {
            List<UserSession> subscribers = activeFlow.getSubscribers();
            if (subscribers.contains(session)) {
              flowList.add(activeFlow.getFlowId());
            }
          }
        } else {
          LOG.error("GetWatchList for sessionId " + sessionId + ": no such session");
        }

        flowList.notify(); // We're done. Wake up the client.
      }
    }

    /**
     * The specified queue is empty and its upstream element is closed. Notify
     * the downstream element of this closure, and remove the queue from the
     * set of things we track.
     */
    private void closeQueue(SelectableQueue<Object> queue, FlowElement flowElem)
        throws IOException, InterruptedException {
      flowElem.closeUpstream();
      mSelect.remove(queue);
      mInputQueues.remove(queue);
      mCloseQueues.remove(queue);
    }

    /**
     * Update the OutputElement of a flow to use a different output stream
     * name for the output.
     */
    private void setFlowName(final FlowId flowId, final String name) {
      ActiveFlowData flowData = mActiveFlows.get(flowId);
      if (null == flowData) {
        LOG.error("Cannot set flow name for flow id " + flowId + ": no such flow.");
        return;
      }

      try {
        // NOTE - This assumes a single OutputElement per flow; we find it by
        // reverseBfs because we assume it's at the end. If there are multiple
        // OutputElements in the flow, we'll get them all trying to open the
        // same node...

        flowData.getFlow().reverseBfs(new DAG.Operator<FlowElementNode>() {
          public void process(FlowElementNode node) throws DAGOperatorException {
            FlowElement flowElem = node.getFlowElement();
            if (flowElem instanceof OutputElement) {
              try {
                ((OutputElement) flowElem).setFlumeTarget(name);
              } catch (IOException ioe) {
                throw new DAGOperatorException(ioe);
              }
            }
          }
        });
      } catch (DAGOperatorException doe) {
        LOG.error("Error setting output stream name: " + doe);
      }
    }

    @Override
    public void run() {
      mSelect.add(mControlQueue); // Listen to events on the control queue.
      mSelect.add(mCompletionEventQueue);
      try {
        boolean isFinished = false;

        while (true) {
          Selectable<Object> nextQueue = null;
          try {
            nextQueue = mSelect.join();
          } catch (InterruptedException ie) {
            // This can happen to notify us we're done processing, etc.
          }

          if (null == nextQueue) {
            continue;
          }

          Object nextAction = null;
          synchronized (nextQueue) {
            if (nextQueue.canRead()) {
              try {
                nextAction = nextQueue.read();
              } catch (InterruptedException ie) {
                // This can happen if we're closing shop fast. We'll just loop around.
              }
            }
          }

          if (null == nextAction) {
            continue;
          } else if (nextAction instanceof ControlOp) {
            ControlOp nextOp = (ControlOp) nextAction;

            switch (nextOp.getOpCode()) {
            case AddFlow:
              LocalFlow newFlow = (LocalFlow) nextOp.getDatum();
              try {
                deployFlow(newFlow);
              } catch (Exception e) {
                LOG.error("Exception deploying flow: " + StringUtils.stringifyException(e));
              } finally {
                // Client waited on this object to know when deployment is done.
                synchronized (newFlow) {
                  newFlow.setDeployed(true);
                  newFlow.notify();
                }
              }
              break;
            case CancelFlow:
              FlowId cancelId = (FlowId) nextOp.getDatum();
              cancelFlow(cancelId);
              break;
            case CancelAll:
              cancelAllFlows();
              break;
            case ShutdownThread:
              isFinished = true;
              break;
            case WatchFlow:
              WatchRequest watchReq = (WatchRequest) nextOp.getDatum();
              watch(watchReq);
              break;
            case UnwatchFlow:
              WatchRequest unwatchReq = (WatchRequest) nextOp.getDatum();
              watch(unwatchReq); // the request has the isWatch flag set false.
              break;
            case GetWatchList:
              Pair<SessionId, List<FlowId>> getReq =
                  (Pair<SessionId, List<FlowId>>) nextOp.getDatum();
              getWatchList(getReq.getLeft(), getReq.getRight());
              break;
            case SetFlowName:
              Pair<FlowId, String> flowNameData =
                  (Pair<FlowId, String>) nextOp.getDatum();
              setFlowName(flowNameData.getLeft(), flowNameData.getRight());
            case Noop:
              // Don't do any control operation; skip ahead to event processing.
              break;
            case ElementComplete:
              // Remove a specific FlowElement from service; it's done.
              LocalCompletionEvent completionEvent = (LocalCompletionEvent) nextOp.getDatum();
              try {
                LocalContext context = completionEvent.getContext();

                List<SelectableQueue<Object>> downstreamQueues =
                    context.getDownstreamQueues();
                List<FlowElement> downstreamElements = context.getDownstream();
                if (null == downstreamElements || downstreamElements.size() == 0) {
                  // We have received close() notification from the last element in a flow.
                  // Remove the entire flow from service.
                  // TODO(aaron): Are multiple SinkFlowElemContexts possible per flow?
                  // If so, we need to wait for the last of these...
                  SinkFlowElemContext sinkContext = (SinkFlowElemContext) context;
                  FlowId id = sinkContext.getFlowId();
                  LOG.info("Processing complete for flow: " + id);
                  if (isActive(id)) {
                    // If the flow is closing naturally, cancel it. If it's
                    // already canceled (inactive), don't do this twice.
                    cancelFlow(id);
                  }
                } else if (null == downstreamQueues || downstreamQueues.size() == 0) {
                  // Has elements, but no queues. Notify the downstream
                  // FlowElement(s) to close too.
                  for (FlowElement downstream : downstreamElements) {
                    downstream.closeUpstream();
                  }
                } else {
                  // May have downstream queues. For each downstream element, close it
                  // immediately if it has no queue, or an empty queue. Otherwise,
                  // watch these queues for emptiness.
                  assert downstreamQueues.size() == downstreamElements.size();
                  for (int i = 0; i < downstreamElements.size(); i++) {
                    SelectableQueue<Object> downstreamQueue = downstreamQueues.get(i);
                    FlowElement downstreamElement = downstreamElements.get(i);
                    if (downstreamQueue == null) {
                      // Close directly.
                      downstreamElement.closeUpstream();
                    } else if (downstreamQueue.size() == 0) {
                      // Queue's dry, close it down.
                      closeQueue(downstreamQueue, downstreamElement);
                    } else {
                      // Watch this queue for completion.
                      mCloseQueues.add(downstreamQueue);
                    }

                  }
                }
              } catch (IOException ioe) {
                LOG.error("IOException closing flow element: " + ioe);
              } catch (InterruptedException ie) {
                LOG.error("Interruption closing downstream element: " + ie);
              }
              break;
            case Join:
              FlowJoinRequest joinReq = (FlowJoinRequest) nextOp.getDatum();
              FlowId id = joinReq.getFlowId();
              Ref<Boolean> waitObj = joinReq.getJoinObj();
              ActiveFlowData flowData = mActiveFlows.get(id);
              if (null == flowData) {
                // This flow id is already canceled. Return immediately.
                synchronized (waitObj) {
                  waitObj.item = Boolean.TRUE;
                  waitObj.notify();
                }
              } else {
                // Mark the waitObj as one we should notify when the flow is canceled.
                flowData.subscribeToCancelation(waitObj);
              }
              break;
            case ListFlows:
              Map<FlowId, FlowInfo> resultMap = (Map<FlowId, FlowInfo>) nextOp.getDatum();
              listFlows(resultMap);
              break;
            }

            if (isFinished) {
              // Stop immediately; ignore any further event processing or control work.
              break;
            }
          } else if (nextAction instanceof EventWrapper) {
            // Process this event with its associated FlowElement.
            // Look up the correct FlowElement based on the queue->FE map.
            FlowElement processor = mInputQueues.get(nextQueue);
            if (null == processor) {
              LOG.error("No FlowElement for input queue " + nextQueue);
            } else {
              try {
                processor.takeEvent((EventWrapper) nextAction);
              } catch (IOException ioe) {
                // TODO(aaron): Encountering an exception mid-flow should cancel the flow.
                LOG.error("Flow element encountered IOException: " + ioe);
              } catch (InterruptedException ie) {
                LOG.error("Flow element encountered InterruptedException: " + ie);
              }
            }

            if (((SelectableQueue<Object>) nextQueue).size() == 0
                && mCloseQueues.contains(nextQueue)) {
              // We just transitioned this FE's input queue to empty, and it was closed
              // upstream. Notify the downstream element of this closure.
              try {
                closeQueue((SelectableQueue<Object>) nextQueue, processor);
              } catch (IOException ioe) {
                LOG.error("IOException closing flow element: " + ioe);
              } catch (InterruptedException ie) {
                LOG.error("InterruptedException closing flow element: " + ie);
              }
            }
          } else {
            LOG.error("Do not know what to do with queue element " + nextAction
                + " of class " + nextAction.getClass().getName());
          }
        }
      } finally {
        // Shut down the embedded Flume instance before we exit the thread.
        if (mFlumeConfig.isRunning()) {
          mFlumeConfig.stop();
        }
      }
    }
  }

  /** A UserSession describing the console of this process. */
  private static final UserSession LOCAL_SESSION;

  static {
    LOCAL_SESSION = new UserSession(new SessionId(0), null, new ClientConsoleImpl());
  }

  /** The configuration for this environment instance. */
  private Configuration mConf;

  /** Next flow id to assign to new flows. */
  private long mNextFlowId;

  /** The AST generator used to parse user queries. */
  private ASTGenerator mGenerator;

  /**
   * Mapping from named outputs to the memory elements fulfilling those outputs.
   * If clients will be accessing elements of this map, then it needs to be
   * internally synchronized.
   */
  private Map<String, MemoryOutputElement> mMemoryOutputMap;

  /**
   * Manager for the embedded Flume instances in this environment.
   * References to this object are distributed in the client thread,
   * but its methods are used only in the execution thread.
   */
  private EmbeddedFlumeConfig mFlumeConfig;

  /** The thread that does the actual flow execution. */
  private LocalEnvThread mLocalThread;

  /** set to true after connect(). */
  private boolean mConnected;

  /**
   * Queue of control events passed from the console thread to the worker thread
   * (e.g., "deploy stream", "cancel stream", etc.)
   */
  private SelectableQueue<Object> mControlQueue; // Actually full of ControlOp instances

  /** Max len for mControlQueue, or any FlowElement's input queue. */
  static final int MAX_QUEUE_LEN = 100;

  /**
   * The root symbol table where streams, etc are defined. Used in the
   * user thread for AST and plan walking.
   */
  private SymbolTable mRootSymbolTable;

  /**
   * Main constructor.
   */
  public LocalEnvironment(Configuration conf) {
    this(conf,
        new HashSymbolTable(new BuiltInSymbolTable()),
        new HashMap<String, MemoryOutputElement>(),
        new EmbeddedFlumeConfig(conf));
  }

  /**
   * Constructor for testing; allows dependency injection.
   */
  public LocalEnvironment(Configuration conf,
      SymbolTable rootSymbolTable,
      Map<String, MemoryOutputElement> memoryOutputMap,
      EmbeddedFlumeConfig flumeConfig) {
    mConf = conf;
    mRootSymbolTable = rootSymbolTable;
    mMemoryOutputMap = memoryOutputMap;

    mGenerator = new ASTGenerator();
    mNextFlowId = 0;
    mControlQueue = new ArrayBoundedSelectableQueue<Object>(MAX_QUEUE_LEN);
    mFlumeConfig = flumeConfig;
    mLocalThread = this.new LocalEnvThread();
  }

  /** Given a Configuration that has SUBMITTER_SESSION_ID_KEY set, return the
   * UserSession corresponding to this SessionId. This is used to resolve the
   * submitter of a LocalFlow, FlowSpecification, etc.
   */
  private UserSession getSessionForConf(Configuration conf) {
    SessionId id = new SessionId(conf.getLong(SUBMITTER_SESSION_ID_KEY, -1));
    return getSession(id);
  }

  @Override
  public SessionId connect() throws IOException {
    Runtime.getRuntime().addShutdownHook(new ShutdownThread());
    mLocalThread.start();
    mConnected = true;
    return new SessionId(0); // Local user is always session 0.
  }

  @Override
  public boolean isConnected() {
    return mConnected;
  }

  @Override
  public String getEnvName() {
    return "local";
  }

  /**
   * Take the user's query, convert it into a local plan,
   * and execute it.
   */
  @Override
  public QuerySubmitResponse submitQuery(String query, Map<String, String> options)
      throws InterruptedException {
    StringBuilder msgBuilder = new StringBuilder();
    FlowId flowId = null;

    // Build a configuration out of our conf and the user's options.
    Configuration planConf = new Configuration(mConf);
    for (Map.Entry<String, String> entry : options.entrySet()) {
      planConf.set(entry.getKey(), entry.getValue());
    }

    try {
      // Send the parser's error messages into a buffer rather than stderr.
      ByteArrayOutputStream errBufferStream = new ByteArrayOutputStream();
      PrintStream errStream = new PrintStream(errBufferStream);

      SQLStatement stmt = mGenerator.parse(query, errStream);

      errStream.close();
      String errMsg = new String(errBufferStream.toByteArray());
      msgBuilder.append(errMsg);

      if (null == stmt) {
        msgBuilder.append("(Could not parse command)");
        return new QuerySubmitResponse(msgBuilder.toString(), null);
      }

      stmt.accept(new AssignFieldLabelsVisitor());
      stmt.accept(new CountStarVisitor()); // Must be after assign labels, before TC.
      stmt.accept(new TypeChecker(mRootSymbolTable));
      stmt.accept(new ReplaceWindows()); // Must be after TC.
      stmt.accept(new JoinKeyVisitor()); // Must be after TC.
      stmt.accept(new JoinNameVisitor());
      stmt.accept(new IdentifyAggregates()); // Must be after TC.
      PlanContext planContext = new PlanContext();
      planContext.setConf(planConf);
      planContext.setSymbolTable(mRootSymbolTable);
      PlanContext retContext = stmt.createExecPlan(planContext);
      msgBuilder.append(retContext.getMsgBuilder().toString());
      FlowSpecification spec = retContext.getFlowSpec();
      if (null != spec) {
        spec.setQuery(query);
        spec.setConf(planConf);
        // Given a flow specification from the AST, run it through
        // necessary post-processing and optimization phases.
        spec.bfs(new PropagateSchemas());
        if (retContext.isExplain()) {
          // We just should explain this flow, but not actually add it.
          msgBuilder.append("Execution plan:\n");
          msgBuilder.append(spec.toString());
          msgBuilder.append("\n");
        } else {
          flowId = addFlow(spec);
        }
      }
    } catch (VisitException ve) {
      msgBuilder.append("Error processing command: " + ve.getMessage());
    } catch (RecognitionException re) {
      msgBuilder.append("Error parsing command: " + re.getMessage());
    } catch (DAGOperatorException doe) {
      msgBuilder.append("Error processing plan: " + doe.getMessage());
    }

    return new QuerySubmitResponse(msgBuilder.toString(), flowId);
  }

  @Override
  public FlowId addFlow(FlowSpecification spec) throws InterruptedException {
    if (null != spec) {
      // Turn the specification into a physical plan and run it.
      FlowId flowId = new FlowId(mNextFlowId++);
      UserSession userSession = getSessionForConf(spec.getConf());
      LocalFlowBuilder flowBuilder = new LocalFlowBuilder(flowId, mRootSymbolTable,
          mFlumeConfig, mMemoryOutputMap, userSession);
      try {
        spec.reverseBfs(flowBuilder);
      } catch (DAGOperatorException doe) {
        // An exception occurred when creating the physical plan.
        // LocalFlowBuilder put a message for the user in here; print it
        // without a stack trace. The flow cannot be executed.
        userSession.sendErr(doe.getMessage());
        return null;
      }
      LocalFlow localFlow = flowBuilder.getLocalFlow();
      localFlow.setQuery(spec.getQuery());
      localFlow.setConf(spec.getConf());
      if (localFlow.getRootSet().size() == 0) {
        // No nodes created (empty flow, or DDL-only flow, etc.)
        return null;
      } else {
        synchronized (localFlow) {
          mControlQueue.put(new ControlOp(ControlOp.Code.AddFlow, localFlow));
          while (!localFlow.isDeployed()) {
            localFlow.wait();
          }
        }
        return flowId;
      }
    } else {
      return null;
    }
  }

  @Override
  public void cancelFlow(FlowId id) throws InterruptedException, IOException {
    mControlQueue.put(new ControlOp(ControlOp.Code.CancelFlow, id));
  }

  @Override
  public void joinFlow(FlowId id) throws InterruptedException {
    Ref<Boolean> joinObj = new Ref<Boolean>();
    synchronized (joinObj) {
      mControlQueue.put(new ControlOp(ControlOp.Code.Join, new FlowJoinRequest(id, joinObj)));
      joinObj.wait();
    }
  }

  @Override
  public boolean joinFlow(FlowId id, long timeout) throws InterruptedException {
    Ref<Boolean> joinObj = new Ref<Boolean>();
    joinObj.item = Boolean.FALSE;
    synchronized (joinObj) {
      mControlQueue.put(new ControlOp(ControlOp.Code.Join, new FlowJoinRequest(id, joinObj)));
      joinObj.wait(timeout);
      return joinObj.item;
    }
  }

  @Override
  public void watchFlow(SessionId sessionId, FlowId flowId) throws InterruptedException {
    mControlQueue.put(new ControlOp(ControlOp.Code.WatchFlow,
        new WatchRequest(sessionId, flowId, true)));
  }

  @Override
  public void unwatchFlow(SessionId sessionId, FlowId flowId) throws InterruptedException {
    mControlQueue.put(new ControlOp(ControlOp.Code.UnwatchFlow,
        new WatchRequest(sessionId, flowId, false)));
  }

  @Override
  public Map<FlowId, FlowInfo> listFlows() throws InterruptedException {
    Map<FlowId, FlowInfo> outData = new TreeMap<FlowId, FlowInfo>();
    synchronized (outData) {
      mControlQueue.put(new ControlOp(ControlOp.Code.ListFlows, outData));
      outData.wait();
    }
    return outData;
  }

  @Override
  public List<FlowId> listWatchedFlows(SessionId sessionId) throws InterruptedException {
    List<FlowId> outList = new ArrayList<FlowId>();
    Pair<SessionId, List<FlowId>> args = new Pair<SessionId, List<FlowId>>(sessionId, outList);
    synchronized (outList) {
      mControlQueue.put(new ControlOp(ControlOp.Code.GetWatchList, args));
      outList.wait();
    }
    return outList;
  }

  @Override
  public void setFlowName(FlowId flowId, String name) throws InterruptedException {
    Pair<FlowId, String> nameReq = new Pair<FlowId, String>(flowId, name);
    mControlQueue.put(new ControlOp(ControlOp.Code.SetFlowName, nameReq));
  }

  /**
   * Stop the local environment and shut down any flows operating therein.
   */
  @Override
  public void disconnect(SessionId sessionId) throws InterruptedException {
    shutdown();
  }

  @Override
  public void shutdown() throws InterruptedException {
    mControlQueue.put(new ControlOp(ControlOp.Code.CancelAll, null));
    mControlQueue.put(new ControlOp(ControlOp.Code.ShutdownThread, null));
    mLocalThread.join();
    mConnected = false;
  }

  /**
   * For the LocalEnvironment, there is only the local user session.
   */
  @Override
  protected UserSession getSession(SessionId id) {
    return LOCAL_SESSION;
  }

  /**
   * Thread that closes the server impl and cleans up if the server itself is shut down.
   */
  private class ShutdownThread extends Thread {
    public ShutdownThread() {
      super("RemoteServerImpl-shutdown");
    }

    @Override
    public void run() {
      if (LocalEnvironment.this.mConnected) {
        LOG.info("Stopping exec environment due to service shutdown...");
        try {
          LocalEnvironment.this.shutdown();
          LOG.info("Exec environment completely stopped.");
        } catch (Exception e) {
          LOG.error("Exception shutting down LocalEnvironment in shutdown thread: "
              + StringUtils.stringifyException(e));
        }
      }
    }
  }
}
TOP

Related Classes of com.odiago.flumebase.exec.local.LocalEnvironment$ShutdownThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.