/** * 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.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; import kilim.Mailbox; import kilim.Pausable; import com.trifork.clj_ds.IPersistentSet; import com.trifork.clj_ds.PersistentHashSet; import kilim.Task; /** * An ETask is what is common for processes and open ports */ public abstract class ETask<H extends EHandle> extends kilim.Task { private static Logger log = Logger.getLogger("erjang.proc"); /*==================== Constants =============================*/ public static final EAtom am_normal = EAtom.intern("normal"); protected static final EAtom am_java_exception = EAtom .intern("java_exception"); private static final EAtom am_DOWN = EAtom.intern("DOWN"); private static final EAtom am_process = EAtom.intern("process"); private static final int MAX_MAILBOX_SIZE = 1000; /*==================== Typedefs ==============================*/ public enum STATE { INIT, RUNNING, EXITING, DONE }; private static final int MUTATOR_COUNT_ONE = (1<<4); private static final int PSTATE_MASK = MUTATOR_COUNT_ONE - 1; private static final int MUTATOR_COUNT_MASK = ~PSTATE_MASK; /*==================== Process state ====================================*/ /** Process lifecycle: * * INIT -----> RUNNING --------------------> DONE * \ \ / * v v / * INIT w/ RUNNING w/ / * exit_reason exit_reason / * \ \ / * -------------------> EXITING -- * * For reliability (trigger-once guarantee) of exit-actions, the following invariant is enforced: * Once the state is DONE, the set of exit-actions (links, monitors, and exit hooks) MUST NOT * change (as there would be e.g. add/perform race conditions in that case), * This is accompliced by keeping the pstate in an AtomicInteger together with the number of current exit-action * mutators, in such a manner that the (logical) transition to DONE can only occur when there are zero mutators. * (Value: <code>(n_mutators << 4) | (STATE value)</code>, where n_mutators is the number of actual mutators * plus the number of wannabe mutators. * * The critical invariant here is: * <em>When the state value part is DONE, the number of actual * mutators will not increase</em>. * (The number of wannabe mutators may increase, but immediate decreases * once the DONE-ness of the state is discovered; the distinction between * wannabe mutators and non-mutators is really a performance question, * allowing use of getAndAdd().) * * Changes to links/monitors/exit hooks must honour this updating pattern. * TODO: Follow through on this. */ private AtomicInteger pstate_and_mutator_count = new AtomicInteger(STATE.INIT.ordinal()); private AtomicReference<PersistentHashSet<EHandle>> linksref = new AtomicReference<PersistentHashSet<EHandle>>(PersistentHashSet.EMPTY); protected Map<ERef,EHandle> monitors = new ConcurrentHashMap<ERef, EHandle>(); // this is not synchronized, as we only mess with it from this proc // TODO That is not accurate, cf. do_proc_termination->send_monitor_exit call. --ESS private Map<ERef,ETuple2> is_monitoring = new HashMap<ERef, ETuple2>(); protected final Mailbox<EObject> mbox = new Mailbox<EObject>(10, MAX_MAILBOX_SIZE); /** Reduction counter. */ private int reds; /** Exit signal and reason - * null when task is alive or terminated normally; non-null when task is terminated/terminating abnormally. * Killer and exit_reason are always set together. * */ protected volatile EObject exit_reason; protected Throwable killer; private Mailbox<EObject> print_trace; private Runnable run_on_stack; /*==================== Instance methods ====================================*/ /** * @return */ public abstract H self_handle(); /*--------- Link related ----------------------------*/ public void unlink(EHandle other) throws Pausable { unlink_oneway(other); other.unlink_oneway(self_handle()); } public boolean unlink_oneway(EHandle handle) { int ps = exit_action_mutator_lock(); try { if (ps == STATE.DONE.ordinal()) return false; // Too late. PersistentHashSet old, links; try { do { old = linksref.get(); links = (PersistentHashSet) old.disjoin(handle); } while (!linksref.weakCompareAndSet(old, links)); return true; } catch (Exception e) { throw new RuntimeException(e); } } finally { exit_action_mutator_unlock(); } } protected boolean has_no_links() { IPersistentSet<EHandle> links = linksref.get(); return links.count() == 0; } /** * @param task * @throws Pausable */ public void link_to(ETask<?> task) throws Pausable { link_to(task.self_handle()); } public void link_to(EHandle handle) throws Pausable { if (link_oneway(handle)) { handle.link_oneway((EHandle) self_handle()); // Ignore failure if linker has received SIG_EXIT } else { link_failure(handle); } } public boolean link_oneway(EHandle h) { int ps = exit_action_mutator_lock(); try { if (ps == STATE.DONE.ordinal()) return false; // Too late. PersistentHashSet old, links; try { do { old = linksref.get(); links = (PersistentHashSet) old.cons(h); } while (!linksref.weakCompareAndSet(old, links)); return true; } catch (Exception e) { throw new RuntimeException(e); } } finally { exit_action_mutator_unlock(); } } public ESeq links() { ESeq res = ERT.NIL; for (EHandle h : linksref.get()) { res = res.cons(h); } return res; } /** * @param h * @throws Pausable * @throws Pausable * @throws Pausable * @throws Pausable */ protected void link_failure(EHandle h) throws Pausable { throw new ErlangError(ERT.am_noproc); } /*--------- Monitor related -------------------------*/ /** * @param object * @param ref TODO * @param selfHandle * @return * @throws Pausable */ public boolean monitor(EHandle observed, EObject object, ERef ref) throws Pausable { if (!observed.add_monitor(self_handle(), ref)) { // System.err.println("unable to add monitor to self="+self_handle()+" pid="+observed+" ref="+ref); return false; } this.is_monitoring.put(ref, new ETuple2(observed, object)); return true; } public boolean monitor(EObject object, ERef ref) throws Pausable { this.is_monitoring.put(ref, new ETuple2(object, object)); return true; } /** * @param r * @return * @throws Pausable */ public EObject demonitor(ERef r) throws Pausable { ETuple2 pair = is_monitoring.remove(r); if (pair == null) { return null; } return pair.elem1; } /** * @param ref TODO * @param self2 * @return */ public boolean add_monitor(EHandle target, ERef ref) { int ps = exit_action_mutator_lock(); try { if (ps == STATE.DONE.ordinal()) return false; // Too late. monitors.put(ref, target); return true; } finally { exit_action_mutator_unlock(); } } /** * @param r */ public boolean remove_monitor(ERef r, boolean flush) { int ps = exit_action_mutator_lock(); try { if (ps == STATE.DONE.ordinal()) return false; // Too late. EHandle val = monitors.remove(r); } finally { exit_action_mutator_unlock(); } if (flush) { // TODO: do we need to represent flush somehow? } return true; //TODO } public EHandle get_monitored_process(ERef monitor) { ETuple2 tup = is_monitoring.get(monitor); if (tup == null) return null; return tup.elem1.testHandle(); } public EObject get_monitored_object(ERef monitor) { ETuple2 tup = is_monitoring.get(monitor); return tup.elem2; } /*--------- Process state ---------------------------*/ /** * @return */ public boolean exists() { int ps = get_state_dirtyread(); return ps == STATE.INIT.ordinal() || ps == STATE.RUNNING.ordinal(); } /*--------- Termination related ---------------------*/ /* (non-Javadoc) * @see kilim.Task#checkKill() */ @Override public void checkKill() { check_exit(); } /** * will check if this process have received an exit signal (and we're not * trapping) */ public final void check_exit() { do_check_exit(); if (print_trace != null) { Mailbox<EObject> mb = print_trace; if (mb != null) { H handle = self_handle(); ESeq trace = new ErlangError(ERT.am_ok).getTrace(); mb.putb(new ETuple2(handle, trace)); } print_trace = null; } if (run_on_stack != null) { Runnable mb = run_on_stack; run_on_stack = null; if (mb != null) { try { mb.run(); } catch (Throwable e) { e.printStackTrace(); } } } } /** because the above is called a bazillion times, we split this out to make it easier to inline */ private void do_check_exit() throws ErlangExitSignal { if (exit_reason != null) { set_state_if_later(STATE.EXITING); // The state might be done, in case // this is called from a subsequent yield // Build and throw exception EObject reason = exit_reason; exit_reason = null; //System.err.println("---- [" + killer + "]"); ErlangExitSignal e = new ErlangExitSignal(reason, killer); //e.printStackTrace(); //System.err.println("---- [ ... ]"); throw e; } } @SuppressWarnings("unchecked") protected void do_proc_termination(EObject exit_reason) throws Pausable { // Precondition: pstate is DONE, exit-action mutator count is zero. this.exit_reason = exit_reason; H me = self_handle(); EAtom name = me.name; for (EHandle handle : linksref.get()) { try { handle.exit_signal(me, exit_reason, false); } catch (Error e) { log.severe("EXCEPTION IN EXIT HANDLER"); log.log(Level.FINE, "details: ", e); throw e; } catch (RuntimeException e) { log.severe("EXCEPTION IN EXIT HANDLER"); log.log(Level.FINE, "details: ", e); throw e; } } for (Map.Entry<ERef, EHandle> ent : monitors.entrySet()) { EHandle pid = ent.getValue(); ERef ref = ent.getKey(); pid.send_monitor_exit((EHandle)me, ref, exit_reason); } if (name != ERT.am_undefined && name != null) { ERT.unregister(name); } } public void send_monitor_exit(EHandle from, ERef ref, EObject reason) throws Pausable { ETuple2 pair = is_monitoring.get(ref); if (pair != null) { mbox_send(ETuple.make(am_DOWN, ref, am_process, pair.elem2, reason)); } } /*--------- Mbox related ----------------------------*/ /** * @return */ public Mailbox<EObject> mbox() { return mbox; } /** * @throws Pausable * */ public void mbox_wait() throws Pausable { mbox.untilHasMessage(); } /** * @param longValue */ public boolean mbox_wait(long timeoutMillis) throws Pausable { return mbox.untilHasMessage(timeoutMillis); } /** * @param msg * @throws Pausable */ public void mbox_send(EObject msg) throws Pausable { mbox.put(msg); } /** * @return * @throws Pausable */ public void mbox_remove_one() throws Pausable { mbox.get(); } /** * @param from * @param reason * @param is_erlang_exit2 TODO */ public final void send_exit(EHandle from, EObject reason, boolean is_erlang_exit2) throws Pausable { if (log.isLoggable(Level.FINE)) { log.log (Level.FINE, "exit " + from + " -> " + this + ", reason="+reason, new Throwable("trace")); } // ignore exit signals from myself if (from == self_handle()) { return; } // make sure we don't also send him an exit signal if (!is_erlang_exit2) unlink_oneway(from); int ps = get_state_dirtyread(); if (ps == STATE.DONE.ordinal() || ps == STATE.EXITING.ordinal() || exit_reason != null) { // we have already received one exit signal, ignore // subsequent ones... return; } process_incoming_exit(from, reason, is_erlang_exit2); } protected abstract void process_incoming_exit(EHandle from, EObject reason, boolean is_erlang_exit2) throws Pausable ; /*--------- Reduction count ------------------------*/ public int get_reductions() { return reds;} public void bump_reductions(int amount) throws Pausable { reds += amount; if (reds > 1000) { reds = 0; Task.yield(); } } /*--------- Process state locking -------------------*/ /** Returns the state value. * Must be paired with exit_action_mutator_unlock! */ protected final int exit_action_mutator_lock() { int v = pstate_and_mutator_count.getAndAdd(MUTATOR_COUNT_ONE); return v & PSTATE_MASK; } protected final void exit_action_mutator_unlock() { pstate_and_mutator_count.getAndAdd(-MUTATOR_COUNT_ONE); } protected final void set_state_to_done_and_wait_for_stability() throws Pausable { // Set state to DONE: int state_and_mutator_count = try_set_state(STATE.DONE); // Wait for any exit-action mutator count to drop to zero: int iter_count = 0; while ((state_and_mutator_count & MUTATOR_COUNT_MASK) != 0) { if ((++iter_count) % 16 == 0) Task.yield(); state_and_mutator_count = pstate_and_mutator_count.get(); } // Invariant: state is DONE and there are no exit-action mutators. assert pstate_and_mutator_count.get() == STATE.DONE.ordinal(); } protected final void set_state(STATE new_state) { if (try_set_state(new_state) == -1) { System.err.println("Internal consistency error: bad process state transition to "+new_state+" ("+pstate_and_mutator_count.get()+")"); // But go on regardless... } } protected final void set_state_if_later(STATE new_state) { try_set_state(new_state); // ignore any error result } /** Returns old state and mutator count (packed) - or (-1) if state transition is invalid. */ private final int try_set_state(STATE new_state) { int old_value, new_value; int new_state_value = new_state.ordinal(); do { old_value = pstate_and_mutator_count.get(); int old_state_value = old_value & PSTATE_MASK; if (old_state_value >= new_state_value) return -1; new_value = (old_value & MUTATOR_COUNT_MASK) | new_state_value; } while (!pstate_and_mutator_count.compareAndSet(old_value, new_value)); return old_value; } protected final int get_state_dirtyread() { return pstate_and_mutator_count.get() & PSTATE_MASK; } protected final int get_pstate_for_debug() { return pstate_and_mutator_count.get(); } /*--------- Miscellaneous ---------------------------*/ public void printStackTrace(Mailbox<EObject> info) { this.print_trace = info; this.resume(); } public void runOnStack(Runnable info) { this.run_on_stack = info; this.resume(); } }