Package statechum.analysis.Erlang

Source Code of statechum.analysis.Erlang.ErlangRuntime

/* Copyright (c) 2013 The University of Sheffield.
*
* This file is part of StateChum
*
* StateChum is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* StateChum is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with StateChum.  If not, see <http://www.gnu.org/licenses/>.
*/
package statechum.analysis.Erlang;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangDecodeException;
import com.ericsson.otp.erlang.OtpErlangExit;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangObject;

import statechum.GlobalConfiguration;
import statechum.Helper;
import statechum.GlobalConfiguration.G_PROPERTIES;
import statechum.analysis.learning.experiments.ExperimentRunner;
import statechum.analysis.learning.experiments.ExperimentRunner.HandleProcessIO;

public class ErlangRuntime {
  /** Erlang machine in which we run most stuff. */
  protected Process erlangProcess = null;
 
  /** Monitors the above machine and dumps its out and err to the console. */
  protected Thread stdDumper = null;

  protected String traceRunnerNode;
 
  public static final int timeBetweenChecks = 100;


  /**
   * Runs the supplied process and returns output and error streams in an
   * exception if the process returned a non-zero error code. Upon success, no
   * output is produced.
   *
   * @param p
   *            process to run.
   */
  public static void dumpProcessOutputOnFailure(String name, Process p) {
    final StringBuffer err = new StringBuffer(), out = new StringBuffer();
    ExperimentRunner.dumpStreams(p, timeBetweenChecks,
        new HandleProcessIO() {

          @Override
          public void OnHeartBeat() {// no prodding is done for a
                        // short-running converter.
          }

          @Override
          public void StdErr(StringBuffer b) {
            err.append(b);
          }

          @Override
          public void StdOut(StringBuffer b) {
            out.append(b);
          }
        });
    try {
      p.waitFor();
    } catch (InterruptedException e) {
      // assumed we have been asked to terminate
    }

    if (p.exitValue() != 0)
      throw new IllegalArgumentException("Failure running " + name + "\n"
          + err + (err.length() > 0 ? "\n" : "") + out);
  }

  /** How long to wait for a server to start. */
  protected int serverTimeout = 500;

  /** Sets new timeout - very useful to increase it for testing. */
  public void setTimeout(int time) {
    serverTimeout = time;
  }

  /** The default constructor is public to permit multiple Erlang runtimes to be started. */
  public ErlangRuntime()
  {
  }

  protected static ErlangRuntime defaultRuntime = null;
 
  /** Most of the time, one would not need to start multiple runtime instances, or use custom parameters. For this purpose, a single runner is good enough and it can be obtained by calling the method below. */
  public static ErlangRuntime getDefaultRuntime()
  {
    if (defaultRuntime == null)
    {
      defaultRuntime = new ErlangRuntime();defaultRuntime.startRunner();
    }
    return defaultRuntime;
  }
 
  /** Starts the runner with default arguments. */
  public void startRunner()
  {
    startErlang(ErlangRunner.genServerDefault, 0);
  }
 
  /** Creates a runner that can be used to communicate with the runtime associated with this node. If this runtime is not associated with Erlang runner, creates this runner. */
  public synchronized ErlangRunner createNewRunner()
  {
    if (erlangProcess == null)
      throw new IllegalArgumentException("Erlang is not running");
    ErlangRunner newRunner = new ErlangRunner(traceRunnerNode);newRunner.initRunner();return newRunner;
  }
 
  /**
   * Starts Erlang in the background,
   *
   * @param runnerMode
   *            "tracerunner" means production use, noserver,halt,error are
   *            used for testing.
   * @param delay
   *            how long to wait after launching the server - used only for
   *            testing to ensure server terminates before our loop starts.
   */
  public synchronized void startErlang(String runnerMode, long delay) {
    proclist = null;
    final boolean displayErlangOutput = Boolean.parseBoolean(GlobalConfiguration.getConfiguration().getProperty(G_PROPERTIES.ERLANGOUTPUT_ENABLED));
    try {
      if (erlangProcess == null)
      {
        if (displayErlangOutput)
        {
          System.out.print("Starting Erlang...");System.out.flush();
        }
       
        final long startTime = System.currentTimeMillis();

        ErlangNode.initNodeParameters(null, null);
       
        String tracerunnerProgram = "tracerunner.erl";
        // It is very important that there is an '@' part to the node name: without it, Erlang adds a host name by default so the actual node name is different from the one supplied via -sname to the process and the node does not respond to the name without '@'.
        traceRunnerNode = "tracerunner" + "_"+ System.nanoTime()+ "_" + "@" + "127.0.0.1";// + InetAddress.getLocalHost().getHostName().replaceAll("\\..*", "");// eliminates everything starting with the first dot, important on MacOS.
        // now we simply evaluate "halt()." which starts epmd if
        // necessary and we can check along the way that we can run
        // Erlang at all.
        Process p = Runtime.getRuntime().exec(
            new String[] { ErlangRunner.getErlangBin() + "erl",
                "-eval", "halt().", "-name", traceRunnerNode,
                "-noshell", "-setcookie", ErlangNode.getErlangNode().cookie() }, null
                );//getErlangBeamDirectory());
        dumpProcessOutputOnFailure(
            "testing that Erlang can be run at all", p);
       
        // After the first Erlang node is created (by passing -sname to Erlang), epmd appears and we can create a node.
        ErlangNode.getErlangNode().createNode();

        // The compilation phase could a few seconds but only needs to
        // be done once after installation of Statechum. Here we only compile the core part of Statechum, the rest is compiled by the runtime
        // because compile options depend on the version of Erlang in use and this is only known to the runtime.
        for (String str : new String[] { tracerunnerProgram,
            "tracer3.erl", "export_wrapper.erl", "gen_event_wrapper.erl",
            "gen_fsm_wrapper.erl", "gen_server_wrapper.erl" })
        ErlangRunner.compileErl(new File(ErlangRunner.getErlangFolder(), str), null,true);

        // Now build environment variables to ensure that dialyzer will
        // find a directory to put its plt file in.
        // This is actually a dialyzer bug which manifests itself on
        // Windows -
        // there is no need for dialyzer_options:build(Opts) to call
        // dialyzer_plt:get_default_plt()
        // before build_options(Opts, DefaultOpts1).
        List<String> envpList = new LinkedList<String>();
       
        Map<String,String> newValues = new TreeMap<String,String>();
        newValues.put("HOME",new File(GlobalConfiguration.getConfiguration().getProperty(G_PROPERTIES.PATH_ERLANGBEAM)).getAbsolutePath());
        newValues.put("ERL_MAX_PORTS","1024");// to limit the size of each Erlang instance to a few meg from a few hundred meg

        for (Entry<String, String> entry : System.getenv().entrySet())
          if (!newValues.containsKey(entry.getKey()))
            envpList.add(entry.getKey() + "=" + entry.getValue());
       
        for(Entry<String, String> entry : newValues.entrySet())
          envpList.add(entry.getKey() + "=" + entry.getValue());

        final Process processWithErlangRuntime = Runtime
            .getRuntime()
            .exec(new String[] {
                ErlangRunner.getErlangBin() + "erl",
                "-pa",ErlangRunner.getErlangBeamDirectory().getAbsolutePath(),
                // the easiest way to substitute our module in place
                // of the original Erlang's one, otherwise I'd
                // have to rely on tracerunner:compileAndLoad
                "-run", "tracerunner", "start", ErlangNode.getErlangNode().getName(),
                runnerMode, "-name", traceRunnerNode,
                "-noshell", "-setcookie", ErlangNode.getErlangNode().cookie() },
                envpList.toArray(new String[0]));
                //getErlangBeamDirectory());
        stdDumper = new Thread(new Runnable() {

          @Override
          public void run() {
            ExperimentRunner.dumpStreams(processWithErlangRuntime,
                timeBetweenChecks, new HandleProcessIO() {

                  @Override
                  public void OnHeartBeat() {
                    // no prodding is done - we are
                    // being prodded by Erlang instead.
                  }

                  @Override
                  public void StdErr(StringBuffer b) {
                    if (displayErlangOutput)
                      System.out.print("[ERLANG] " + b.toString());
                  }

                  @Override
                  public void StdOut(StringBuffer b) {
                    if (displayErlangOutput)
                      System.out.print("[ERLERR] " + b.toString());
                  }
                });
          }
        });
        stdDumper.setDaemon(true);
        stdDumper.start();
        if (delay > 0)
          Thread.sleep(delay);
        // At this point, the process may have not yet started or
        // already terminated, it is easy to find out which of the
        // two has happened by doing
        int timeout = serverTimeout;
        while (!ErlangNode.getErlangNode().getNode().ping(traceRunnerNode, 10) && timeout > 0) {
          try {
            processWithErlangRuntime.exitValue();
            // process terminated, record this as a failure
            timeout = 0;
          } catch (IllegalThreadStateException e) {
            // process not yet terminated, hence we keep waiting
            --timeout;
          }
        }
        if (timeout <= 0)
        {
          final long endTime = System.currentTimeMillis();
          throw new IllegalArgumentException("timeout waiting for a server to start after " + (endTime - startTime) + "ms");
        }
        ErlangRunner runner = new ErlangRunner(traceRunnerNode);
        runner.forceReady();// cannot call createNewRunner because that one is supposed only to be used after we have successfully started Erlang runtime.
        timeout = serverTimeout;
        OtpErlangObject response = null;
       
        // Now we verify that communication with the runtime is possible because genserver may not have started yet and our messages will disappear into the void.
        // We attempt to send messages with a short response time and check that we get what we expect.
        do
        {
          try
          {
            response = runner.call(new OtpErlangObject[]{new OtpErlangAtom("echo2Notuple"),new OtpErlangAtom("aaa")},10);
            if (!(response instanceof OtpErlangAtom) || !((OtpErlangAtom)response).atomValue().equals("ok_aaa"))
              timeout = 0;// force failure upon an invalid response
          }
          catch(IllegalArgumentException ex)
          {
            // response remains null
          }
          --timeout;
        }
        while(timeout > 0 && response == null);
        if (timeout <= 0)
        {
          final long endTime = System.currentTimeMillis();
          throw new IllegalArgumentException("timeout waiting for a server's genserver to start after " + (endTime - startTime) + "ms");
        }
       
        // Erlang started, the next step is to train message queue since given that we may have sent a number of messages above, responses to them may just begin to trickle.
        while(null != runner.thisMbox.receive(100))
        {// drain the queue
        }
       
        for(G_PROPERTIES folderKind:new G_PROPERTIES[]{G_PROPERTIES.PATH_ERLANGTYPER,G_PROPERTIES.PATH_ERLANGSYNAPSE})
          for (File f : new File(GlobalConfiguration.getConfiguration().getProperty(folderKind)).listFiles())
            if (ErlangRunner.validName(f.getName()))
              ErlangRunner.compileErl(f, runner, true);
       
        // seems like success, set the value of ErlangProcess
        erlangProcess = processWithErlangRuntime;
        proclist = runner.listProcesses();runner.close();
      }
      //final long endTime = System.currentTimeMillis();
      //System.out.println("Started. " + (endTime - startTime) + "ms");
    } catch (IOException e) {
      killErlang();
      Helper.throwUnchecked("failed to start Erlang", e);
    } catch (InterruptedException e1) {
      killErlang();
      Helper.throwUnchecked("terminating as requested", e1);
    }
    catch (OtpErlangDecodeException e) {
      killErlang();
      Helper.throwUnchecked("decode exception", e);
    }
    catch (OtpErlangExit e) {
      killErlang();
      Helper.throwUnchecked("exlang exit", e);
    }
  }

  /**
   * Stores the processes that were extant just after the tracerunner starts.
   * The killProcesses method will kill anything not in this list, which
   * should mean anything started by traces.
   */
  protected OtpErlangList proclist;

  public void killErlang() {
    if (erlangProcess != null)
    {
      ErlangRunner.closeAll(traceRunnerNode);
      final boolean displayErlangOutput = Boolean.parseBoolean(GlobalConfiguration.getConfiguration().getProperty(G_PROPERTIES.ERLANGOUTPUT_ENABLED));
      if (displayErlangOutput)
      {
        System.out.print("Stopping Erlang...");
        System.out.flush();
      }
      erlangProcess.destroy();
      try { erlangProcess.waitFor(); }
      catch (InterruptedException e) {
        statechum.Helper.throwUnchecked("wait for Erlang to terminate aborted", e);
      }
      erlangProcess = null;
      stdDumper.interrupt();// if this one is sleeping, interrupt will wake it and it will terminate since
                  // Erlang is already no more ...
      try { stdDumper.join()}
      catch (InterruptedException e) {
        Helper.throwUnchecked("Interrupt waiting for erlang out/err dumper to terminate",e);
      }// wait for that to happen.
      stdDumper = null;
    }
  }

}
TOP

Related Classes of statechum.analysis.Erlang.ErlangRuntime

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.