internal static void LogNodeInMessageException(SerializedRelayMessage[] messages, Node node, Exception ex)
		{
			if (_log.IsErrorEnabled)
			{
				if (ex is SocketException)
				{
					SocketException sex = (SocketException)ex;
					_log.ErrorFormat("Socket error {0} while handling {1} IN messages for node {2}", sex.SocketErrorCode, messages.Length, node.ToExtendedString());
				}
				else
				{
					_log.ErrorFormat("Error handling {0} IN messages for node {1}: {2}.", messages.Length, node.ToExtendedString(), ex);
				}
			}
		}
		internal static void LogNodeOutMessageException(List<RelayMessage> messages, Node node, Exception ex)
		{
			if (_log.IsErrorEnabled)
			{
				if (ex is SocketException)
				{
					SocketException sex = (SocketException)ex;
					_log.ErrorFormat("Socket error {0} while handling {1} OUT messages for node {2}", sex.SocketErrorCode, messages.Count, node.ToExtendedString());
				}
				else
				{
					_log.ErrorFormat("Error handling {0} OUT messages for node {1}: {2}", messages.Count, node.ToExtendedString(), ex.ToString());
				}
			}
		}
		internal NodeCluster(RelayNodeClusterDefinition clusterDefinition, RelayNodeConfig nodeConfig, NodeGroup nodeGroup, ForwardingConfig forwardingConfig)
		{
			_nodeGroup = nodeGroup;
			_clusterDefinition = clusterDefinition;
			_minimumId = clusterDefinition.MinId;
			_maximumId = clusterDefinition.MaxId;
			NodeSelectionHopWindowSize = nodeGroup.NodeSelectionHopWindowSize;
			RelayNodeDefinition meDefinition = nodeConfig.GetMyNode();
			_meInThisCluster = false;
			_mapNetwork = forwardingConfig.MapNetwork;
			_localZone = nodeConfig.GetLocalZone();
			_zoneDefinitions = nodeConfig.RelayNodeMapping.ZoneDefinitions;
			foreach (RelayNodeDefinition nodeDefinition in clusterDefinition.RelayNodes)
			{
				if (meDefinition == nodeDefinition)
				{
					_meInThisCluster = true;
				}
			}
			
			DispatcherQueue nodeInQueue, nodeOutQueue;

			if (_meInThisCluster)
			{
				GetMessageQueuesFor(meDefinition, clusterDefinition,
					NodeManager.Instance.InMessageDispatcher, NodeManager.Instance.OutMessageDispatcher,
					out nodeInQueue, out nodeOutQueue);

				Me = new Node(meDefinition, nodeGroup, this, forwardingConfig,
						nodeInQueue, nodeOutQueue);
			}			

			ushort maxDetectedZone = _localZone;
			foreach (RelayNodeDefinition nodeDefinition in clusterDefinition.RelayNodes)
			{
				if (nodeDefinition != meDefinition)
				{
					GetMessageQueuesFor(nodeDefinition, clusterDefinition,
						NodeManager.Instance.InMessageDispatcher, NodeManager.Instance.OutMessageDispatcher,
						out nodeInQueue, out nodeOutQueue);
					Node node = new Node(nodeDefinition, nodeGroup, this, forwardingConfig,
						nodeInQueue, nodeOutQueue);

					Nodes.Add(node);

					if (node.DetectedZone > maxDetectedZone)
					{
						maxDetectedZone = node.DetectedZone;
					}
					if (node.Zone > maxDetectedZone)
					{
						maxDetectedZone = node.Zone;
					}

					if (!ZoneNodes.ContainsKey(nodeDefinition.Zone))
					{
						ZoneNodes[nodeDefinition.Zone] = new List<Node>();
					}

					ZoneNodes[nodeDefinition.Zone].Add(node);
				}				
			}

			_nodesByNumberOfHops = CalculateTopography(Nodes, MaximumHops);
			_nodeLayers = CalculateNodeLayers(_nodesByNumberOfHops, NodeSelectionHopWindowSize);
			_nodesByDetectedZone = CalculateNodesByDetectedZone(Nodes, maxDetectedZone);
		}
		internal void ReselectNode()
		{
			lock (_chooseLock)
			{
				ChosenNode = null;
			}
		}
		private Node GetChosenNode()
		{
			
			if (Nodes.Count == 0)
			{
				return null;
			}
			Node chosenNode = ChosenNode;
			//could potentially be nulled here, after selection made, 
			//in that case it wouldn't be re-chosen. that's ok, it will be next time
			if (chosenNode == null || !chosenNode.Activated || chosenNode.DangerZone)
			{
				lock (_chooseLock)
				{   
					if (ChosenNode == null || !ChosenNode.Activated || ChosenNode.DangerZone)
					{
						if (_mapNetwork || _nodesByDetectedZone == null)
						{
							chosenNode = SelectANodeIncrementally(_nodeLayers, Randomizer);
						}
						else
						{
							chosenNode = SelectANodeByZone(_nodesByDetectedZone, Randomizer, _localZone);
						}
						ChosenNode = chosenNode; 
					}
				}
				//even if ChosenNode is nulled out before we can return,
				//the local variable will already have a node so we 
				//won't falsely return null
			}
			
			return chosenNode; 
		}
		internal void ReloadMapping(RelayNodeClusterDefinition relayNodeClusterDefinition, RelayNodeConfig newConfig, ForwardingConfig forwardingConfig)
		{
			_minimumId = relayNodeClusterDefinition.MinId;
			_maximumId = relayNodeClusterDefinition.MaxId;
			_mapNetwork = forwardingConfig.MapNetwork;
			
			//figure out if anything changed. if it did, rebuild
			
			bool rebuild = false;

			ushort newLocalZone = newConfig.GetLocalZone();
			if (newLocalZone != _localZone)
			{
				rebuild = true;
				_localZone = newLocalZone;
			}
			else
			{
				if((_zoneDefinitions == null && newConfig.RelayNodeMapping.ZoneDefinitions != null) ||
				(_zoneDefinitions != null && newConfig.RelayNodeMapping.ZoneDefinitions == null) ||
				(_zoneDefinitions != null && !_zoneDefinitions.Equals(newConfig.RelayNodeMapping.ZoneDefinitions)))
				{
					rebuild = true;
					_zoneDefinitions = newConfig.RelayNodeMapping.ZoneDefinitions;
				}
				
			}
			

			int effectiveSize = (!_meInThisCluster ? Nodes.Count : Nodes.Count + 1);
			
			//if there's a different number of nodes, we definitely have to rebuild
			if (relayNodeClusterDefinition.RelayNodes.Length != effectiveSize)
			{
				if(_log.IsInfoEnabled)
					_log.InfoFormat("Number of nodes in a cluster in group {0} changed from {1} to {2}, rebuilding", _nodeGroup.GroupName, effectiveSize, relayNodeClusterDefinition.RelayNodes.Length);				
				rebuild = true;
			}
			else
			{
				//if any of the nodes we do have aren't in the config, rebuild				
				foreach (Node node in Nodes)
				{
					if (!relayNodeClusterDefinition.ContainsNode(node.EndPoint.Address, node.EndPoint.Port))
					{
						if (_log.IsInfoEnabled)
							_log.InfoFormat("Node {0} is no longer found in its cluster in group {1}, rebuilding.",
								node, _nodeGroup.GroupName);
						rebuild = true;						
						break;
					}
				}
				if (!rebuild && _nodeGroup.NodeSelectionHopWindowSize != NodeSelectionHopWindowSize)
				{
					NodeSelectionHopWindowSize = _nodeGroup.NodeSelectionHopWindowSize;
					rebuild = true;                    
				}

				if (!rebuild && _meInThisCluster)
				{
					if (!relayNodeClusterDefinition.ContainsNode(Me.EndPoint.Address, Me.EndPoint.Port))
					{
						if (_log.IsInfoEnabled)
							_log.InfoFormat("Node {0} (this machine) is no longer found in its cluster in group {1}, rebuilding.",
								Me, _nodeGroup.GroupName);
						rebuild = true;						
					}
				}

				//or if there are any nodes in the config that aren't here, rebuild
				if (!rebuild)
				{
					foreach(RelayNodeDefinition nodeDefinition in relayNodeClusterDefinition.RelayNodes)
					{
						if (!ContainsNode(nodeDefinition))
						{
							if (_log.IsInfoEnabled)
								_log.InfoFormat("Node {0} is defined in the new config but does not exist in this cluster in group {1}, rebuilding.",
									nodeDefinition, _nodeGroup.GroupName);
							rebuild = true;
							break;
						}
					}
				}
			}

			if (rebuild)
			{				
				Dictionary<ushort, List<Node>> newZoneNodes = new Dictionary<ushort, List<Node>>();		
				List<Node> newNodes = new List<Node>();

				RelayNodeDefinition meDefinition = newConfig.GetMyNode();
				DispatcherQueue nodeInQueue, nodeOutQueue;
				
				if (meDefinition != null)
				{                    
					GetMessageQueuesFor(meDefinition, relayNodeClusterDefinition,
						NodeManager.Instance.InMessageDispatcher, NodeManager.Instance.OutMessageDispatcher,
						out nodeInQueue, out nodeOutQueue);
					//Me is in the new config
					//Either create it new or overwrite the old one					
					Me = new Node(meDefinition, _nodeGroup, this, forwardingConfig, 
						nodeInQueue, nodeOutQueue);

				}
				else
				{
					//me is NOT in the new config.
					Me = null;
				}
				ushort maxDetectedZone = _localZone;
				foreach (RelayNodeDefinition nodeDefinition in relayNodeClusterDefinition.RelayNodes)
				{					
					if (nodeDefinition != meDefinition)
					{
						GetMessageQueuesFor(nodeDefinition, relayNodeClusterDefinition,
						NodeManager.Instance.InMessageDispatcher, NodeManager.Instance.OutMessageDispatcher,
						out nodeInQueue, out nodeOutQueue);

						Node node = new Node(nodeDefinition, _nodeGroup, this, forwardingConfig,
							nodeInQueue, nodeOutQueue);

						newNodes.Add(node);
						if (node.DetectedZone > maxDetectedZone)
						{
							maxDetectedZone = node.DetectedZone;
						}
						if (node.Zone > maxDetectedZone)
						{
							maxDetectedZone = node.Zone;
						}
						if (!newZoneNodes.ContainsKey(nodeDefinition.Zone))
						{
							newZoneNodes[nodeDefinition.Zone] = new List<Node>();
						}

						newZoneNodes[nodeDefinition.Zone].Add(node);

					}
				}
				Nodes = newNodes;
				ZoneNodes = newZoneNodes;
				_nodesByNumberOfHops = CalculateTopography(Nodes, MaximumHops);                
				_nodeLayers = CalculateNodeLayers(_nodesByNumberOfHops, NodeSelectionHopWindowSize);
				_nodesByDetectedZone = CalculateNodesByDetectedZone(Nodes, maxDetectedZone);
				lock (_chooseLock)
				{
					ChosenNode = null;
				}
				ChosenZoneNodes = new Dictionary<ushort, Node>();
			}
			else
			{	
				//just reload the configs to get any new network or queue settings
				bool hitMe = false;
				string meString = String.Empty;
				if (Me != null)
				{
					meString = Me.ToString();
				}
				for (int i = 0; i < relayNodeClusterDefinition.RelayNodes.Length; i++)
				{
					string definitionString = relayNodeClusterDefinition.RelayNodes[i].Host + ":" + relayNodeClusterDefinition.RelayNodes[i].Port;
					if (definitionString == meString)
					{
						hitMe = true;
						Me.ReloadMapping(relayNodeClusterDefinition.RelayNodes[i], forwardingConfig);
					}
					else
					{						
						Nodes[(hitMe ? i - 1 : i)].ReloadMapping(relayNodeClusterDefinition.RelayNodes[i],forwardingConfig);						
					}
				}
				lock (_chooseLock)
				{
					ChosenNode = null;
				}
			}
		}