/** * 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.m.erlang; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryType; import java.lang.management.MemoryUsage; import java.nio.ByteOrder; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import kilim.Pausable; import erjang.BIF; import erjang.EAbstractNode; import erjang.EAtom; import erjang.ECons; import erjang.EFun; import erjang.EHandle; import erjang.EModuleManager; import erjang.EObject; import erjang.EPID; import erjang.EPeer; import erjang.EProc; import erjang.ERT; import erjang.ERef; import erjang.ESeq; import erjang.ESmall; import erjang.EString; import erjang.ETask; import erjang.ETuple; import erjang.ETuple2; import erjang.ErjangHibernateException; import erjang.ErlangException; import erjang.ErlangExit; import erjang.ErlangHalt; import erjang.ErlangUndefined; import erjang.FunID; import erjang.Import; import erjang.Main; import erjang.NotImplemented; /** * */ public class ErlProc { private static Logger log = Logger.getLogger("erjang.module.proc"); private static final EAtom am_smp_support = EAtom.intern("smp_support"); private static final EAtom am_process_limit = EAtom.intern("process_limit"); private static final EAtom am_fullsweep_after = EAtom.intern("fullsweep_after"); private static final EAtom am_schedulers = EAtom.intern("schedulers"); private static final EAtom am_schedulers_online = EAtom.intern("schedulers_online"); private static final EAtom am_break_ignored = EAtom.intern("break_ignored"); private static final EAtom am_threads = EAtom.intern("threads"); public static final EAtom am_process = EAtom.intern("process"); private static final EAtom am_wordsize = EAtom.intern("wordsize"); private static final EAtom am_thread_pool_size = EAtom .intern("thread_pool_size"); private static final EAtom am_os_type = EAtom.intern("os_type"); private static final EAtom am_win32 = EAtom.intern("win32"); private static final EAtom am_unix = EAtom.intern("unix"); private static final EAtom am_version = EAtom.intern("version"); private static final EAtom am_undefined = EAtom.intern("undefined"); private static final EAtom am_heap = EAtom.intern("heap"); private static final EAtom am_non_heap = EAtom.intern("non_heap"); private static final EAtom am_jvm = EAtom.intern("jvm"); private static final EAtom am_allocated_areas = EAtom.intern("allocated_areas"); private static final EAtom am_otp_release = EAtom.intern("otp_release"); private static final EAtom am_driver_version = EAtom.intern("driver_version"); private static final EAtom am_global_heaps_size = EAtom.intern("global_heaps_size"); private static final EAtom am_process_count = EAtom.intern("process_count"); private static final EAtom am_system_architecture = EAtom.intern("system_architecture"); private static final EAtom am_logical_processors = EAtom.intern("logical_processors"); private static final EAtom am_hipe_architecture = EAtom.intern("hipe_architecture"); private static final EAtom am_machine = EAtom.intern("machine"); private static final EAtom am_link = EAtom.intern("link"); private static final EAtom am_monitor = EAtom.intern("monitor"); private static final EAtom am_priority = EAtom.intern("priority"); private static final EAtom am_system_version = EAtom.intern("system_version"); public static final EAtom am_flush = EAtom.intern("flush"); public static final EAtom am_shutdown = EAtom.intern("shutdown"); private static final EAtom am_ets_alloc = EAtom.intern("ets_alloc"); private static final EAtom am_error_checker = EAtom.intern("error_checker"); private static final EAtom am_debug_compiled = EAtom.intern("debug_compiled"); private static final EAtom am_lock_checking = EAtom.intern("lock_checking"); private static final EAtom am_compat_rel = EAtom.intern("compat_rel"); private static final EAtom am_endian = EAtom.intern("endian"); private static final EAtom am_little = EAtom.intern("little"); private static final EAtom am_big = EAtom.intern("big"); @BIF public static EObject process_info(EObject pid, EObject what) { EPID p = pid.testPID(); if (p == null) throw ERT.badarg(pid,what); // TODO: validate WHAT locally before going remote? return p.process_info(what); } @BIF public static EObject process_info(EObject pid) { EPID p = pid.testPID(); if (p == null) throw ERT.badarg(pid); return p.process_info(); } @BIF public static EObject display(EProc proc, EObject obj) { ERT.getOutputStream().println(obj); return ERT.TRUE; } @BIF public static ESeq get_stacktrace(EProc proc) { ErlangException ex = proc.getLastException(); return ex != null ? ex.getTrace() : ERT.NIL; } @BIF public static EObject erase(EProc proc, EObject key) { return proc.erase(key); } @BIF public static EObject erase(EProc proc) { return proc.erase(); } @BIF public static EObject register(EObject name, EObject pid) { EAtom aname; EHandle handle = pid.testHandle(); if ((aname=name.testAtom()) == null || handle == null) throw ERT.badarg(name, pid); ERT.register(aname, handle); return ERT.TRUE; } @BIF public static EObject unregister(EObject name) { EAtom aname; if ((aname=name.testAtom()) == null || !ERT.unregister(aname)) throw ERT.badarg(name); return ERT.TRUE; } @BIF public static EObject spawn_link(EProc proc, EObject mod, EObject fun, EObject args) throws Pausable { EAtom m = mod.testAtom(); EAtom f = fun.testAtom(); ESeq a = args.testSeq(); if (m==null||f==null||a==null) throw ERT.badarg(mod, fun, args); EProc p2 = new EProc(proc.group_leader(), m, f, a); proc.link_to(p2); ERT.run(p2); return p2.self_handle(); } @BIF public static EObject spawn_opt(EProc self, EObject tup) throws Pausable { ETuple t; EAtom m; EAtom f; ESeq a; ESeq o; if ((t=tup.testTuple()) == null || t.arity() != 4 || (m=t.elm(1).testAtom()) == null || (f=t.elm(2).testAtom()) == null || (a=t.elm(3).testSeq()) == null || (o=t.elm(4).testSeq()) == null ) throw ERT.badarg(tup); boolean link = false; boolean monitor = false; EAtom priority = null; for (; !o.isNil(); o = o.tail() ) { EObject val = o.head(); ETuple2 t2; if (val == am_link) { link = true; } else if (val == am_monitor) { monitor = true; } else if ((t2 = ETuple2.cast(val)) != null) { if (t2.elm(1) == am_priority) { EAtom am = t2.elm(2).testAtom(); if (am != null) priority = am; } // ignore full_sweep_after and min_heap_size } } EProc p2 = new EProc(self.group_leader(), m, f, a); if (link) { self.link_to(p2); } if (priority != null) { // may throw badarg! p2.process_flag(am_priority, priority); } ERef ref = null; if (monitor) { ref = ERT.getLocalNode().createRef(); if (!self.monitor(p2.self_handle(), p2.self_handle(), ref)) { throw new InternalError("cannot monitor new process?"); // self.mbox_send(ETuple.make(ERT.am_DOWN, ref, p2.self_handle(), ERT.am_noproc)); } } ERT.run(p2); if (monitor) { return new ETuple2(p2.self_handle(), ref); } else { return p2.self_handle(); } } @BIF public static EObject spawn(EProc proc, EObject mod, EObject fun, EObject args) { EAtom m = mod.testAtom(); EAtom f = fun.testAtom(); ESeq a = args.testSeq(); if (m==null||f==null||a==null) throw ERT.badarg(mod, fun, args); EProc p2 = new EProc(proc.group_leader(), m, f, a); ERT.run(p2); return p2.self_handle(); } @BIF public static EObject halt(EProc proc) { return halt(proc, null); } @BIF public static EObject halt(EProc proc, EObject value) { int exitCode = 1; String message = null; if (value != null) { ESmall val = value.testSmall(); if (val != null) { exitCode = val.value; } EString str = value.testString(); if (str != null) { message = str.stringValue(); } } /* // TODO: create crash file if (message != null) { log.severe("halting system: " + message); } else { log.info("halting system"); } */ ERT.shutdown(); throw new ErlangHalt(); } @BIF public static EObject unlink(EProc self, EObject pid) throws Pausable { EHandle h = EHandle.cast(pid); if (h != null) { self.unlink(h); } return pid; } @BIF static public EObject link(EProc self, EObject pid) throws Pausable { EHandle h = EHandle.cast(pid); if (h == null) throw ERT.badarg(pid); self.link_to(h); return ERT.TRUE; } @BIF static public EObject monitor(EProc self, EObject how, EObject object) throws Pausable { if (how != am_process) throw ERT.badarg(how, object); // case 1: object is a PID EHandle h = EHandle.cast(object); if (h != null) { ERef ref = ERT.getLocalNode().createRef(); if (!self.monitor(h, h, ref)) { self.mbox_send(ETuple.make(ERT.am_DOWN, ref, am_process, object, ERT.am_noproc)); } return ref; } // case 2: object is a name EAtom name; if (h == null && (name=object.testAtom()) != null) { ERef ref = ERT.getLocalNode().createRef(); boolean success = false; object = new ETuple2(name, ErlDist.node()); if ((h = ERT.whereis(name).testHandle()) != null) { success = self.monitor(h, object, ref); } if (!success) { self.mbox_send(ETuple.make(ERT.am_DOWN, ref, am_process, object, ERT.am_noproc)); } return ref; } // case 3: object is {name, node} ETuple tup; EAtom node; if ((tup=object.testTuple()) != null && tup.arity()==2 && (name=tup.elm(1).testAtom()) != null && (node=tup.elm(2).testAtom()) != null) { if (node == ErlDist.node()) { ERef ref = ERT.getLocalNode().createRef(); boolean success = false; if ((h = ERT.whereis(name).testHandle()) != null) { success = self.monitor(h, object, ref); } if (!success) { self.mbox_send(ETuple.make(ERT.am_DOWN, ref, am_process, object, ERT.am_noproc)); } return ref; } else { EPeer peer = (EPeer) EPeer.get(node); if (peer != null) { ERef ref = ERT.getLocalNode().createRef(); self.monitor(tup, ref); peer.dsig_monitor(self.self_handle(), name, ref); return ref; } return ErlDist.dmonitor_p2_trap.invoke(self, new EObject[] {how, object}); } } throw ERT.badarg(how, object); } @Import(module="erlang", fun="flush_monitor_message", arity=2) static EFun flush_monitor_message = null; @BIF static public EObject demonitor(EProc self, EObject ref) throws Pausable { return demonitor(self, ref, ERT.NIL); } /* TODO: Split option parsing from the action; used the action * part more directly in demonitor/1. * TODO: Support the 'info' option. */ @BIF static public EObject demonitor(EProc self, EObject ref, EObject options) throws Pausable { return demonitor((ETask<?>)self, ref, options); } static public EObject demonitor(ETask<?> self, EObject ref, EObject options) throws Pausable { ERef r = ref.testReference(); ESeq o = options.testSeq(); if (r==null||o==null) throw ERT.badarg(ref, options); boolean flush = (!o.isNil() && o.head()==am_flush); EObject found = self.demonitor(r); if (found == null) { return ERT.FALSE; } EHandle h; ETuple tup; EAtom name; EAtom node; if ((h=found.testHandle()) != null) { h.remove_monitor(self.self_handle(), r, flush); } else if ((tup=found.testTuple()) != null && tup.arity()==2 && (name=tup.elm(1).testAtom()) != null && (node=tup.elm(2).testAtom()) != null) { EAbstractNode n = EAbstractNode.get_or_connect(self, node); if (n != null) { n.dsig_demonitor(self.self_handle(), r, name); } } if (flush && (self instanceof EProc)) { flush_monitor_message.invoke((EProc) self, new EObject[] {ref, ERT.am_ok}); } return ERT.TRUE; } @BIF static EObject flush_monitor_message(EProc proc, EObject arg1, EObject arg2) throws Pausable { ERef ref = arg1.testReference(); EAtom ok = arg2.testAtom(); if (ref == null || ok == null) { throw ERT.badarg(arg1, arg2); } LOOP: do { EObject m = ERT.loop_rec(proc); ETuple tup; if (m == null) { if (ERT.wait_timeout(proc, ESmall.ZERO)) { continue LOOP; } else { ERT.timeout(proc); break LOOP; } } if ((tup = m.testTuple()) != null && tup.arity() == 4 && tup.elm(2)==ref ) { ERT.remove_message(proc); break LOOP; } ERT.loop_rec_end(proc); } while (true); return ERT.am_ok; } @BIF public static EAtom exit(EProc proc, EObject p, EObject reason) throws Pausable { // System.err.println(proc.self_handle() + ":: erlang:exit(" + p + ", " + reason + ")"); EHandle pid = p.testHandle(); if (pid == null) throw ERT.badarg(p, reason); if (pid == proc.self_handle()) { throw new ErlangExit(reason, proc.self_handle()); } pid.exit_signal(proc.self_handle(), reason, true); return ERT.TRUE; } @BIF public static EObject exit(EProc self, EObject a1) { throw new ErlangExit(a1, self.self_handle()); } @BIF public static ERef make_ref(EProc proc) { return ERT.getLocalNode().createRef(); } @BIF static EObject group_leader(EProc proc) { return proc.group_leader(); } @BIF static EObject group_leader(EObject group_leader, EObject pid) { EPID p = pid.testPID(); EPID gl = group_leader.testPID(); if (p==null || gl==null) throw ERT.badarg(group_leader, pid); p.set_group_leader(gl); return ERT.TRUE; } static EAtom am_allocator = EAtom.intern("allocator"); static EAtom am_heap_type = EAtom.intern("heap_type"); static EAtom am_shared = EAtom.intern("shared"); private static EAtom am_build_type = EAtom.intern("build_type"); @BIF static EObject system_info(EProc proc, EObject type) { if (type == am_machine) { // we report BEAM so that the compiler emits BEAM files return EString.fromString("BEAM"); } else if (type == am_smp_support) { return ERT.TRUE; } else if (type == am_schedulers) { return ERT.box(ERT.threadPoolSize()); } else if (type == am_process_limit) { return ERT.box(ERT.processLimit()); } else if (type == am_fullsweep_after) { return ERT.box(65535); } else if (type == am_schedulers_online) { return ERT.box(Math.max(1, ERT.threadPoolSize())); } else if (type == am_threads) { return ERT.box(true); } else if (type == am_thread_pool_size) { return ERT.box(ERT.asyncThreadPoolSize()); } else if (type == am_break_ignored) { return ERT.box(false); } else if (type == am_compat_rel) { // we return same value as R14 return new ESmall(14); } else if (type == am_endian) { if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { return am_little; } else { return am_big; } } ETuple2 tup; if (type == am_allocated_areas) { ECons res = ERT.NIL; List<MemoryPoolMXBean> bean2 = ManagementFactory .getMemoryPoolMXBeans(); if (bean2 == null) { MemoryMXBean bean = ManagementFactory.getMemoryMXBean(); if (bean != null) { MemoryUsage mu = bean.getHeapMemoryUsage(); res = res.cons(ETuple.make(am_heap, ERT.box(mu.getCommitted()), ERT.box(mu.getUsed()))); mu = bean.getNonHeapMemoryUsage(); res = res.cons(ETuple.make(am_non_heap, ERT.box(mu .getCommitted()), ERT.box(mu.getUsed()))); } return res; } for (MemoryPoolMXBean mb : bean2) { String name = mb.getName(); MemoryUsage mu = mb.getUsage(); if (mu == null) continue; String name2 = (mb.getType()==MemoryType.HEAP ? "heap:" : "non_heap:" ) + name; res = res.cons(ETuple.make(EAtom.intern(name2), ERT.box(mu .getCommitted()), ERT.box(mu.getUsed()))); } return res; } else if (type == am_allocator) { return am_jvm; } else if (type == am_heap_type) { return am_shared; } else if (type == am_smp_support) { return ERT.TRUE; } else if (type == am_thread_pool_size) { // TODO: hook up to thread pool return new ESmall(ERT.threadPoolSize()); } else if (type == am_os_type) { String os = System.getProperty("os.name"); if (os.startsWith("Windows")) { return ETuple.make(am_win32, new EString(os)); } else { return ETuple.make(am_unix, new EString(os)); } } else if (type == am_threads) { return ERT.TRUE; } else if (type == am_version) { String erts_version = ERT.runtime_info.erts_version; // remove prefix String prefix = "erts-"; if (erts_version.startsWith(prefix)) { erts_version = erts_version.substring(prefix.length()); } return EString.fromString(erts_version); } else if (type == am_otp_release) { return new EString(ERT.runtime_info.otp_version); } else if (type == am_logical_processors) { // TODO: be smarter somehow return ERT.box(Runtime.getRuntime().availableProcessors()); } else if (type == am_global_heaps_size) { return ERT.box(Runtime.getRuntime().totalMemory()); } else if (type == am_process_count) { return ERT.box(EProc.process_count()); } else if (type == am_system_architecture) { return new EString(Main.SYSTEM_ARCHITECTURE); } else if (type == am_driver_version) { // TODO: be smarter somehow return new EString(Main.DRIVER_VERSION); } else if (type == am_wordsize) { return new ESmall(4); } else if (type == am_debug_compiled || type == am_lock_checking) { throw ERT.badarg(type); } else if (type == am_hipe_architecture) { return am_undefined; } else if (type == am_build_type) { return EAtom.intern("opt"); } else if (type == am_system_version) { return new EString("Erjang ["+ ERT.runtime_info.erts_version+"]"); } else if ((tup=ETuple2.cast(type)) != null) { if (tup.elem1 == am_allocator) { if (tup.elem2 == am_ets_alloc) { return ERT.FALSE; } } else if (tup.elem1 == am_error_checker) { throw ERT.badarg(type); } return am_undefined; } else { log.info("erlang:system_info("+type+") unknown"); throw ERT.badarg(type); } } @BIF static EAtom module_loaded(EObject mod) { EAtom m; if ((m=mod.testAtom()) == null) throw ERT.badarg(mod); return EModuleManager.module_loaded(m) ? ERT.TRUE : ERT.FALSE; } @BIF static ESeq processes() { return EProc.processes(); } @BIF public static EAtom is_process_alive(EObject p) { EPID pid = p.testPID(); if (pid == null) throw ERT.badarg(p); return ERT.box(pid.is_alive_dirtyread()); } @BIF public static EObject suspend_process(EObject a1, EObject a2) { throw new NotImplemented(); } @BIF public static EAtom check_process_code(EObject pid_arg, EObject mod_arg) { EPID pid = pid_arg.testPID(); EAtom mod = mod_arg.testAtom(); if (pid == null || mod == null) { throw ERT.badarg(pid_arg, mod_arg); } log.log(Level.FINE, "check_process_code not implemented (" + pid + ", " + mod + ")"); return ERT.FALSE; } @BIF public static EAtom purge_module(EObject mod_arg) { log.log(Level.FINE, "purge_module not implemented (" + mod_arg + ")"); return ERT.TRUE; } @BIF public static EObject hibernate(EProc self, EObject a1, EObject a2, EObject a3) { EAtom m = a1.testAtom(); EAtom f = a2.testAtom(); ESeq a = a3.testSeq(); if (m == null || f == null || a == null) { throw ERT.badarg(a1, a2, a3); } int arity = a.length(); EFun target = EModuleManager.resolve(new FunID(m,f,arity)); if (target == null) { throw new ErlangUndefined(m, f, new ESmall(arity)); } self.tail = target; a = a.reverse(); switch (arity) { default: throw new NotImplemented("hibernate w/" + arity + " args"); case 7: self.arg6 = a.head(); a = a.tail(); case 6: self.arg5 = a.head(); a = a.tail(); case 5: self.arg4 = a.head(); a = a.tail(); case 4: self.arg3 = a.head(); a = a.tail(); case 3: self.arg2 = a.head(); a = a.tail(); case 2: self.arg1 = a.head(); a = a.tail(); case 1: self.arg0 = a.head(); // a = a.tail(); case 0: } throw ErjangHibernateException.INSTANCE; } }