/** * 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.io.IOException; import java.nio.ByteBuffer; import java.util.Map; import java.util.LinkedList; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import erjang.driver.EDriverTask; import kilim.Pausable; /** * This corresponds to a DistEntry in BEAM */ public class EPeer extends EAbstractNode { static Logger log = Logger.getLogger("erjang.dist"); protected static final byte passThrough = (byte) 0x70; protected static final byte distHeader = (byte) 131; protected static final byte version = (byte) 0x83; // Erlang message header tags protected static final int LINK = 1; protected static final int SEND = 2; protected static final int EXIT = 3; protected static final int UNLINK = 4; protected static final int NODE_LINK = 5; protected static final int REG_SEND = 6; protected static final int GROUP_LEADER = 7; protected static final int EXIT2 = 8; protected static final int SEND_TT = 12; protected static final int EXIT_TT = 13; protected static final int REG_SEND_TT = 16; protected static final int EXIT2_TT = 18; protected static final int MONITOR_P = 19; protected static final int DEMONITOR_P = 20; protected static final int MONITOR_P_EXIT = 21; private static final EAtom am_ = EAtom.intern(""); static ConcurrentHashMap<EAtom, EPeer> peers = new ConcurrentHashMap<EAtom, EPeer>(); /** Invariant: (port != null) xor (port_queue != null). */ private EInternalPort port; private LinkedList<ByteBuffer[]> port_queue; public EPeer(EAtom node, int creation, EInternalPort port, int flags, int version) { super(node); this.flags = flags; this.creation = creation; this.port = port; this.ntype = version == 5 ? NTYPE_R6 : 0; this.port_queue = (this.port == null) ? new LinkedList<ByteBuffer[]>() : null; } private void setPort(EInternalPort port) throws Pausable { assert(port != null); assert(this.port == null); assert(this.port_queue != null); //System.err.println("EPeer: Patching port for "+node+" to "+port); EDriverTask task = port.task(); assert(task != null); /* Note: The following could be simpler if * EDriverTask.outputv() wasn't Pausable - which it * shouldn't be, but is as long as we're using * bounded-size Mailboxes. -- eriksoe */ try { // Empty port_queue, then set port: while (true) { ByteBuffer[] msg; synchronized (this) { if (port_queue.isEmpty()) { // Go from queueing mode to direct-port mode: this.port = port; this.port_queue = null; break; } msg = port_queue.removeFirst(); assert(msg != null); } task.outputv(null, msg); // Pausable so called outside lock } } catch (IOException e) { e.printStackTrace(); close_and_finish(port); } } private void send_to_port(ByteBuffer[] ev, EHandle sender) throws Pausable { // Either send to port, or enqueue...: EInternalPort port; synchronized (this) { // get port; enqueue if necessary port = this.port; if (port == null) { // In queueing mode assert(port_queue != null); port_queue.addLast(ev); return; } } assert (port != null); // In direct-to-port mode EDriverTask task = port.task(); if (task != null) { try { task.outputv(null, ev); } catch (IOException e) { e.printStackTrace(); close_and_finish(port); } } else { log.warning("sending cast to dead task (port="+port+", this="+this+", sender="+sender+")"); } } // TODO: who closes/deletes these peers? /** get_or_create - "no port" case. */ public static EPeer get_or_create(EAtom node, int creation, int flags, int version) { EPeer peer = peers.get(node); if (peer != null) { // check version, etc? return peer; } peer = new EPeer(node, creation, null, flags, version); peers.put(node, peer); return peer; } /** get_or_create - "port given" case. */ public static EPeer get_or_create(EAtom node, int creation, EInternalPort port, int flags, int version) throws Pausable { EPeer peer = peers.get(node); if (peer != null) { // check version, etc? boolean do_set_port; synchronized (peer) { do_set_port = (peer.port == null && port != null); } if (do_set_port) { peer.setPort(port); } else if (port != null) { log.warning("Port already set for node (to "+peer.port+"); refraining from setting it again (to "+port+")"); } return peer; } peer = new EPeer(node, creation, port, flags, version); peers.put(node, peer); return peer; } public void net_message(EInternalPort port, ByteBuffer hdr, ByteBuffer buf) throws Pausable { try { net_message2(port, hdr, buf); } catch (IOException e) { //e.printStackTrace(); // TODO: this close and finish doesn't work right! close_and_finish(port); } } EAtom[][] atom_cache; { atom_cache = new EAtom[8][]; for (int i = 0; i < 8; i++) { atom_cache[i] = new EAtom[256]; } } public void net_message2(EInternalPort port, ByteBuffer hdr, ByteBuffer buf) throws IOException, Pausable { if (buf.remaining() == 0) { log.fine("received tick from " + this.node); return; } if (hdr != null && hdr.remaining() != 0) { close_and_finish(port); return; } EInputStream ibuf = new EInputStream(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining(), flags); int start = ibuf.getPos(); receive_loop: do { int first = ibuf.read1(); EAtom[] atom_cache_refs; switch (first) { case 131: if (!process_distribution_header(port, ibuf)) return; case passThrough: break; default: close_and_finish(port); return; } // got a real message (really) EObject reason = null; EAtom cookie = null; EObject tmp = null; ETuple head = null; EAtom toName; EPID to; EPID from; int tag; // decode the header tmp = ibuf.read_any(); if ((head = tmp.testTuple()) == null) { break receive_loop; } if (log.isLoggable(Level.FINE)) { log.fine("received net_message " + tmp); } ESmall tag_sm; if ((tag_sm = head.elm(1).testSmall()) == null) { break receive_loop; } // lets see what kind of message this is tag = tag_sm.value; switch (tag) { case LINK: { // {1, observer, observed } EPID from_pid = head.elm(2).testPID(); EInternalPID to_pid = head.elm(3).testInternalPID(); if (!to_pid.link_oneway(from_pid)) { dsig_exit(to_pid, from_pid, ERT.am_noproc); } return; } case UNLINK: { // {1, observer, observed } EPID from_pid = head.elm(2).testPID(); EInternalPID to_pid = head.elm(3).testInternalPID(); if (to_pid != null && from_pid != null) { to_pid.unlink_oneway(from_pid); } return; } case SEND: { // {2, Cookie, ToPid} EObject msg = ibuf.read_any(); if (log.isLoggable(Level.FINE)) log.fine(" payload: " + msg); EHandle dst = head.elm(3).testHandle(); if (dst == null) throw new IOException("protocol error"); if (dst != null) { dst.send(null, msg); } return; } case REG_SEND: // { REG_SEND, FromPid, Cookie, ToName } case REG_SEND_TT: // { REG_SEND, FromPid, Cookie, ToName, TraceToken // } { EObject msg = ibuf.read_any(); if (log.isLoggable(Level.FINE)) log.fine(" payload: " + msg); EAtom toname = head.elm(4).testAtom(); if (toname == null) throw new IOException("protocol error"); EHandle dst = ERT.whereis(toname).testHandle(); if (dst != null) { dst.sendb(msg); } return; } case EXIT: case EXIT2: { EPID from_pid = head.elm(2).testPID(); EPID to_proc = head.elm(3).testPID(); reason = head.elm(4); to_proc.exit_signal(from_pid, reason, false); return; } case MONITOR_P: { // {19, FromPid, ToProc, Ref} // FromPid = monitoring process // ToProc = monitored process pid or name (atom) EPID from_pid = head.elm(2).testPID(); EPID to_proc = head.elm(3).testPID(); ERef ref = head.elm(4).testReference(); if (to_proc == null) { EAtom am = head.elm(3).testAtom(); if (am == null) { throw new IOException("protocol error: " + head); } to_proc = ERT.whereis(am).testPID(); } if (to_proc == null) { throw new IOException("protocol error: " + head); } if (!to_proc.add_monitor(from_pid, ref)) { dsig_send_monitor_exit(to_proc, from_pid, ref, ERT.am_noproc); } return; } case DEMONITOR_P: { // {20, FromPid, ToProc, Ref} EPID from_pid = head.elm(2).testPID(); EPID to_proc = head.elm(3).testPID(); ERef ref = head.elm(4).testReference(); if (to_proc == null) { EAtom am = head.elm(3).testAtom(); if (am == null) { throw new IOException("protocol error: " + head); } to_proc = ERT.whereis(am).testPID(); } if (to_proc == null) { throw new IOException("protocol error: " + head); } to_proc.remove_monitor(from_pid, ref, false); return; } case MONITOR_P_EXIT: { // {21, FromProc, ToPid, Ref, Reason} EPID from_pid = head.elm(2).testPID(); EPID to_proc = head.elm(3).testPID(); ERef ref = head.elm(4).testReference(); reason = head.elm(5); if (to_proc == null) { EAtom am = head.elm(3).testAtom(); if (am == null) { throw new IOException("protocol error: " + head); } to_proc = ERT.whereis(am).testPID(); } if (to_proc == null) { throw new IOException("protocol error: " + head); } to_proc.send_monitor_exit(from_pid, ref, reason); return; } default: throw new NotImplemented("dmesg: " + head); } } while (false); // end receive_loop } private boolean process_distribution_header(EInternalPort port, EInputStream ibuf) throws IOException { EAtom[] atom_cache_refs; log.fine("parsing distribuionHeader...."); int datum = ibuf.read1(); if (datum != 68) { close_and_finish(port); return false; } int numberOfAtomCacheRefs = ibuf.read1() & 0xff; atom_cache_refs = new EAtom[numberOfAtomCacheRefs]; if (numberOfAtomCacheRefs == 0) { // do nothing // } else { int nflag_bytes = numberOfAtomCacheRefs / 2 + 1; byte[] flags = new byte[nflag_bytes * 2]; int pos = 0; for (int i = 0; i < nflag_bytes; i++) { int twoflags = ibuf.read1() & 0xff; // System.err.println("flag["+i+"]="+Integer.toBinaryString(twoflags)); flags[pos++] = (byte) (twoflags & 0xf); flags[pos++] = (byte) (twoflags >>> 4); // System.err.println("flags["+(pos-2)+"]="+Integer.toBinaryString(flags[pos-2])); // System.err.println("flags["+(pos-1)+"]="+Integer.toBinaryString(flags[pos-1])); } boolean longAtoms = (flags[numberOfAtomCacheRefs] & 0x01) == 1; if (longAtoms) { log.fine("LONGATOMS!"); } for (int i = 0; i < numberOfAtomCacheRefs; i++) { int segment_index = flags[i] & 7; int index = ibuf.read1(); if (log.isLoggable(Level.FINE)) log.fine("cache[" + i + "] -> ref[" + segment_index + "][" + index + "]"); if ((flags[i] & 8) == 8) { // it's new! int len = ibuf.read1() & 0xff; if (longAtoms) { len <<= 8; len |= (ibuf.read1() & 0xff); } byte[] atom_text = new byte[len]; ibuf.read(atom_text); atom_cache[segment_index][index] = EAtom .intern(atom_text); } else { // it's old } atom_cache_refs[i] = atom_cache[segment_index][index]; if (log.isLoggable(Level.FINE)) log.fine(" => " + atom_cache_refs[i]); } ibuf.setAtomCacheRefs(atom_cache_refs); } return true; } private void close_and_finish(EInternalPort port) { port.task().exit(ERT.am_killed); } public static EAbstractNode get(EAtom node) { if (node == ERT.getLocalNode().node()) return ERT.getLocalNode(); return peers.get(node); } void dsig_cast(EHandle sender, ETuple hdr) throws Pausable { ByteBuffer disthdr = ByteBuffer.allocate(3); disthdr.put((byte) 131); disthdr.put((byte) 68); disthdr.put((byte) 0); disthdr.flip(); EOutputStream eos = new EOutputStream(1024,flags); hdr.encode(eos); ByteBuffer barr = eos.toByteBuffer(); ByteBuffer[] ev = new ByteBuffer[] { disthdr, barr }; send_to_port(ev, sender); } int dsig_cast(EHandle sender, ETuple hdr, EObject payload) throws Pausable { ByteBuffer disthdr = ByteBuffer.allocate(3); disthdr.put((byte) 131); disthdr.put((byte) 68); disthdr.put((byte) 0); disthdr.flip(); EOutputStream eos = new EOutputStream(1024,flags); hdr.encode(eos); payload.encode(eos); ByteBuffer barr = eos.toByteBuffer(); ByteBuffer[] ev = new ByteBuffer[] { disthdr, barr }; send_to_port(ev, sender); return eos.size(); } public int dsig_send(EHandle sender, EExternalPID pid, EObject msg) throws Pausable { ETuple hdr = ETuple.make(ERT.box(SEND), (EAtom) am_, pid); return dsig_cast(sender, hdr, msg); } public void dsig_send_monitor_exit(EHandle sender, EPID to_pid, ERef ref, EObject reason) throws Pausable { ETuple hdr = ETuple.make(ERT.box(MONITOR_P_EXIT), sender, to_pid, ref, reason); dsig_cast(sender, hdr); } public void dsig_monitor(EHandle sender, EObject to_pid, ERef ref) throws Pausable { ETuple hdr = ETuple.make(ERT.box(MONITOR_P), sender, to_pid, ref); dsig_cast(sender, hdr); } public void dsig_exit(EHandle sender, EPID to_pid, EObject reason) throws Pausable { ETuple hdr = ETuple.make(ERT.box(EXIT), sender, to_pid, reason); dsig_cast(sender, hdr); } public void dsig_link(EHandle sender, EExternalPID to_pid) throws Pausable { ETuple hdr = ETuple.make(ERT.box(LINK), sender, to_pid); dsig_cast(sender, hdr); } public void dsig_unlink(EHandle sender, EExternalPID to_pid) throws Pausable { ETuple hdr = ETuple.make(ERT.box(UNLINK), sender, to_pid); dsig_cast(sender, hdr); } public void dsig_demonitor(EHandle sender, ERef ref, EObject to_pid_or_name) throws Pausable { ETuple hdr = ETuple.make(ERT.box(DEMONITOR_P), sender, to_pid_or_name, ref); dsig_cast(sender, hdr); } public EObject dsig_reg_send(EInternalPID sender, EAtom to_name, EObject msg) throws Pausable { ETuple hdr = ETuple.make(ERT.box(REG_SEND), sender, am_, to_name); dsig_cast(sender, hdr, msg); return msg; } public static ESeq getRemoteNodes() { EAtom[] nodes = peers.keySet().toArray(new EAtom[0]); ESeq reply = ERT.NIL; for (int i = 0; i < nodes.length; i++) { reply = reply.cons(nodes[i]); } return reply; } }