PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/otp.net/Otp/OtpNode.cs

https://github.com/babo/jungerl
C# | 853 lines | 526 code | 90 blank | 237 comment | 65 complexity | d2de28b634aaaa4843def31dcef1025a MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, AGPL-1.0
  1. /*``The contents of this file are subject to the Erlang Public License,
  2. * Version 1.1, (the "License"); you may not use this file except in
  3. * compliance with the License. You should have received a copy of the
  4. * Erlang Public License along with this software. If not, it can be
  5. * retrieved via the world wide web at http://www.erlang.org/.
  6. *
  7. * Software distributed under the License is distributed on an "AS IS"
  8. * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  9. * the License for the specific language governing rights and limitations
  10. * under the License.
  11. *
  12. * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
  13. * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
  14. * AB. All Rights Reserved.''
  15. *
  16. * Converted from Java to C# by Vlad Dumitrescu (vlad_Dumitrescu@hotmail.com)
  17. */
  18. namespace Otp
  19. {
  20. using System;
  21. /*
  22. * <p> Represents a local OTP node. This class is used when you do not
  23. * wish to manage connections yourself - outgoing connections are
  24. * established as needed, and incoming connections accepted
  25. * automatically. This class supports the use of a mailbox API for
  26. * communication, while management of the underlying communication
  27. * mechanism is automatic and hidden from the application programmer.
  28. * </p>
  29. *
  30. * <p> Once an instance of this class has been created, obtain one or
  31. * more mailboxes in order to send or receive messages. The first
  32. * message sent to a given node will cause a connection to be set up
  33. * to that node. Any messages received will be delivered to the
  34. * appropriate mailboxes. </p>
  35. *
  36. * <p> To shut down the node, call {@link #close close()}. This will
  37. * prevent the node from accepting additional connections and it will
  38. * cause all existing connections to be closed. Any unread messages in
  39. * existing mailboxes can still be read, however no new messages will
  40. * be delivered to the mailboxes. </p>
  41. *
  42. * <p> Note that the use of this class requires that Epmd (Erlang Port
  43. * Mapper Daemon) is running on each cooperating host. This class does
  44. * not start Epmd automatically as Erlang does, you must start it
  45. * manually or through some other means. See the Erlang documentation
  46. * for more information about this. </p>
  47. **/
  48. public class OtpNode:OtpLocalNode
  49. {
  50. private bool initDone = false;
  51. // thread to manage incoming connections
  52. private Acceptor acceptor = null;
  53. // keep track of all connections
  54. internal System.Collections.Hashtable connections = null;
  55. // keep track of all mailboxes
  56. internal Mailboxes mboxes = null;
  57. // handle status changes
  58. internal OtpNodeStatus handler;
  59. /*
  60. * <p> Create a node using the default cookie. The default
  61. * cookie is found by reading the first line of the .erlang.cookie
  62. * file in the user's home directory. The home directory is obtained
  63. * from the System property "user.home". </p>
  64. *
  65. * <p> If the file does not exist, an empty string is used. This
  66. * method makes no attempt to create the file. </p>
  67. *
  68. * @param node the name of this node.
  69. *
  70. * @exception IOException if communication could not be initialized.
  71. *
  72. **/
  73. public OtpNode(System.String node):this(node, defaultCookie, 0)
  74. {
  75. }
  76. /*
  77. * Create a node.
  78. *
  79. * @param node the name of this node.
  80. *
  81. * @param cookie the authorization cookie that will be used by this
  82. * node when it communicates with other nodes.
  83. *
  84. * @exception IOException if communication could not be initialized.
  85. *
  86. **/
  87. public OtpNode(System.String node, System.String cookie):this(node, cookie, 0)
  88. {
  89. }
  90. /*
  91. * Create a node.
  92. *
  93. * @param node the name of this node.
  94. *
  95. * @param cookie the authorization cookie that will be used by this
  96. * node when it communicates with other nodes.
  97. *
  98. * @param port the port number you wish to use for incoming
  99. * connections. Specifying 0 lets the system choose an available
  100. * port.
  101. *
  102. * @exception IOException if communication could not be initialized.
  103. *
  104. **/
  105. public OtpNode(System.String node, System.String cookie, int port):base(node, cookie)
  106. {
  107. init(port);
  108. }
  109. private void init(int port)
  110. {
  111. lock(this)
  112. {
  113. if (!initDone)
  114. {
  115. connections = new System.Collections.Hashtable(17, (float) 0.95);
  116. mboxes = new Mailboxes(this);
  117. acceptor = new Acceptor(this, port);
  118. initDone = true;
  119. }
  120. }
  121. }
  122. /*
  123. * Close the node. Unpublish the node from Epmd (preventing new
  124. * connections) and close all existing connections.
  125. **/
  126. public virtual void close()
  127. {
  128. lock(this)
  129. {
  130. acceptor.quit();
  131. OtpCookedConnection conn;
  132. System.Collections.IDictionaryEnumerator it = connections.GetEnumerator();
  133. mboxes.clear();
  134. it.Reset();
  135. while (it.MoveNext())
  136. {
  137. conn = (OtpCookedConnection) it.Value;
  138. conn.close();
  139. }
  140. initDone = false;
  141. }
  142. }
  143. ~OtpNode()
  144. {
  145. close();
  146. }
  147. /*
  148. * Create an unnamed {@link OtpMbox mailbox} that can be used to
  149. * send and receive messages with other, similar mailboxes and with
  150. * Erlang processes. Messages can be sent to this mailbox by using
  151. * its associated {@link OtpMbox#self pid}.
  152. *
  153. * @return a mailbox.
  154. **/
  155. public virtual OtpMbox createMbox()
  156. {
  157. return mboxes.create();
  158. }
  159. /*
  160. * Close the specified mailbox.
  161. *
  162. * @param the mailbox to close.
  163. *
  164. * <p> After this operation, the mailbox will no longer be able to
  165. * receive messages. Any delivered but as yet unretrieved messages
  166. * can still be retrieved however. </p>
  167. *
  168. * <p> If there are links from the mailbox to other {@link
  169. * OtpErlangPid pids}, they will be broken when this method is
  170. * called and exit signals will be sent. </p>
  171. *
  172. **/
  173. public virtual void closeMbox(OtpMbox mbox)
  174. {
  175. if (mbox != null)
  176. {
  177. mboxes.remove(mbox);
  178. mbox.name = null;
  179. mbox.breakLinks("normal");
  180. }
  181. }
  182. /*
  183. * Create an named mailbox that can be used to send and receive
  184. * messages with other, similar mailboxes and with Erlang processes.
  185. * Messages can be sent to this mailbox by using its registered name
  186. * or the associated {@link OtpMbox#self pid}.
  187. *
  188. * @param name a name to register for this mailbox. The name must be
  189. * unique within this OtpNode.
  190. *
  191. * @return a mailbox, or null if the name was already in use.
  192. *
  193. **/
  194. public virtual OtpMbox createMbox(System.String name)
  195. {
  196. return mboxes.create(name);
  197. }
  198. /*
  199. * <p> Register or remove a name for the given mailbox. Registering
  200. * a name for a mailbox enables others to send messages without
  201. * knowing the {@link OtpErlangPid pid} of the mailbox. A mailbox
  202. * can have at most one name; if the mailbox already had a name,
  203. * calling this method will supercede that name. </p>
  204. *
  205. * @param name the name to register for the mailbox. Specify null to
  206. * unregister the existing name from this mailbox.
  207. *
  208. * @param mbox the mailbox to associate with the name.
  209. *
  210. * @return true if the name was available, or false otherwise.
  211. **/
  212. public virtual bool registerName(System.String name, OtpMbox mbox)
  213. {
  214. return mboxes.register(name, mbox);
  215. }
  216. /*
  217. * Get a list of all known registered names on this node.
  218. *
  219. * @return an array of Strings, containins all known registered
  220. * names on this node.
  221. **/
  222. public virtual System.String[] getNames()
  223. {
  224. return mboxes.names();
  225. }
  226. /*
  227. * Determine the {@link Pid pid} corresponding to a
  228. * registered name on this node.
  229. *
  230. * @return the {@link Pid pid} corresponding to the
  231. * registered name, or null if the name is not known on this node.
  232. **/
  233. public virtual Erlang.Pid whereis(System.String name)
  234. {
  235. OtpMbox m = mboxes.get(name);
  236. if (m != null)
  237. return m.self();
  238. return null;
  239. }
  240. /*
  241. * Register interest in certain system events. The {@link
  242. * OtpNodeStatus OtpNodeStatus} handler object contains callback
  243. * methods, that will be called when certain events occur.
  244. *
  245. * @param handler the callback object to register. To clear the
  246. * handler, specify null as the handler to use.
  247. *
  248. **/
  249. public virtual void registerStatusHandler(OtpNodeStatus handler)
  250. {
  251. lock(this)
  252. {
  253. this.handler = handler;
  254. }
  255. }
  256. /*
  257. * <p> Determine if another node is alive. This method has the side
  258. * effect of setting up a connection to the remote node (if
  259. * possible). Only a single outgoing message is sent; the timeout is
  260. * how long to wait for a response. </p>
  261. *
  262. * <p> Only a single attempt is made to connect to the remote node,
  263. * so for example it is not possible to specify an extremely long
  264. * timeout and expect to be notified when the node eventually comes
  265. * up. If you wish to wait for a remote node to be started, the
  266. * following construction may be useful: </p>
  267. *
  268. <pre>
  269. // ping every 2 seconds until positive response
  270. while (!me.ping(him,2000));
  271. </pre>
  272. *
  273. * @param node the name of the node to ping.
  274. *
  275. * @param timeout the time, in milliseconds, to wait for response
  276. * before returning false.
  277. *
  278. * @return true if the node was alive and the correct ping response
  279. * was returned. false if the correct response was not returned on
  280. * time.
  281. **/
  282. /*internal info about the message formats...
  283. *
  284. * the request:
  285. * -> REG_SEND {6,#Pid<bingo@aule.1.0>,'',net_kernel}
  286. * {'$gen_call',{#Pid<bingo@aule.1.0>,#Ref<bingo@aule.2>},{is_auth,bingo@aule}}
  287. *
  288. * the reply:
  289. * <- SEND {2,'',#Pid<bingo@aule.1.0>}
  290. * {#Ref<bingo@aule.2>,yes}
  291. */
  292. public virtual bool ping(System.String node, long timeout)
  293. {
  294. if (node.Equals(this._node))
  295. return true;
  296. OtpMbox mbox = null;
  297. try
  298. {
  299. mbox = createMbox();
  300. mbox.send("net_kernel", node, getPingTuple(mbox));
  301. Erlang.Object reply = mbox.receive(timeout);
  302. Erlang.Tuple t = (Erlang.Tuple) reply;
  303. Erlang.Atom a = (Erlang.Atom) (t.elementAt(1));
  304. return "yes".Equals(a.atomValue());
  305. }
  306. catch (System.Exception)
  307. {
  308. }
  309. finally
  310. {
  311. closeMbox(mbox);
  312. }
  313. return false;
  314. }
  315. /*create the outgoing ping message */
  316. private Erlang.Tuple getPingTuple(OtpMbox mbox)
  317. {
  318. Erlang.Object[] ping = new Erlang.Object[3];
  319. Erlang.Object[] pid = new Erlang.Object[2];
  320. Erlang.Object[] _node = new Erlang.Object[2];
  321. pid[0] = mbox.self();
  322. pid[1] = createRef();
  323. _node[0] = new Erlang.Atom("is_auth");
  324. _node[1] = new Erlang.Atom(node());
  325. ping[0] = new Erlang.Atom("$gen_call");
  326. ping[1] = new Erlang.Tuple(pid);
  327. ping[2] = new Erlang.Tuple(_node);
  328. return new Erlang.Tuple(ping);
  329. }
  330. /*
  331. * this method simulates net_kernel only for
  332. * the purpose of replying to pings.
  333. */
  334. private bool netKernel(OtpMsg m)
  335. {
  336. OtpMbox mbox = null;
  337. try
  338. {
  339. Erlang.Tuple t = (Erlang.Tuple) (m.getMsg());
  340. Erlang.Tuple req = (Erlang.Tuple) t.elementAt(1); // actual request
  341. Erlang.Pid pid = (Erlang.Pid) req.elementAt(0); // originating pid
  342. Erlang.Object[] pong = new Erlang.Object[2];
  343. pong[0] = req.elementAt(1); // his #Ref
  344. pong[1] = new Erlang.Atom("yes");
  345. mbox = createMbox();
  346. mbox.send(pid, new Erlang.Tuple(pong));
  347. return true;
  348. }
  349. catch (System.Exception)
  350. {
  351. }
  352. finally
  353. {
  354. closeMbox(mbox);
  355. }
  356. return false;
  357. }
  358. /*
  359. * OtpCookedConnection delivers messages here
  360. * return true if message was delivered successfully, or false otherwise.
  361. */
  362. internal virtual bool deliver(OtpMsg m)
  363. {
  364. OtpMbox mbox = null;
  365. try
  366. {
  367. int t = m.type();
  368. if (t == OtpMsg.regSendTag)
  369. {
  370. System.String name = m.getRecipientName();
  371. /*special case for netKernel requests */
  372. if (name.Equals("net_kernel"))
  373. return netKernel(m);
  374. else
  375. mbox = mboxes.get(name);
  376. }
  377. else
  378. {
  379. mbox = mboxes.get(m.getRecipientPid());
  380. }
  381. if (mbox == null)
  382. return false;
  383. mbox.deliver(m);
  384. }
  385. catch (System.Exception)
  386. {
  387. return false;
  388. }
  389. return true;
  390. }
  391. /*
  392. * OtpCookedConnection delivers errors here, we send them on to the
  393. * handler specified by the application
  394. */
  395. internal virtual void deliverError(OtpCookedConnection conn, System.Exception e)
  396. {
  397. removeConnection(conn);
  398. remoteStatus(conn.name, false, e);
  399. }
  400. /*
  401. * find or create a connection to the given node
  402. */
  403. internal virtual OtpCookedConnection getConnection(System.String node)
  404. {
  405. OtpPeer peer = null;
  406. OtpCookedConnection conn = null;
  407. lock(connections)
  408. {
  409. // first just try looking up the name as-is
  410. conn = (OtpCookedConnection) connections[node];
  411. if (conn == null)
  412. {
  413. // in case node had no '@' add localhost info and try again
  414. peer = new OtpPeer(node);
  415. conn = (OtpCookedConnection) connections[peer.node()];
  416. if (conn == null)
  417. {
  418. try
  419. {
  420. conn = new OtpCookedConnection(this, peer);
  421. addConnection(conn);
  422. }
  423. catch (System.Exception e)
  424. {
  425. /*false = outgoing */
  426. connAttempt(peer.node(), false, e);
  427. }
  428. }
  429. }
  430. return conn;
  431. }
  432. }
  433. private void addConnection(OtpCookedConnection conn)
  434. {
  435. if ((conn != null) && (conn.name != null))
  436. {
  437. connections[conn.name] = conn;
  438. remoteStatus(conn.name, true, null);
  439. }
  440. }
  441. private void removeConnection(OtpCookedConnection conn)
  442. {
  443. if ((conn != null) && (conn.name != null))
  444. {
  445. connections.Remove(conn.name);
  446. }
  447. }
  448. /*use these wrappers to call handler functions */
  449. private void remoteStatus(System.String node, bool up, System.Object info)
  450. {
  451. lock(this)
  452. {
  453. if (handler == null)
  454. return ;
  455. try
  456. {
  457. handler.remoteStatus(node, up, info);
  458. }
  459. catch (System.Exception)
  460. {
  461. }
  462. }
  463. }
  464. private void localStatus(System.String node, bool up, System.Object info)
  465. {
  466. lock(this)
  467. {
  468. if (handler == null)
  469. return ;
  470. try
  471. {
  472. handler.localStatus(node, up, info);
  473. }
  474. catch (System.Exception)
  475. {
  476. }
  477. }
  478. }
  479. private void connAttempt(System.String node, bool incoming, System.Object info)
  480. {
  481. lock(this)
  482. {
  483. if (handler == null)
  484. return ;
  485. try
  486. {
  487. handler.connAttempt(node, incoming, info);
  488. }
  489. catch (System.Exception)
  490. {
  491. }
  492. }
  493. }
  494. /*this class used to wrap the mailbox hashtables so we can use weak references */
  495. public class Mailboxes
  496. {
  497. private void InitBlock(OtpNode enclosingInstance)
  498. {
  499. this.enclosingInstance = enclosingInstance;
  500. }
  501. private OtpNode enclosingInstance;
  502. internal System.Collections.Hashtable byPid = null; // mbox pids here
  503. private System.Collections.Hashtable byName = null; // mbox names here
  504. public Mailboxes(OtpNode enclosingInstance)
  505. {
  506. InitBlock(enclosingInstance);
  507. byPid = new System.Collections.Hashtable(17, (float) 0.95);
  508. byName = new System.Collections.Hashtable(17, (float) 0.95);
  509. }
  510. public virtual OtpMbox create(System.String name)
  511. {
  512. OtpMbox m = null;
  513. lock(byName)
  514. {
  515. if (get(name) != null)
  516. return null;
  517. Erlang.Pid pid = enclosingInstance.createPid();
  518. m = new OtpMbox(enclosingInstance, pid, name);
  519. byPid[pid] = new WeakReference(m);
  520. byName[name] = new WeakReference(m);
  521. }
  522. return m;
  523. }
  524. public virtual OtpMbox create()
  525. {
  526. Erlang.Pid pid = enclosingInstance.createPid();
  527. OtpMbox m = new OtpMbox(enclosingInstance, pid);
  528. byPid[pid] = new WeakReference(m);
  529. return m;
  530. }
  531. public virtual void clear()
  532. {
  533. byPid.Clear();
  534. byName.Clear();
  535. }
  536. public virtual System.String[] names()
  537. {
  538. System.String[] allnames = null;
  539. lock(byName)
  540. {
  541. int n = byName.Count;
  542. System.Collections.IDictionaryEnumerator keys = byName.GetEnumerator();
  543. allnames = new System.String[n];
  544. int i = 0;
  545. keys.Reset();
  546. while (keys.MoveNext())
  547. {
  548. allnames[i++] = (System.String) (keys.Key);
  549. }
  550. }
  551. return allnames;
  552. }
  553. public virtual System.String[] pids()
  554. {
  555. System.String[] allpids = null;
  556. lock(byPid)
  557. {
  558. int n = byPid.Count;
  559. System.Collections.IDictionaryEnumerator keys = byPid.GetEnumerator();
  560. allpids = new System.String[n];
  561. int i = 0;
  562. keys.Reset();
  563. while (keys.MoveNext())
  564. {
  565. allpids[i++] = ((Erlang.Pid) (keys.Key)).ToString();
  566. }
  567. }
  568. return allpids;
  569. }
  570. public virtual bool register(System.String name, OtpMbox mbox)
  571. {
  572. if (name == null)
  573. {
  574. if (mbox.name != null)
  575. {
  576. byName.Remove(mbox.name);
  577. mbox.name = null;
  578. }
  579. }
  580. else
  581. {
  582. lock(byName)
  583. {
  584. if (get(name) != null)
  585. return false;
  586. byName[name] = new WeakReference(mbox);
  587. mbox.name = name;
  588. }
  589. }
  590. return true;
  591. }
  592. /*look up a mailbox based on its name. If the mailbox has gone out
  593. * of scope we also remove the reference from the hashtable so we
  594. * don't find it again.
  595. */
  596. public virtual OtpMbox get(System.String name)
  597. {
  598. WeakReference wr = (WeakReference) byName[name];
  599. if (wr != null)
  600. {
  601. OtpMbox m = (OtpMbox) wr.Target;
  602. if (m != null)
  603. return m;
  604. byName.Remove(name);
  605. }
  606. return null;
  607. }
  608. /*look up a mailbox based on its pid. If the mailbox has gone out
  609. * of scope we also remove the reference from the hashtable so we
  610. * don't find it again.
  611. */
  612. public virtual OtpMbox get(Erlang.Pid pid)
  613. {
  614. WeakReference wr = (WeakReference) byPid[pid];
  615. if (wr != null)
  616. {
  617. OtpMbox m = (OtpMbox) wr.Target;
  618. if (m != null)
  619. return m;
  620. byPid.Remove(pid);
  621. }
  622. return null;
  623. }
  624. public virtual void remove(OtpMbox mbox)
  625. {
  626. byPid.Remove(mbox._self);
  627. if (mbox.name != null)
  628. byName.Remove(mbox.name);
  629. }
  630. }
  631. /*
  632. * this thread simply listens for incoming connections
  633. */
  634. public class Acceptor
  635. {
  636. private void InitBlock(OtpNode a_node)
  637. {
  638. this.a_node = a_node;
  639. }
  640. private OtpNode a_node;
  641. private System.Net.Sockets.TcpListener a_sock;
  642. private int a_port;
  643. private bool a_done = false;
  644. private System.Threading.Thread thread;
  645. internal Acceptor(OtpNode a_node, int port)
  646. {
  647. InitBlock(a_node);
  648. a_sock = new System.Net.Sockets.TcpListener(System.Net.Dns.Resolve("localhost").AddressList[0], port);
  649. a_sock.Start();
  650. this.a_port = ((System.Net.IPEndPoint)a_sock.LocalEndpoint).Port;
  651. a_node._port = this.a_port;
  652. publishPort();
  653. thread = new System.Threading.Thread(new System.Threading.ThreadStart(Start));
  654. thread.IsBackground = true;
  655. thread.Name = "acceptor "+a_node.node();
  656. thread.Start();
  657. }
  658. private bool publishPort()
  659. {
  660. if (a_node.getEpmd() != null)
  661. return false;
  662. // already published
  663. OtpEpmd.publishPort(a_node);
  664. return true;
  665. }
  666. private void unPublishPort()
  667. {
  668. // unregister with epmd
  669. OtpEpmd.unPublishPort(a_node);
  670. // close the local descriptor (if we have one)
  671. closeSock(a_node.epmd);
  672. a_node.epmd = null;
  673. }
  674. public virtual void quit()
  675. {
  676. unPublishPort();
  677. a_done = true;
  678. closeSock(a_sock);
  679. //thread.Interrupt();
  680. }
  681. private void closeSock(System.Net.Sockets.TcpListener s)
  682. {
  683. try
  684. {
  685. if (s != null)
  686. s.Stop();
  687. }
  688. catch (System.Exception)
  689. {
  690. }
  691. }
  692. private void closeSock(System.Net.Sockets.TcpClient s)
  693. {
  694. try
  695. {
  696. if (s != null)
  697. s.Close();
  698. }
  699. catch (System.Exception)
  700. {
  701. }
  702. }
  703. public virtual int port()
  704. {
  705. return this.a_port;
  706. }
  707. public void Start()
  708. {
  709. System.Net.Sockets.TcpClient newsock = null;
  710. OtpCookedConnection conn = null;
  711. a_node.localStatus(a_node._node, true, null);
  712. while (!a_done)
  713. {
  714. conn = null;
  715. try
  716. {
  717. newsock = a_sock.AcceptTcpClient();
  718. }
  719. catch (System.Exception e)
  720. {
  721. a_node.localStatus(a_node._node, false, e);
  722. goto accept_loop_brk;
  723. }
  724. try
  725. {
  726. lock(a_node.connections)
  727. {
  728. conn = new OtpCookedConnection(a_node, newsock);
  729. a_node.addConnection(conn);
  730. }
  731. }
  732. catch (OtpAuthException e)
  733. {
  734. if (conn != null && conn.name != null)
  735. a_node.connAttempt(conn.name, true, e);
  736. else
  737. a_node.connAttempt("unknown", true, e);
  738. closeSock(newsock);
  739. }
  740. catch (System.IO.IOException e)
  741. {
  742. if (conn != null && conn.name != null)
  743. a_node.connAttempt(conn.name, true, e);
  744. else
  745. a_node.connAttempt("unknown", true, e);
  746. closeSock(newsock);
  747. }
  748. catch (System.Exception e)
  749. {
  750. closeSock(newsock);
  751. closeSock(a_sock);
  752. a_node.localStatus(a_node._node, false, e);
  753. goto accept_loop_brk;
  754. }
  755. }
  756. accept_loop_brk: ;
  757. // while
  758. // if we have exited loop we must do this too
  759. unPublishPort();
  760. }
  761. }
  762. }
  763. }