/**
* 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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import kilim.Pausable;
import erjang.m.java.JavaObject;
/**
* An erlang process
*/
public final class EProc extends ETask<EInternalPID> {
static Logger log = Logger.getLogger("erjang.proc");
/*==================== Constants =============================*/
public static final EObject TAIL_MARKER = null;
public static final EAtom am_trap_exit = EAtom.intern("trap_exit");
public static final EAtom am_sensitive = EAtom.intern("sensitive");
public static final EAtom am_messages = EAtom.intern("messages");
public static final EAtom am_message_queue_len = EAtom.intern("message_queue_len");
public static final EAtom am_dictionary = EAtom.intern("dictionary");
public static final EAtom am_group_leader = EAtom.intern("group_leader");
public static final EAtom am_links = EAtom.intern("links");
public static final EAtom am_heap_size = EAtom.intern("heap_size");
public static final EAtom am_stack_size = EAtom.intern("stack_size");
public static final EAtom am_reductions = EAtom.intern("reductions");
public static final EAtom am_initial_call = EAtom.intern("initial_call");
public static final EAtom am_current_function = EAtom.intern("current_function");
public static final EAtom am_priority = EAtom.intern("priority");
public static final EAtom am_memory = EAtom.intern("memory");
public static final EAtom am_monitor_nodes = EAtom.intern("monitor_nodes");
public static final EAtom am_registered_name = EAtom.intern("registered_name");
public static final EAtom am_error_handler = EAtom.intern("error_handler");
public static final EAtom am_undefined_function = EAtom.intern("undefined_function");
public static final EAtom am_max = EAtom.intern("max");
public static final EAtom am_normal = EAtom.intern("normal");
public static final EAtom am_low = EAtom.intern("low");
public static final EAtom am_high = EAtom.intern("high");
static final EObject am_kill = EAtom.intern("kill");
static final EObject am_killed = EAtom.intern("killed");
private static final EObject am_status = EAtom.intern("status");
private static final EObject am_waiting = EAtom.intern("waiting");
private static final EObject am_running = EAtom.intern("running");
private static final EObject am_runnable = EAtom.intern("runnable");
private static final EAtom am_error_logger = EAtom.intern("error_logger");
private static final EAtom am_info_report = EAtom.intern("info_report");
private static final EObject am_noproc = EAtom.intern("noproc");
private static final EAtom[] priorities = new EAtom[] {
am_max,
am_high,
am_normal,
am_low
};
private static final ExitHook[] NO_HOOKS = new ExitHook[0];
/*==================== Global state ====================================*/
public static ConcurrentHashMap<Integer,EProc> all_tasks
= new ConcurrentHashMap<Integer,EProc> ();
/*==================== Process state ====================================*/
/*========= Immutable state =========================*/
private final EInternalPID self;
// TODO: Make final (and set in constructor rather than in setTarget)
private EAtom spawn_mod;
private EAtom spawn_fun;
private int spawn_args;
/*========= Mutable Erlang-dictated state ===========*/
private EPID group_leader;
public ErlangException last_exception; // TODO: Make private
private final Map<EObject, EObject> pdict = new HashMap<EObject, EObject>();
private EAtom trap_exit = ERT.FALSE;
private EAtom sensitive = ERT.FALSE;
private EAtom error_handler = am_error_handler;
private int priority = 2;
/*========= Mutable implementation-related state ====*/
// TODO - make private. (Only accessed from ERT.) Add accessors/operations.
// Message receive group:
/** Message box index - used for selective receive */
public int midx = 0;
public long timeout_start;
public boolean in_receive;
/** Used for implementing tail calls. */
public EFun tail;
public EObject arg0, arg1, arg2, arg3, arg4, arg5,
arg6, arg7, arg8, arg9, arg10, arg11,
arg12, arg13, arg14, arg15, arg16, arg17;
// For interpreter use:
public EObject[] stack = new EObject[10];
public int sp = 0;
public EObject[] regs;
public EDouble[] fregs = new EDouble[16];
public EModuleManager.FunctionInfo undefined_function
= EModuleManager.get_module_info(error_handler).
get_function_info(new FunID(error_handler, am_undefined_function, 3));
/** For process clean-up. Protected by exit-action mutator lock. */
private final List<ExitHook> exit_hooks = new ArrayList<ExitHook>();
ERT.TraceFlags trace_flags;
/*==================== Construction =============================*/
public EProc(EPID group_leader, EAtom m, EAtom f, Object[] a) {
self = new EInternalPID(this);
// if no group leader is given, we're our own group leader
this.group_leader = group_leader == null ? self : group_leader;
setTarget(m, f, a);
all_tasks.put(key(), this);
}
/**
* @param m
* @param f
* @param array
*/
public EProc(EPID group_leader, EAtom m, EAtom f, ESeq a) {
self = new EInternalPID(this);
// if no group leader is given, we're our own group leader
this.group_leader = group_leader == null ? self : group_leader;
setTarget(m, f, a);
all_tasks.put(key(), this);
}
protected void setTarget(EAtom m, EAtom f, Object[] args) {
// wrap any non-EObject argument in JavaObject
EObject[] eargs = new EObject[args.length];
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof EObject) {
EObject earg = (EObject) arg;
eargs[i] = earg;
}
else {
// wrap in JavaObject
eargs[i] = JavaObject.box(this, arg);
}
}
setTarget(m, f, ESeq.fromArray(eargs));
}
protected void setTarget(EAtom m, EAtom f, ESeq a) {
this.spawn_mod = m;
this.spawn_fun = f;
this.spawn_args = a.length();
int arity = spawn_args;
EFun target = EModuleManager.resolve(new FunID(m,f,arity));
if (target == null) {
throw new ErlangUndefined(m, f, new ESmall(arity));
}
this.tail = target;
a = a.reverse();
switch (arity) {
default:
throw new NotImplemented();
case 7:
this.arg6 = a.head(); a = a.tail();
case 6:
this.arg5 = a.head(); a = a.tail();
case 5:
this.arg4 = a.head(); a = a.tail();
case 4:
this.arg3 = a.head(); a = a.tail();
case 3:
this.arg2 = a.head(); a = a.tail();
case 2:
this.arg1 = a.head(); a = a.tail();
case 1:
this.arg0 = a.head(); a = a.tail();
case 0:
}
}
/*==================== Public interface - runtime ===============*/
public EInternalPID self_handle() {
return self;
}
public ErlangException getLastException() {
return last_exception;
}
/*==================== Public interface - Erlang operations =====*/
/**
* @return
*/
public EPID group_leader() {
return group_leader;
}
/**
* Only called from ELocalPID
*
* @param group_leader
*/
void set_group_leader(EPID group_leader) {
this.group_leader = group_leader;
}
/*--------- Process dictionary --------------------------*/
public EObject put(EObject key, EObject value) {
EObject res = pdict.put(key, value);
if (res == null)
return ERT.am_undefined;
return res;
}
public EObject get(EObject key) {
EObject res = pdict.get(key);
return (res == null) ? ERT.am_undefined : res;
}
/**
* @return list of the process dictionary
*/
public ESeq get() {
ESeq res = ERT.NIL;
for (Map.Entry<EObject, EObject> ent : pdict.entrySet()) {
res = res.cons(ETuple.make(ent.getKey(), ent.getValue()));
}
return res;
}
/**
* @param key
* @return
*/
public EObject erase(EObject key) {
EObject res = pdict.remove(key);
if (res == null)
res = ERT.am_undefined;
return res;
}
/**
* @return
*/
public EObject erase() {
EObject res = get();
pdict.clear();
return res;
}
/*--------- Process flags --------------------------*/
/**
* @param testAtom
* @param a2
* @return
* @throws Pausable
*/
public EObject process_flag(EAtom flag, EObject value) {
if (flag == am_trap_exit) {
EAtom old = this.trap_exit;
trap_exit = value.testBoolean();
return ERT.box(old==ERT.TRUE);
}
if (flag == am_priority) {
EAtom old = priorities[getPriority()];
for (int i = 0; i < priorities.length; i++) {
if (value == priorities[i]) {
setPriority(i);
return old;
}
}
throw ERT.badarg(flag, value);
}
if (flag == am_error_handler) {
EAtom val;
if ((val = value.testAtom()) != null) {
EAtom old = this.error_handler;
this.error_handler = val;
FunID uf = new FunID(error_handler, am_undefined_function, 3);
undefined_function = EModuleManager.get_module_info(error_handler).get_function_info(uf);
return old;
} else {
throw ERT.badarg(flag, value);
}
}
if (flag == am_monitor_nodes) {
if (!value.isBoolean()) throw ERT.badarg(flag, value);
boolean activate = value==ERT.TRUE;
Boolean old = EAbstractNode.monitor_nodes(self_handle(), activate, ERT.NIL);
if (old == null) throw ERT.badarg(flag, value);
return ERT.box(old.booleanValue());
}
if (flag == am_trap_exit) {
EAtom old = this.trap_exit;
trap_exit = value.testBoolean();
return ERT.box(old==ERT.TRUE);
}
if (flag == am_sensitive) {
EAtom old = this.sensitive;
sensitive = value.testBoolean();
return ERT.box(old==ERT.TRUE);
}
ETuple2 tup;
if ((tup = ETuple2.cast(flag)) != null && tup.elem1==am_monitor_nodes) {
ESeq opts = tup.elem2.testSeq();
if (opts == null) throw ERT.badarg(flag, value);
if (!value.isBoolean()) throw ERT.badarg(flag, value);
boolean activate = value.testBoolean()==ERT.TRUE;
Boolean old = EAbstractNode.monitor_nodes(self_handle(), activate, opts);
if (old == null) throw ERT.badarg(flag, value);
return ERT.box(old.booleanValue());
}
throw new NotImplemented("process_flag flag="+flag+", value="+value);
}
private int getPriority() {
return this.priority;
}
private void setPriority(int i) {
this.priority = i;
}
/*--------- Process info --------------------------*/
public EObject process_info() {
ESeq res = ERT.NIL;
res = res.cons(process_info(am_trap_exit));
res = res.cons(process_info(am_messages));
res = res.cons(process_info(am_message_queue_len));
res = res.cons(process_info(am_dictionary));
res = res.cons(process_info(am_group_leader));
res = res.cons(process_info(am_links));
res = res.cons(process_info(am_heap_size));
res = res.cons(process_info(am_initial_call));
res = res.cons(process_info(am_reductions));
EObject reg_name = self_handle().name;
if (reg_name != ERT.am_undefined)
res = res.cons(new ETuple2(am_registered_name, reg_name));
if (res == ERT.NIL) return ERT.am_undefined;
return res;
}
/**
* @param spec
* @return
*/
public EObject process_info(EObject spec) {
if (spec == am_registered_name) {
return self_handle().name == ERT.am_undefined
? ERT.NIL
: new ETuple2(am_registered_name, self_handle().name);
} else if (spec == am_trap_exit) {
return new ETuple2(am_trap_exit, trap_exit);
} else if (spec == am_message_queue_len) {
return new ETuple2(am_message_queue_len,
new ESmall(mbox.size()));
} else if (spec == am_messages) {
ESeq messages = EList.make((Object[])mbox.messages());
return new ETuple2(am_messages, messages);
} else if (spec == am_dictionary) {
return new ETuple2(am_dictionary, get());
} else if (spec == am_group_leader) {
return new ETuple2(am_group_leader, group_leader);
} else if (spec == am_links) {
ESeq links = links();
return new ETuple2(am_links, links);
} else if (spec == am_status) {
if (this.running) {
return new ETuple2(am_status, am_running);
} else if (this.pauseReason != null) {
return new ETuple2(am_status, am_waiting);
} else {
return new ETuple2(am_status, am_runnable);
}
} else if (spec == am_heap_size) {
return new ETuple2(am_heap_size,
ERT.box(Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory()));
} else if (spec == am_stack_size) {
// TODO: Maybe use HotSpotDiagnosticMXBean ThreadStackSize property?
return new ETuple2(am_stack_size,
ERT.box(0));
} else if (spec == am_reductions) {
return new ETuple2(am_reductions, ERT.box(this.get_reductions()));
} else if (spec == am_initial_call) {
return new ETuple2(am_initial_call,
ETuple.make(spawn_mod, spawn_fun, ERT.box(spawn_args)));
} else if (spec == am_current_function) {
/** TODO: fix this so we return something meaningful... */
return new ETuple2(am_current_function,
ETuple.make(spawn_mod, spawn_fun, ERT.box(spawn_args)));
} else if (spec == am_memory) {
return new ETuple2(am_memory, ERT.box(50000));
} else if (spec == am_error_handler) {
return new ETuple2(am_error_handler, am_error_handler);
} else if (spec == am_priority) {
return new ETuple2(am_priority, am_normal);
} else {
log.warning("NotImplemented: process_info("+spec+")");
throw new NotImplemented("process_info("+spec+")");
}
}
/*==================== Internals ================================*/
private int key() {
int key = (self.serial() << 15) | (self.id() & 0x7fff);
return key;
}
/*--------- Process lifecycle --------------------------*/
public boolean is_alive_dirtyread() {
int pstate = get_state_dirtyread();
return pstate == STATE.INIT.ordinal() || pstate == STATE.RUNNING.ordinal();
}
protected void link_failure(EHandle h) throws Pausable {
if (trap_exit == ERT.TRUE || h.testLocalHandle()==null) {
send_exit(h, am_noproc, false);
} else {
throw new ErlangError(am_noproc);
}
}
@Override
protected void do_proc_termination(EObject result) throws Pausable {
// Precondition: pstate is DONE, exit-action mutator count is zero.
final ExitHook[] hooks;
if (exit_hooks == null || exit_hooks.isEmpty()) {
hooks = NO_HOOKS;
} else {
hooks = exit_hooks.toArray(new ExitHook[exit_hooks.size()]);
}
for (int i = 0; i < hooks.length; i++) {
hooks[i].on_exit(self);
}
super.do_proc_termination(result);
self.done();
all_tasks.remove(this.key());
}
protected void process_incoming_exit(EHandle from, EObject reason, boolean is_erlang_exit2) throws Pausable
{
int pstate = get_state_dirtyread();
if (exit_reason != null || pstate == STATE.DONE.ordinal()) {
if (log.isLoggable(Level.FINE)) {
log.fine("Ignoring incoming exit reason="+reason+", as we're already exiting reason="+exit_reason);
}
return;
}
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "incoming exit to "+this+" from "+from+" reason="+reason+"; is_exit2="+is_erlang_exit2);
}
if (is_erlang_exit2) {
EObject exit_reason_to_set;
if (reason == am_kill) {
exit_reason_to_set = am_killed;
} else if (trap_exit == ERT.TRUE) {
// we're trapping exits, so we in stead send an {'EXIT', from,
// reason} to self
ETuple msg = ETuple.make(ERT.am_EXIT, from, reason);
// System.err.println("kill message to self: "+msg);
mbox.put(msg);
return;
} else {
exit_reason_to_set = reason;
}
this.exit_reason = exit_reason_to_set;
this.killer = new Throwable("stack of process calling exit");
this.resume();
return;
}
if (from == self_handle()) {
return;
}
if (trap_exit == ERT.TRUE) {
// we're trapping exits, so we in stead send an {'EXIT', from,
// reason} to self
ETuple msg = ETuple.make(ERT.am_EXIT, from, reason);
// System.err.println("kill message to self: "+msg);
mbox.put(msg);
} else if (reason == am_kill) {
this.exit_reason = am_killed;
this.killer = new Throwable();
this.resume();
} else if (reason != am_normal) {
// System.err.println("kill signal: " +reason + " from "+from);
// try to kill this thread
this.exit_reason = reason;
this.killer = new Throwable();
this.resume();
}
}
@Override
public void execute() throws Pausable {
Throwable[] death = new Throwable[1];
EObject result = null;
try {
execute0(death, result);
} catch (ErlangHalt e) {
return;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
System.err.println("uncaught top-level exception");
e.printStackTrace(System.err);
}
}
private void execute0(Throwable[] death, EObject result) throws ErlangHalt,
ThreadDeath, Pausable {
try {
result = execute1();
} catch (NotImplemented e) {
log.log(Level.SEVERE, "[fail] exiting "+self_handle(), e);
result = e.reason();
death[0] = e;
} catch (ErlangException e) {
log.log(Level.FINE, "[erl] exiting "+self_handle(), e);
last_exception = e;
result = e.reason();
death[0] = e;
} catch (ErlangExitSignal e) {
log.log(Level.FINE, "[signal] exiting "+self_handle(), e);
result = e.reason();
death[0] = e;
} catch (ErlangHalt e) {
throw e;
} catch (Throwable e) {
log.log(Level.SEVERE, "[java] exiting "+self_handle()+" with: ", e);
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);
death[0] = e;
} finally {
set_state_to_done_and_wait_for_stability();
}
if (result != am_normal && monitors.isEmpty() && has_no_links() && !(death[0] instanceof ErlangExitSignal)) {
EFun fun = EModuleManager.resolve(new FunID(am_error_logger, am_info_report, 1));
String msg = "Process " +self_handle()+ " exited abnormally without links/monitors\n"
+ "exit reason was: " + result + "\n"
+ (death[0] == null ? "" : ERT.describe_exception(death[0]));
try {
fun.invoke(this, new EObject[] { EString.fromString(msg) });
} catch (ErlangHalt e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
System.err.println(msg);
e.printStackTrace();
// ignore //
}
}
// System.err.println("task "+this+" exited with "+result);
do_proc_termination(result);
}
private EObject execute1() throws Pausable {
EObject result;
this.check_exit();
set_state(STATE.RUNNING);
hibernate_loop:
while (true) {
try {
while (this.tail.go(this) == TAIL_MARKER) {
/* skip */
}
break hibernate_loop;
} catch (ErjangHibernateException e) {
// noop, live = true //
}
mbox_wait();
}
result = am_normal;
return result;
}
public boolean add_exit_hook(ExitHook hook) {
int ps = exit_action_mutator_lock();
try {
if (ps == STATE.DONE.ordinal()) return false; // Too late.
exit_hooks.add(hook);
return true;
} finally {
exit_action_mutator_unlock();
}
}
public boolean remove_exit_hook(ExitHook hook) {
int ps = exit_action_mutator_lock();
try {
if (ps == STATE.DONE.ordinal()) return false; // Too late.
exit_hooks.remove(hook);
return true;
} finally {
exit_action_mutator_unlock();
}
}
/*--------- Convenience --------------------------------*/
/* (non-Javadoc)
* @see kilim.Task#toString()
*/
@Override
public String toString() {
return self.toString() + super.toString() +
"::" + spawn_mod + ":" + spawn_fun + "/" + spawn_args;
}
/*==================== Globals - public interface ===============*/
public static ESeq processes() {
ESeq res = ERT.NIL;
for (EProc proc : all_tasks.values()) {
if (proc.is_alive_dirtyread()) {
res = res.cons(proc.self_handle());
}
}
return res;
}
public static int process_count() {
int count = 0;
for (EProc proc : all_tasks.values()) {
if (proc.is_alive_dirtyread()) {
count += 1;
}
}
return count;
}
public static EInternalPID find(int id, int serial) {
int key = (serial << 15) | (id & 0x7fff);
EProc task = all_tasks.get(key);
if (task != null) return task.self_handle();
return null;
}
/*==================== Globals - internal ===============*/
static {
if (ErjangConfig.getBoolean("erjang.dump_on_exit"))
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
log.warning("===== LIVE TASKS UPON EXIT");
for (EProc task : all_tasks.values()) {
log.warning("==" + task);
log.warning(task.fiber.toString());
}
log.warning("=====");
}
});
}
public ERT.TraceFlags get_trace_flags() {
if (trace_flags == null)
return ERT.global_trace_flags;
return trace_flags;
}
public ERT.TraceFlags get_own_trace_flags() {
if (trace_flags == null)
trace_flags = ERT.global_trace_flags.clone();
return trace_flags;
}
}