Package erjang.driver

Source Code of erjang.driver.EDriverTask

/**
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2009 by Trifork
*
* Licensed 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 erjang.driver;

import static erjang.EPort.am_arg0;
import static erjang.EPort.am_args;
import static erjang.EPort.am_binary;
import static erjang.EPort.am_cd;
import static erjang.EPort.am_close;
import static erjang.EPort.am_env;
import static erjang.EPort.am_eof;
import static erjang.EPort.am_exit_status;
import static erjang.EPort.am_hide;
import static erjang.EPort.am_in;
import static erjang.EPort.am_line;
import static erjang.EPort.am_nouse_stdio;
import static erjang.EPort.am_out;
import static erjang.EPort.am_packet;
import static erjang.EPort.am_stream;
import static erjang.EPort.am_use_stdio;
import erjang.ETask.STATE;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import kilim.Pausable;
import kilim.Task;
import erjang.EAtom;
import erjang.EBinary;
import erjang.ECons;
import erjang.EHandle;
import erjang.EInternalPID;
import erjang.EInternalPort;
import erjang.EObject;
import erjang.EPID;
import erjang.EPeer;
import erjang.EPort;
import erjang.EProc;
import erjang.ERT;
import erjang.ERef;
import erjang.ESeq;
import erjang.EString;
import erjang.ETask;
import erjang.ETuple;
import erjang.ETuple2;
import erjang.ETuple3;
import erjang.ETuple4;
import erjang.ErjangConfig;
import erjang.ErlangError;
import erjang.ErlangException;
import erjang.ErlangExit;
import erjang.ErlangExitSignal;
import erjang.NotImplemented;

/**
* Base class for the two kinds of driver tasks: drivers, and "exec"s
*/
public abstract class EDriverTask extends ETask<EInternalPort> implements
    NIOHandler {

  static Logger log = Logger.getLogger("erjang.driver");

  public abstract EObject getName();
 
  @Override
  public String toString() {
    return "<driver_task:" + super.id + ">";
  }

  private static final EAtom am_name = EAtom.intern("name");
  private static final EAtom am_data = EAtom.intern("data");
  private static final EAtom am_connected = EAtom.intern("connected");
  private static final EAtom am_closed = EAtom.intern("closed");
  private final EInternalPort port;
  protected EPID owner;
  private final EDriverControl instance;

 
  public EPID owner() {
    return owner;
  }
 
  public void owner(EInternalPID ipid) {
    this.owner = ipid;
  }
 
  private  static ConcurrentHashMap<Integer,EDriverTask> all_ports
    = new ConcurrentHashMap<Integer,EDriverTask> ();

  public EDriverTask(EPID owner, EDriverControl driver) {

    EDriver drv = driver.getDriver();
    if (drv.useDriverLevelLocking() == true) {
      log.fine("DRIVER_LEVEL_LOCK: "+driver);
      driver = new LockingDriverInstance(driver, drv.getLock());
    } else {
      driver = new LockingDriverInstance(driver, new kilim.ReentrantLock());
    }
   
    this.owner = owner;
    this.instance = driver;
    this.port = new EInternalPort(this);
    driver.setTask(this);
   

    all_ports.put(id, this);
  }

  public void setupInstance() {instance.setup();}

  @Override
  public EInternalPort self_handle() {
    return port;
  }

  static enum Mode {
    STREAM, PACKET, LINE
  };

  protected boolean send_binary_data;
  protected boolean is_out_only;
  protected boolean is_in_only;
  protected boolean send_eof;
  protected boolean hide;
  protected int port_out_fd;
  protected int port_in_fd;
  protected boolean send_exit_status;
  protected int packet;
  protected int line_length;
  protected Mode mode = Mode.STREAM;
  protected String[] cmd;
  protected String cwd;
  protected HashMap<String, String> env;
  private long abs_timeout;

  /** state controlled from elsewhere... erlang:port_set_data/2*/
  public EObject port_data;
 
  public static final int ERTS_PORT_SFLG_CONNECTED = 1<<0;
  public static final int ERTS_PORT_SFLG_EXITING = 1<<1;
  public static final int ERTS_PORT_SFLG_DISTRIBUTION = 1<<2;
  public static final int ERTS_PORT_SFLG_BINARY_IO = 1<<3;
  public static final int ERTS_PORT_SFLG_SOFT_EOF = 1<<4;
  public static final int ERTS_PORT_SFLG_PORT_BUSY = 1<<5;
  public static final int ERTS_PORT_SFLG_CLOSING = 1<<6;
  public static final int ERTS_PORT_SFLG_SEND_CLOSED = 1<<7;
  public static final int ERTS_PORT_SFLG_LINEBUF_IO = 1<<8;
  public static final int ERTS_PORT_SFLG_IMMORTAL = 1<<9;
  public static final int ERTS_PORT_SFLG_FREE = 1<<10;
  public static final int ERTS_PORT_SFLG_FREE_SCHEDULED = 1<<11;
  public static final int ERTS_PORT_SFLG_INITIALIZING = 1<<12;
  public static final int ERTS_PORT_SFLG_PORT_SPECIFIC_LOCK = 1<<13;
  public static final int ERTS_PORT_SFLG_INVALID = 1<<14;
  public static final int ERTS_PORT_SFLG_DEBUG = 1<<31;
 
  public int status;
  private EPeer peer;
  boolean stderr_to_stdout;
  private EPID reply_closed_to;

  /**
   * @param cmd
   * @param portSetting
   */
  protected void parseOptions(String[] cmd, EObject portSetting) {
    // TODO: most of this is way too expensive for non-exec ports

    // set by options
    this.cmd = cmd;
    this.cwd = System.getProperty("user.dir");
    this.env = new HashMap<String, String>(ErjangConfig.getenv());

    this.packet = -1; // not set
    this.line_length = -1;

    this.send_exit_status = false;
    this.stderr_to_stdout = false;
    this.port_in_fd = 1;
    this.port_out_fd = 2;

    this.hide = false;
    this.send_eof = false;

    this.is_in_only = false;
    this.is_out_only = false;
    this.send_binary_data = false;

    ECons settings = portSetting.testCons();
    if (settings == null)
      throw ERT.badarg();

    for (; settings != null && !settings.isNil(); settings = settings
        .tail().testCons()) {

      EObject val = settings.head();
      ETuple tup;
      if ((tup = val.testTuple()) != null) {
        ETuple2 tup2;
        if ((tup2 = ETuple2.cast(tup)) != null) {

          if (tup2.elem1 == am_args) {
            ESeq list = tup2.elem2.testSeq();
            EObject[] nargs = list.toArray();

            String[] new_cmd = new String[nargs.length + 1];
            new_cmd[0] = cmd[0];
            for (int i = 0; i < nargs.length; i++) {
              new_cmd[i + 1] = EString.make(nargs[i])
                  .stringValue();
            }
            cmd = new_cmd;

          } else if (tup2.elem1 == am_arg0) {
            String[] new_cmd = new String[2];
            new_cmd[0] = cmd[0];
            new_cmd[1] = EString.make(tup2.elem2).stringValue();

          } else if (tup2.elem1 == am_packet) {
            packet = tup2.elem2.asInt();
            mode = Mode.PACKET;

          } else if (tup2.elem1 == am_cd) {
            cwd = EString.make(tup2.elem2).stringValue();

          } else if (tup2.elem1 == am_env) {

            ESeq ee;
            if ((ee = tup2.elem2.testSeq()) == null) {
              throw ERT.badarg();
            }

            EObject[] envs = ee.toArray();
            for (int i = 0; i < envs.length; i++) {
              ETuple2 e = ETuple2.cast(envs[i].testTuple());
              if (e.elem2 == ERT.FALSE) {
                env.remove(EString.make(e.elem1).stringValue());
              } else {
                env.put(EString.make(e.elem1).stringValue(),
                    EString.make(e.elem2).stringValue());
              }
            }

          } else if (tup2.elem1 == am_line) {
            line_length = tup2.elem2.asInt();
            mode = Mode.LINE;

          } else {

            throw ERT.badarg();
          }

        }
      } else if (val == am_stream) {
        mode = Mode.STREAM;

      } else if (val == am_use_stdio) {
        port_in_fd = 1;
        port_out_fd = 2;

      } else if (val == am_nouse_stdio) {
        port_in_fd = 3;
        port_out_fd = 4;

      } else if (val == am_hide) {
        hide = true;

      } else if (val == am_exit_status) {
        send_exit_status = true;

      } else if (val == EPort.am_stderr_to_stdout) {
        stderr_to_stdout = true;

      } else if (val == am_eof) {
        send_eof = true;

      } else if (val == am_in) {
        is_in_only = true;

      } else if (val == am_out) {
        is_out_only = true;

      } else if (val == am_binary) {
        send_binary_data = true;

      }
    }

  }

  @Override
  public Task start() {
    Task result = super.start();
    this.pstate = STATE.RUNNING;
    return result;
  }
 
  @Override
  public void execute() throws Pausable {
    try {

      EObject result = null;
      try {

        // driver main loop
        main_loop();

        result = am_normal;

      } catch (NotImplemented e) {
        log.log(Level.SEVERE, "exiting "+self_handle(), e);
        result = e.reason();

      } catch (ErlangException e) {
        // e.printStackTrace();
        result = e.reason();

      } catch (ErlangExitSignal e) {
        // e.printStackTrace();
        result = e.reason();

      } catch (Throwable e) {

        e.printStackTrace();

        ESeq erl_trace = ErlangError.decodeTrace(e.getStackTrace());
        ETuple java_ex = ETuple.make(am_java_exception, EString
            .fromString(ERT.describe_exception(e)));

        result = ETuple.make(java_ex, erl_trace);
       
        if (log.isLoggable(Level.FINER)) {
          log.finer("EXITING "+result);
        }

      } finally {
        // this.runner = null;
        this.pstate = STATE.DONE;
      }

      // System.err.println("task "+this+" exited with "+result);
      do_proc_termination(result);

    } catch (ThreadDeath e) {
      throw e;

    } catch (Throwable e) {
      e.printStackTrace();
    }

  }

  /**
   * @throws Pausable
   *
   */
  protected void main_loop() throws Exception, Pausable {

    /** out is used locally later, but we allocate it once and for all. */
    List<ByteBuffer> out = new ArrayList<ByteBuffer>();
    EObject msg;

    next_message: while (true) {

      /** if the driver has a registered timeout ... handle that */
      if (abs_timeout == 0) {
        msg = mbox.get();
      } else {
        msg = null;
        long timeout = abs_timeout - System.currentTimeMillis();
        if (timeout > 0) {
          msg = mbox.get(timeout);
        }
        if (msg == null) {
          abs_timeout = 0;
          this.instance.timeout();
          continue next_message;
        }
      }

      ETuple2 t2;
      EPortControl ctrl;
      ETuple3 t3;
      ETuple4 t4;
      ETuple tup;
      if ((t2 = ETuple2.cast(msg)) != null) {

        EObject sender = t2.elem1;

        ETuple2 cmd;
        if ((cmd = ETuple2.cast(t2.elem2)) != null) {
          // cmd must be one of
          // {command, iodata()}
          // {connect, PID}

          if (cmd.elem1 == EPort.am_command) {
            if (cmd.elem2.collectIOList(out)) {
              EHandle caller = sender.testHandle();
             
              if (caller == null) {
                log.warning("*** sender is null? "+sender);
              }
             
              if (out.size() == 0) {
                instance.outputv(caller, ERT.EMPTY_BYTEBUFFER_ARR);
              } else {
                instance.outputv(caller, out.toArray(new ByteBuffer[out
                    .size()]));
              }
              // if collectIOList fails, do the port task die?
              // and how?
            }
           
            out.clear();

            continue next_message;

          } else if (cmd.elem1 == EPort.am_connect) {
            EPID new_owner;
            if ((new_owner = cmd.elem2.testPID()) == null)
              break;

            EPID old_owner = this.owner;
            this.owner = new_owner;

            old_owner.send(this.port, ETuple.make(this.self_handle(),
                    EPort.am_connected));

            continue next_message;

          }

        } else if (t2.elem2 == am_close) {
          this.reply_closed_to = t2.elem1.testPID();
          // will call instance.stop()
          return;
        }

      } else if ((ctrl = msg.testPortControl()) != null) {

        // port control messages are simply run
        ctrl.execute();
        continue next_message;

      } else if ((t3 = ETuple3.cast(msg)) != null) {

        // {'EXIT', From, Reason} comes in this way
        if (t3.elem1 == ERT.am_EXIT) {
          // close is handled by exception handling code
          return;
        }
      } else if ((tup = msg.testTuple()) != null && tup.arity() == 5) {
        // {'DOWN', ref, process, pid, reason}
        if (tup.elm(1) == ERT.am_DOWN) {
          ERef ref = tup.elm(2).testReference();
          instance.processExit(ref);
        }

      }

      break;
    }

    throw new ErlangError(ERT.am_badsig, msg);
  }

  /**
   * implementation of port_control
   * @param caller
   *
   * @param op
   * @param cmd2
   * @throws Pausable
   */
  public EObject control(EProc caller, int op, ByteBuffer cmd2) throws Pausable {

    if (pstate == STATE.RUNNING || pstate == STATE.INIT) {
      // ok
    } else {
      log.warning("port "+this.self_handle()+" in state: "+pstate);
      throw ERT.badarg();
    }

    if (log.isLoggable(Level.FINE))
      log.fine("ctrl: cmd="+op+"; arg="+EBinary.make(cmd2));
   
    while (mbox.hasMessage()) {
      Task.yield();
    }   
   
    long old_to = abs_timeout;
    ByteBuffer bb = instance.control(caller.self_handle(), op, cmd2);
    long new_to = abs_timeout;
   
    if (old_to != new_to) {
      mbox.put(new EPortControl() {
        @Override
        public void execute() throws Pausable, IOException {
          // do nothing, just trigger main loop
        }
      });
    }
   
    if (bb == null || bb.position() == 0) {
        return ERT.NIL;

    } else {

      bb.flip();
      return EString.make(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining());
    }
  }

  /**
   * @param out
   * @return
   */
  public static ByteBuffer flatten(ByteBuffer[] out) {
    if (out.length == 0) {
      return ERT.EMPTY_BYTEBUFFER;
    } else if (out.length == 1) {
      return out[0];
    } else {
      long size = 0;
      for (int i = 0; i < out.length; i++) {
        size += out[i].limit();
      }
      if (size > Integer.MAX_VALUE)
        throw new IllegalArgumentException("buffer too large to flatten "+size);
     
      ByteBuffer res = ByteBuffer.allocate((int)size);
      for (int i = 0; i < out.length; i++) {
        res.put(out[i]);
      }
      res.flip();
      return res;
    }
  }

  /**
   * @param op
   * @param data
   * @return
   * @throws Pausable
   */
  public EObject call(EProc caller, int op, EObject data) throws Pausable {
    if (pstate != STATE.RUNNING) {
      throw ERT.badarg();
    }

    EObject result = instance.call(caller.self_handle(), op, data);

    if (result == null) {
      return ERT.NIL;
    } else {
      return result;
    }
  }

  /**
   * erlang:port_command uses this, since error handling happens in the BIF
   * @param caller TODO
   * @param out
   *
   * @return
   * @throws Pausable
   */
  public void command(final EHandle caller, final ByteBuffer[] out) throws Pausable {
    mbox.put(new EPortControl() {
      @Override
      public void execute() throws Pausable, IOException {
        instance.outputv(caller, out);
      }
    });
  }
 
  public void close() throws Pausable {
    final Throwable t = new Throwable();
    mbox.put(new EPortControl() {
     
      @Override
      public void execute() throws Exception, Pausable {
        throw new ErlangExitSignal(am_normal, t);
      }
     
    });
  }

  /** our owner died, do something! */
  @Override
  protected void process_incoming_exit(EHandle from, EObject reason, boolean exitToSender) throws Pausable
       {
   
    // TODO: do special things for reason=kill ?
   
    // System.err.println("sending exit msg to self "+this);
    mbox.put(ETuple.make(ERT.am_EXIT, from, reason));
  }

  /* (non-Javadoc)
   * @see erjang.ETask#send_exit_to_all_linked(erjang.EObject)
   */
  @Override
  protected void do_proc_termination(EObject result) throws Pausable {
   
    if (this.reply_closed_to != null) {
      this.reply_closed_to.send(self_handle(), ETuple.make(self_handle(), am_closed));
    }
   
    super.do_proc_termination(result);
    if (result != am_normal) {
      owner.send(self_handle(), ETuple.make(ERT.am_EXIT, self_handle(), result));
    }
    //this.port.done();
    all_ports.remove(this.id);
   
    EDriverControl i = instance;
    if (i != null)
      i.stop(result);
   
  }
 
  /*
   * (non-Javadoc)
   *
   * @see erjang.driver.IOHandler#ready(java.nio.channels.SelectableChannel,
   * int)
   */
  @Override
  public void ready(final SelectableChannel ch, final int readyOps) {
    mbox.putb(new EPortControl() {
      @Override
      public void execute() throws Pausable {
        if ((readyOps & EDriverInstance.ERL_DRV_READ) == EDriverInstance.ERL_DRV_READ) {
          instance.readyInput(ch);
        }
        if ((readyOps & EDriverInstance.ERL_DRV_WRITE) == EDriverInstance.ERL_DRV_WRITE) {
          instance.readyOutput(ch);
        }
        if ((readyOps & EDriverInstance.ERL_DRV_CONNECT) == EDriverInstance.ERL_DRV_CONNECT) {
          instance.readyConnect(ch);
        }
        if ((readyOps & EDriverInstance.ERL_DRV_ACCEPT) == EDriverInstance.ERL_DRV_ACCEPT) {
          instance.readyAccept(ch);
        }
      }
    });
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * erjang.driver.IOHandler#released(java.nio.channels.SelectableChannel)
   */
  @Override
  public void released(final SelectableChannel ch) {
    mbox.putb(new EPortControl() {
      @Override
      public void execute() throws Pausable {
        instance.stopSelect(ch);
      }
    });
  }

  /*
   * (non-Javadoc)
   *
   * @see
   * erjang.driver.IOHandler#exception(java.nio.channels.SelectableChannel,
   * java.io.IOException)
   */
  @Override
  public void exception(SelectableChannel ch, IOException e) {
    // TODO Auto-generated method stub

  }

  /**
   * @param howlong
   */
  public void set_timer(long howlong) {
    this.abs_timeout = System.currentTimeMillis() + howlong;
  }
 
  public long read_timer() {
    return this.abs_timeout - System.currentTimeMillis();
  }
 
  /**
   * @param port2
   */
  public void cancel_timer(EPort port2) {
    assert port2 == this.port : "can only cancel timer on self" ;
    this.abs_timeout = 0;
  }


  /**
   * @param job
   */
  public void async_done(final EAsync job) {

    mbox.putb(new EPortControl() {
      @Override
      public void execute() throws Pausable {
        instance.readyAsync(job);
      }
    });
    }

  /**
   * @param out
   * @throws Pausable
   */
  public void output_from_driver(EObject out) throws Pausable {
    output_term_from_driver(new ETuple2(port, new ETuple2(am_data, out)));
  }

  public void output_from_driver_b(EObject out) {
    output_term_from_driver_b(new ETuple2(port, new ETuple2(am_data, out)));
  }

  public void output_term_from_driver(EObject out) throws Pausable {
    if (log.isLoggable(Level.FINE)) log.fine(""+owner+" ! "+out);
    owner.send(port, out);
  }

  public void output_term_from_driver_b(EObject out) {
    if (log.isLoggable(Level.FINE)) log.fine(""+owner+" ! "+out);
    owner.sendb(out);
  }

  /**
   * @throws Pausable
   *
   */
  public void eof_from_driver_b() {
    output_term_from_driver_b(new ETuple2(port, am_eof));
  }

  public void eof_from_driver() throws Pausable {
    output_term_from_driver(new ETuple2(port, am_eof));
  }

  public void exit_status_from_driver(int code) throws Pausable {
    output_term_from_driver(new ETuple2(port, new ETuple2(am_exit_status, ERT.box(code))));
  }

  public void exit_status_from_driver_b(int code) {
    output_term_from_driver_b(new ETuple2(port, new ETuple2(am_exit_status, ERT.box(code))));
  }

  public static ESeq all_ports() {
   
    ESeq res = ERT.NIL;
    for (EDriverTask dt : all_ports.values()) {
      if (dt.isDone()) continue;
      res = res.cons(dt.self_handle());
    }

    return res;
  }

  public EObject port_info(EAtom spec) {
   
    if (spec == am_connected) {
      return new ETuple2(am_connected, owner);
    }
   
    if (spec == am_name) {
      return new ETuple2(am_name, getName());
    }
   
    if (spec == EProc.am_links) {
      return new ETuple2(EProc.am_links, links());
    }
   
    throw new NotImplemented("port_info(" + spec + ")");
  
  }

  public void exit(final EObject reason) {
    mbox.putb(new EPortControl() {
      @Override
      public void execute() throws Pausable, IOException {
        throw new ErlangExit(reason);
      }
    });
  }

  public EPeer node() {
    return this.peer;
  }

  public void node(EPeer peer) {
    this.peer = peer;
    this.status |= ERTS_PORT_SFLG_DISTRIBUTION;
  }

  /** magic direct call ! */
  public void outputv(EHandle sender, ByteBuffer[] ev) throws IOException, Pausable {
    this.command(sender, ev);
  }

  public boolean send_binary_data() {
    return send_binary_data;
  }


}
TOP

Related Classes of erjang.driver.EDriverTask

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.