PageRenderTime 136ms CodeModel.GetById 89ms app.highlight 25ms RepoModel.GetById 17ms app.codeStats 1ms

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

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