/* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2012. All Rights Reserved. * * 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. * * %CopyrightEnd% */ package com.ericsson.otp.erlang; /** * <p> * Provides a simple mechanism for exchanging messages with Erlang processes or other * instances of this class. * </p> * * <p> * Each mailbox is associated with a unique {@link OtpErlangPid pid} that contains * information necessary for delivery of messages. When sending messages to named * processes or mailboxes, the sender pid is made available to the recipient of the * message. When sending messages to other mailboxes, the recipient can only respond if * the sender includes the pid as part of the message contents. The sender can determine * his own pid by calling {@link #self() self()}. * </p> * * <p> * Mailboxes can be named, either at creation or later. Messages can be sent to named * mailboxes and named Erlang processes without knowing the {@link OtpErlangPid pid} that * identifies the mailbox. This is neccessary in order to set up initial communication * between parts of an application. Each mailbox can have at most one name. * </p> * * <p> * Since this class was intended for communication with Erlang, all of the send methods * take {@link OtpErlangObject OtpErlangObject} arguments. However this class can also be * used to transmit arbitrary Java objects (as long as they implement one of * java.io.Serializable or java.io.Externalizable) by encapsulating the object in a * {@link OtpErlangBinary OtpErlangBinary}. * </p> * * <p> * Messages to remote nodes are externalized for transmission, and as a result the * recipient receives a <b>copy</b> of the original Java object. To ensure consistent * behaviour when messages are sent between local mailboxes, such messages are cloned * before delivery. * </p> * * <p> * Additionally, mailboxes can be linked in much the same way as Erlang processes. If a * link is active when a mailbox is {@link #close closed}, any linked Erlang processes or * OtpMboxes will be sent an exit signal. As well, exit signals will be (eventually) sent * if a mailbox goes out of scope and its {@link #finalize finalize()} method called. * However due to the nature of finalization (i.e. Java makes no guarantees about when * {@link #finalize finalize()} will be called) it is recommended that you always * explicitly close mailboxes if you are using links instead of relying on finalization to * notify other parties in a timely manner. * </p> * * <p> * When retrieving messages from a mailbox that has received an exit signal, an * {@link OtpErlangExit OtpErlangExit} exception will be raised. Note that the exception * is queued in the mailbox along with other messages, and will not be raised until it * reaches the head of the queue and is about to be retrieved. * </p> * */ public class OtpMbox { OtpNode home; OtpErlangPid self; GenericQueue queue; String name; Links links; // package constructor: called by OtpNode:createMbox(name) // to create a named mbox OtpMbox(final OtpNode home, final OtpErlangPid self, final String name) { this.self = self; this.home = home; this.name = name; queue = new GenericQueue(); links = new Links(10); } // package constructor: called by OtpNode:createMbox() // to create an anonymous OtpMbox(final OtpNode home, final OtpErlangPid self) { this(home, self, null); } /** * <p> * Get the identifying {@link OtpErlangPid pid} associated with this mailbox. * </p> * * <p> * The {@link OtpErlangPid pid} associated with this mailbox uniquely identifies the * mailbox and can be used to address the mailbox. You can send the * {@link OtpErlangPid pid} to a remote communicating part so that he can know where * to send his response. * </p> * * @return the self pid for this mailbox. */ public OtpErlangPid self() { return self; } /** * <p> * Register or remove a name for this mailbox. Registering a name for a mailbox * enables others to send messages without knowing the {@link OtpErlangPid pid} of the * mailbox. A mailbox can have at most one name; if the mailbox already had a name, * calling this method will supercede that name. * </p> * * @param aname * the name to register for the mailbox. Specify null to unregister the * existing name from this mailbox. * * @return true if the name was available, or false otherwise. */ public synchronized boolean registerName(final String aname) { return home.registerName(aname, this); } /** * Get the registered name of this mailbox. * * @return the registered name of this mailbox, or null if the mailbox had no * registered name. */ public String getName() { return name; } /** * Block until a message arrives for this mailbox. * * @return an {@link OtpErlangObject OtpErlangObject} representing the body of the * next message waiting in this mailbox. * * @exception OtpErlangDecodeException * if the message can not be decoded. * * @exception OtpErlangExit * if a linked {@link OtpErlangPid pid} has exited or has sent an exit * signal to this mailbox. */ public OtpErlangObject receive() throws OtpErlangExit, OtpErlangDecodeException { try { return receiveMsg().getMsg(); } catch (final OtpErlangExit e) { throw e; } catch (final OtpErlangDecodeException f) { throw f; } } /** * Wait for a message to arrive for this mailbox. * * @param timeout * the time, in milliseconds, to wait for a message before returning null. * * @return an {@link OtpErlangObject OtpErlangObject} representing the body of the * next message waiting in this mailbox. * * @exception OtpErlangDecodeException * if the message can not be decoded. * * @exception OtpErlangExit * if a linked {@link OtpErlangPid pid} has exited or has sent an exit * signal to this mailbox. */ public OtpErlangObject receive(final long timeout) throws OtpErlangExit, OtpErlangDecodeException { try { final OtpMsg m = receiveMsg(timeout); if (m != null) { return m.getMsg(); } } catch (final OtpErlangExit e) { throw e; } catch (final OtpErlangDecodeException f) { throw f; } catch (final InterruptedException g) { } return null; } /** * Block until a message arrives for this mailbox. * * @return a byte array representing the still-encoded body of the next message * waiting in this mailbox. * * @exception OtpErlangExit * if a linked {@link OtpErlangPid pid} has exited or has sent an exit * signal to this mailbox. * */ public OtpInputStream receiveBuf() throws OtpErlangExit { return receiveMsg().getMsgBuf(); } /** * Wait for a message to arrive for this mailbox. * * @param timeout * the time, in milliseconds, to wait for a message before returning null. * * @return a byte array representing the still-encoded body of the next message * waiting in this mailbox. * * @exception OtpErlangExit * if a linked {@link OtpErlangPid pid} has exited or has sent an exit * signal to this mailbox. * * @exception InterruptedException * if no message if the method times out before a message becomes * available. */ public OtpInputStream receiveBuf(final long timeout) throws InterruptedException, OtpErlangExit { final OtpMsg m = receiveMsg(timeout); if (m != null) { return m.getMsgBuf(); } return null; } /** * Block until a message arrives for this mailbox. * * @return an {@link OtpMsg OtpMsg} containing the header information as well as the * body of the next message waiting in this mailbox. * * @exception OtpErlangExit * if a linked {@link OtpErlangPid pid} has exited or has sent an exit * signal to this mailbox. * */ public OtpMsg receiveMsg() throws OtpErlangExit { final OtpMsg m = (OtpMsg) queue.get(); switch (m.type()) { case OtpMsg.exitTag: case OtpMsg.exit2Tag: try { final OtpErlangObject o = m.getMsg(); throw new OtpErlangExit(o, m.getSenderPid()); } catch (final OtpErlangDecodeException e) { throw new OtpErlangExit("unknown", m.getSenderPid()); } default: return m; } } /** * Wait for a message to arrive for this mailbox. * * @param timeout * the time, in milliseconds, to wait for a message. * * @return an {@link OtpMsg OtpMsg} containing the header information as well as the * body of the next message waiting in this mailbox. * * @exception OtpErlangExit * if a linked {@link OtpErlangPid pid} has exited or has sent an exit * signal to this mailbox. * * @exception InterruptedException * if no message if the method times out before a message becomes * available. */ public OtpMsg receiveMsg(final long timeout) throws InterruptedException, OtpErlangExit { final OtpMsg m = (OtpMsg) queue.get(timeout); if (m == null) { return null; } switch (m.type()) { case OtpMsg.exitTag: case OtpMsg.exit2Tag: try { final OtpErlangObject o = m.getMsg(); throw new OtpErlangExit(o, m.getSenderPid()); } catch (final OtpErlangDecodeException e) { throw new OtpErlangExit("unknown", m.getSenderPid()); } default: return m; } } /** * Send a message to a remote {@link OtpErlangPid pid}, representing either another * {@link OtpMbox mailbox} or an Erlang process. * * @param to * the {@link OtpErlangPid pid} identifying the intended recipient of the * message. * * @param msg * the body of the message to send. * */ public void send(final OtpErlangPid to, final OtpErlangObject msg) { try { final String node = to.node(); if (node.equals(home.node())) { home.deliver(new OtpMsg(to, (OtpErlangObject) msg.clone())); } else { final OtpCookedConnection conn = home.getConnection(node); if (conn == null) { return; } conn.send(self, to, msg); } } catch (final Exception e) { } } /** * Send a message to a named mailbox created from the same node as this mailbox. * * @param aname * the registered name of recipient mailbox. * * @param msg * the body of the message to send. * */ public void send(final String aname, final OtpErlangObject msg) { home.deliver(new OtpMsg(self, aname, (OtpErlangObject) msg.clone())); } /** * Send a message to a named mailbox created from another node. * * @param aname * the registered name of recipient mailbox. * * @param node * the name of the remote node where the recipient mailbox is registered. * * @param msg * the body of the message to send. * */ public void send(final String aname, final String node, final OtpErlangObject msg) { try { final String currentNode = home.node(); if (node.equals(currentNode)) { send(aname, msg); } else if (node.indexOf('@', 0) < 0 && node .equals(currentNode.substring(0, currentNode.indexOf('@', 0)))) { send(aname, msg); } else { // other node final OtpCookedConnection conn = home.getConnection(node); if (conn == null) { return; } conn.send(self, aname, msg); } } catch (final Exception e) { } } /** * Close this mailbox with the given reason. * * <p> * After this operation, the mailbox will no longer be able to receive messages. Any * delivered but as yet unretrieved messages can still be retrieved however. * </p> * * <p> * If there are links from this mailbox to other {@link OtpErlangPid pids}, they will * be broken when this method is called and exit signals will be sent. * </p> * * @param reason * an Erlang term describing the reason for the exit. */ public void exit(final OtpErlangObject reason) { home.closeMbox(this, reason); } /** * Equivalent to <code>exit(new OtpErlangAtom(reason))</code>. * * @see #exit(OtpErlangObject) */ public void exit(final String reason) { exit(new OtpErlangAtom(reason)); } /** * <p> * Send an exit signal to a remote {@link OtpErlangPid pid}. This method does not * cause any links to be broken, except indirectly if the remote {@link OtpErlangPid * pid} exits as a result of this exit signal. * </p> * * @param to * the {@link OtpErlangPid pid} to which the exit signal should be sent. * * @param reason * an Erlang term indicating the reason for the exit. */ // it's called exit, but it sends exit2 public void exit(final OtpErlangPid to, final OtpErlangObject reason) { exit(2, to, reason); } /** * <p> * Equivalent to <code>exit(to, new * OtpErlangAtom(reason))</code>. * </p> * * @see #exit(OtpErlangPid, OtpErlangObject) */ public void exit(final OtpErlangPid to, final String reason) { exit(to, new OtpErlangAtom(reason)); } // this function used internally when "process" dies // since Erlang discerns between exit and exit/2. private void exit(final int arity, final OtpErlangPid to, final OtpErlangObject reason) { try { final String node = to.node(); if (node.equals(home.node())) { home.deliver(new OtpMsg(OtpMsg.exitTag, self, to, reason)); } else { final OtpCookedConnection conn = home.getConnection(node); if (conn == null) { return; } switch (arity) { case 1: conn.exit(self, to, reason); break; case 2: conn.exit2(self, to, reason); break; } } } catch (final Exception e) { } } /** * <p> * Link to a remote mailbox or Erlang process. Links are idempotent, calling this * method multiple times will not result in more than one link being created. * </p> * * <p> * If the remote process subsequently exits or the mailbox is closed, a subsequent * attempt to retrieve a message through this mailbox will cause an * {@link OtpErlangExit OtpErlangExit} exception to be raised. Similarly, if the * sending mailbox is closed, the linked mailbox or process will receive an exit * signal. * </p> * * <p> * If the remote process cannot be reached in order to set the link, the exception is * raised immediately. * </p> * * @param to * the {@link OtpErlangPid pid} representing the object to link to. * * @exception OtpErlangExit * if the {@link OtpErlangPid pid} referred to does not exist or could * not be reached. * */ public void link(final OtpErlangPid to) throws OtpErlangExit { try { final String node = to.node(); if (node.equals(home.node())) { if (!home.deliver(new OtpMsg(OtpMsg.linkTag, self, to))) { throw new OtpErlangExit("noproc", to); } } else { final OtpCookedConnection conn = home.getConnection(node); if (conn != null) { conn.link(self, to); } else { throw new OtpErlangExit("noproc", to); } } } catch (final OtpErlangExit e) { throw e; } catch (final Exception e) { } links.addLink(self, to); } /** * <p> * Remove a link to a remote mailbox or Erlang process. This method removes a link * created with {@link #link link()}. Links are idempotent; calling this method once * will remove all links between this mailbox and the remote {@link OtpErlangPid pid}. * </p> * * @param to * the {@link OtpErlangPid pid} representing the object to unlink from. * */ public void unlink(final OtpErlangPid to) { links.removeLink(self, to); try { final String node = to.node(); if (node.equals(home.node())) { home.deliver(new OtpMsg(OtpMsg.unlinkTag, self, to)); } else { final OtpCookedConnection conn = home.getConnection(node); if (conn != null) { conn.unlink(self, to); } } } catch (final Exception e) { } } /** * <p> * Create a connection to a remote node. * </p> * * <p> * Strictly speaking, this method is not necessary simply to set up a connection, * since connections are created automatically first time a message is sent to a * {@link OtpErlangPid pid} on the remote node. * </p> * * <p> * This method makes it possible to wait for a node to come up, however, or check that * a node is still alive. * </p> * * <p> * This method calls a method with the same name in {@link OtpNode#ping Otpnode} but * is provided here for convenience. * </p> * * @param node * the name of the node to ping. * * @param timeout * the time, in milliseconds, before reporting failure. */ public boolean ping(final String node, final long timeout) { return home.ping(node, timeout); } /** * <p> * Get a list of all known registered names on the same {@link OtpNode node} as this * mailbox. * </p> * * <p> * This method calls a method with the same name in {@link OtpNode#getNames Otpnode} * but is provided here for convenience. * </p> * * @return an array of Strings containing all registered names on this {@link OtpNode * node}. */ public String[] getNames() { return home.getNames(); } /** * Determine the {@link OtpErlangPid pid} corresponding to a registered name on this * {@link OtpNode node}. * * <p> * This method calls a method with the same name in {@link OtpNode#whereis Otpnode} * but is provided here for convenience. * </p> * * @return the {@link OtpErlangPid pid} corresponding to the registered name, or null * if the name is not known on this node. */ public OtpErlangPid whereis(final String aname) { return home.whereis(aname); } /** * Close this mailbox. * * <p> * After this operation, the mailbox will no longer be able to receive messages. Any * delivered but as yet unretrieved messages can still be retrieved however. * </p> * * <p> * If there are links from this mailbox to other {@link OtpErlangPid pids}, they will * be broken when this method is called and exit signals with reason 'normal' will be * sent. * </p> * * <p> * This is equivalent to {@link #exit(String) exit("normal")}. * </p> */ public void close() { home.closeMbox(this); } @Override protected void finalize() { close(); queue.flush(); } /** * Determine if two mailboxes are equal. * * @return true if both Objects are mailboxes with the same identifying * {@link OtpErlangPid pids}. */ @Override public boolean equals(final Object o) { if (!(o instanceof OtpMbox)) { return false; } final OtpMbox m = (OtpMbox) o; return m.self.equals(self); } @Override public int hashCode() { return self.hashCode(); } /* * called by OtpNode to deliver message to this mailbox. * * About exit and exit2: both cause exception to be raised upon receive(). However * exit (not 2) causes any link to be removed as well, while exit2 leaves any links * intact. */ void deliver(final OtpMsg m) { switch (m.type()) { case OtpMsg.linkTag: links.addLink(self, m.getSenderPid()); break; case OtpMsg.unlinkTag: links.removeLink(self, m.getSenderPid()); break; case OtpMsg.exitTag: links.removeLink(self, m.getSenderPid()); queue.put(m); break; case OtpMsg.exit2Tag: default: queue.put(m); break; } } // used to break all known links to this mbox void breakLinks(final OtpErlangObject reason) { final Link[] l = links.clearLinks(); if (l != null) { final int len = l.length; for (int i = 0; i < len; i++) { exit(1, l[i].remote(), reason); } } } }