/// <summary> /// Depart from the Chord ring and stop maintenance. /// </summary> public void Depart() { // first, stop maintenance so nothing gets broken StopMaintenance(); try { // courteously introduce departing node's successor and predecessor // ...successor, meet predecessor; predecessor, meet successor ChordInstance instance = ChordServer.GetInstance(this.Successor); instance.Predecessor = this.Predecessor; instance = ChordServer.GetInstance(this.Predecessor); instance.Successor = this.Successor; } catch (Exception e) { ChordServer.Log(LogLevel.Error, "Navigation", "Error on Depart ({0}).", e.Message); } finally { // set local state in such a way as to force it out of the Chord ring this.Successor = ChordServer.LocalNode; this.Predecessor = ChordServer.LocalNode; this.FingerTable = new ChordFingerTable(ChordServer.LocalNode); for (int i = 0; i < this.SuccessorCache.Length; i++) { this.SuccessorCache[i] = ChordServer.LocalNode; } } }
/// <summary> /// Maintenance task to stabilize the local node's predecessor as per the Chord paper. /// </summary> /// <param name="sender">The backgroundworker thread that this task is running on.</param> /// <param name="ea">Args (ignored)</param> private void StabilizePredecessors(object sender, DoWorkEventArgs ea) { BackgroundWorker me = (BackgroundWorker)sender; while (!me.CancellationPending) { if (this.Predecessor != null) { try { // validate predecessor (in case of error, predecessor becomes null // and is fixed by stabilizesuccessors and notify. ChordInstance instance = ChordServer.GetInstance(this.Predecessor); if (!ChordServer.IsInstanceValid(instance)) { this.Predecessor = null; } } catch (Exception e) { ChordServer.Log(LogLevel.Error, "StabilizePredecessors", "StabilizePredecessors error: {0}", e.Message); this.Predecessor = null; } } // TODO: make this configurable either via config file or passed in via arguments. Thread.Sleep(5000); } }
/// <summary> /// Maintenance task to ensure that the local node has valid successor node. Roughly equivalent /// to what is called out in the Chord paper. /// </summary> /// <param name="sender">The worker thread the task is running on.</param> /// <param name="ea">Args (ignored here).</param> private void StabilizeSuccessors(object sender, DoWorkEventArgs ea) { BackgroundWorker me = (BackgroundWorker)sender; while (!me.CancellationPending) { try { // check in successor and if it's bad, replace it with // the next live entry in the successor cache ChordNode succPredNode = ChordServer.GetPredecessor(this.Successor); if (succPredNode != null) { if (ChordServer.IsIDInRange(succPredNode.ID, this.ID, this.Successor.ID)) { this.Successor = succPredNode; } // ignoring return because bad node will be detected on next invocation ChordServer.CallNotify(this.Successor, ChordServer.LocalNode); GetSuccessorCache(this.Successor); } else { bool successorCacheHelped = false; foreach (ChordNode entry in this.m_SuccessorCache) { ChordInstance instance = ChordServer.GetInstance(entry); if (ChordServer.IsInstanceValid(instance)) { this.Successor = entry; ChordServer.CallNotify(this.Successor, ChordServer.LocalNode); GetSuccessorCache(this.Successor); successorCacheHelped = true; break; } } // if we get here, then we got no help and have no other recourse than to re-join using the initial seed... if (!successorCacheHelped) { ChordServer.Log(LogLevel.Error, "StabilizeSuccessors", "Ring consistency error, Re-Joining Chord ring."); Join(this.m_SeedNode, this.Host, this.Port); return; } } } catch (Exception e) { ChordServer.Log(LogLevel.Error, "Maintenance", "Error occured during StabilizeSuccessors ({0})", e.Message); } // TODO: this could be tweaked and/or made configurable elsewhere or passed in as arguments Thread.Sleep(5000); } }
/// <summary> /// Maintenance task to perform ring consistency checking and re-joining to keep a Chord /// ring stable under extreme churn and in cases of ring damage. /// </summary> /// <param name="sender">The calling backgroundworker.</param> /// <param name="ea">Args (ignored for this task).</param> private void ReJoin(object sender, DoWorkEventArgs ea) { BackgroundWorker me = (BackgroundWorker)sender; while (!me.CancellationPending) { try { // if this is the first iteration, then the core logic // is skipped, as the first iteration generally occurs // right after node Join - allowing a short buffer for // routing structures to stabilize improves the utility // of the ReJoin facility. if (this.m_HasReJoinRun) { // first find the successor for the seed node if (this.m_SeedNode != null) { ChordNode seedSuccessor = FindSuccessor(this.m_SeedNode.ID); // if the successor is not equal to the seed node, something is fishy if (seedSuccessor.ID != this.m_SeedNode.ID) { // if the seed node is still active, re-join the ring to the seed node ChordInstance instance = ChordServer.GetInstance(this.m_SeedNode); if (ChordServer.IsInstanceValid(instance)) { ChordServer.Log(LogLevel.Error, "ReJoin", "Unable to contact initial seed node {0}. Re-Joining...", this.m_SeedNode); Join(this.m_SeedNode, this.Host, this.Port); } // otherwise, in the future, there will be a cache of seed nodes to check/join from... // as it may be the case that the seed node simply has disconnected from the network. } } } else { // subsequent iterations will go through the core logic this.m_HasReJoinRun = true; } } catch (Exception e) { ChordServer.Log(LogLevel.Error, "Maintenance", "Error occured during ReJoin ({0})", e.Message); } // TODO: the delay between iterations, here, is configurable, and // ideally should be retrieved from configuration or otherwise passed in... Thread.Sleep(30000); } }
/// <summary> /// Safely checks whether a ChordInstance is valid by ensuring the port and successor values are valid. /// </summary> /// <param name="instance">The ChordInstance to validity-check.</param> /// <returns>TRUE if valid; FALSE otherwise.</returns> public static bool IsInstanceValid(ChordInstance instance) { try { if (instance.Port > 0 && instance.Successor != null) { return(true); } else { return(false); } } catch (Exception e) { Log(LogLevel.Debug, "Incoming instance was not valid: ({0}).", e.ToString()); // TODO; better logging return(false); } }
/// <summary> /// Get a (local or remote) ChordInstance given a ChordNode. /// </summary> /// <param name="node">The ChordNode specifying the node to get an instance of.</param> /// <returns>A ChordInstance from the specified node, or null if an error is encountered.</returns> public static ChordInstance GetInstance(ChordNode node) { if (node == null) { ChordServer.Log(LogLevel.Error, "Navigation", "Invalid Node ({0}).", "Null Argument."); return(null); } try { ChordInstance retInstance = (ChordInstance)Activator.GetObject(typeof(ChordInstance), string.Format("tcp://{0}:{1}/chord", node.Host, node.PortNumber)); return(retInstance); } catch (Exception e) { // perhaps instead we should just pass on the error? ChordServer.Log(LogLevel.Error, "Navigation", "Unable to activate remote server {0}:{1} ({2}).", node.Host, node.PortNumber, e.Message); return(null); } }
/// <summary> /// Returns the closest successor preceding id. /// </summary> /// <param name="id">The id for which the closest finger should be found</param> /// <returns>The successor node of the closest finger to id in the current node's finger table</returns> private ChordNode FindClosestPrecedingFinger(UInt64 id) { // iterate downward through the finger table looking for the right finger in the right range. if the finger is // in the range but not valid, keep moving. if the entire finger table is checked without success, check the successor // cache - if that fails, return the local node as the closest preceding finger. for (int i = this.FingerTable.Length - 1; i >= 0; i--) { // if the finger is more closely between the local node and id and that finger corresponds to a valid node, return the finger if (this.FingerTable.Successors[i] != null && this.FingerTable.Successors[i] != ChordServer.LocalNode) { if (ChordServer.FingerInRange(this.FingerTable.Successors[i].ID, this.ID, id)) { ChordInstance instance = ChordServer.GetInstance(this.FingerTable.Successors[i]); if (ChordServer.IsInstanceValid(instance)) { return(this.FingerTable.Successors[i]); } } } } // at this point, not even the successor is any good so go through the successor cache and run the same test for (int i = 0; i < this.SuccessorCache.Length; i++) { if (this.SuccessorCache[i] != null && this.SuccessorCache[i] != ChordServer.LocalNode) { if (ChordServer.FingerInRange(this.SuccessorCache[i].ID, this.ID, id)) { ChordInstance instance = ChordServer.GetInstance(this.SuccessorCache[i]); if (ChordServer.IsInstanceValid(instance)) { return(this.SuccessorCache[i]); } } } } // otherwise, if there is nothing closer, the local node is the closest preceding finger return(ChordServer.LocalNode); }
/// <summary> /// Calls AddKey remotely. /// </summary> /// <param name="remoteNode">The remote node on which to call AddKey.</param> /// <param name="value">The string value to add.</param> /// <param name="retryCount">The number of retries to attempt.</param> public static void CallAddKey(ChordNode remoteNode, string value, int retryCount) { ChordInstance instance = ChordServer.GetInstance(remoteNode); try { instance.AddKey(value); } catch (System.Exception ex) { ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallAddKey error: {0}", ex.Message); if (retryCount > 0) { CallAddKey(remoteNode, value, --retryCount); } else { ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallAddKey failed - error: {0}", ex.Message); } } }
/// <summary> /// Gets the remote Successor property, given a custom retry count. /// </summary> /// <param name="remoteNode">The remote node from which to access the property.</param> /// <param name="retryCount">The number of times to retry the operation in case of error.</param> /// <returns>The remote successor, or NULL in case of error.</returns> public static ChordNode GetSuccessor(ChordNode remoteNode, int retryCount) { ChordInstance instance = ChordServer.GetInstance(remoteNode); try { return(instance.Successor); } catch (System.Exception ex) { ChordServer.Log(LogLevel.Debug, "Remote Accessor", "GetSuccessor error: {0}", ex.Message); if (retryCount > 0) { return(GetSuccessor(remoteNode, --retryCount)); } else { return(null); } } }
/// <summary> /// Calls FindKey remotely. /// </summary> /// <param name="remoteNode">The remote node on which to call FindKey.</param> /// <param name="key">The key to look up.</param> /// <param name="retryCount">The number of retries to attempt.</param> /// <returns>The value corresponding to the key, or empty string if not found.</returns> public static string CallFindKey(ChordNode remoteNode, ulong key, int retryCount) { ChordInstance instance = ChordServer.GetInstance(remoteNode); try { return(instance.FindKey(key)); } catch (System.Exception ex) { ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallFindKey error: {0}", ex.Message); if (retryCount > 0) { return(CallFindKey(remoteNode, key, --retryCount)); } else { ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallFindKey failed - error: {0}", ex.Message); return(string.Empty); } } }
/// <summary> /// Calls FindSuccessor() remotely, using a default retry value of three. /// </summary> /// <param name="remoteNode">The remote node on which to call FindSuccessor().</param> /// <param name="id">The ID to look up.</param> /// <param name="retryCount">The number of times to retry the operation in case of error.</param> /// <param name="hopCountIn">The known hopcount prior to calling FindSuccessor on this node.</param> /// <param name="hopCountOut">The total hopcount of this operation (either returned upwards, or reported for hopcount efficiency validation).</param> /// <returns>The Successor of ID, or NULL in case of error.</returns> public static ChordNode CallFindSuccessor(ChordNode remoteNode, UInt64 id, int retryCount, int hopCountIn, out int hopCountOut) { ChordInstance instance = ChordServer.GetInstance(remoteNode); try { return(instance.FindSuccessor(id, hopCountIn, out hopCountOut)); } catch (System.Exception ex) { ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallFindSuccessor error: {0}", ex.Message); if (retryCount > 0) { return(CallFindSuccessor(remoteNode, id, --retryCount, hopCountIn, out hopCountOut)); } else { hopCountOut = hopCountIn; return(null); } } }
/// <summary> /// Calls Notify() remotely, using a default retry value of three. /// </summary> /// <param name="remoteNode">The remote on which to call the method.</param> /// <param name="callingNode">The node to inform the remoteNode of.</param> /// <param name="retryCount">The number of times to retry the operation in case of error.</param> /// <returns>True if succeeded, FALSE otherwise.</returns> public static bool CallNotify(ChordNode remoteNode, ChordNode callingNode, int retryCount) { ChordInstance instance = ChordServer.GetInstance(remoteNode); try { instance.Notify(callingNode); return(true); } catch (System.Exception ex) { ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallNotify error: {0}", ex.Message); if (retryCount > 0) { return(CallNotify(remoteNode, callingNode, --retryCount)); } else { return(false); } } }
/// <summary> /// Join the current ChordInstance to a chord ring given a seed node. /// </summary> /// <param name="seed">Remote node to connect to that is a member of a Chord ring. To start a new Chord ring, a null seed may be provided.</param> /// <param name="host">The local host name.</param> /// <param name="port">The tcp port on which this node will listen for calls from other Chord nodes.</param> /// <returns>true if the Join succeeds; false, otherwise.</returns> public bool Join(ChordNode seed, string host, int port) { // LocalNode is established first to ensure proper logging. ChordServer.LocalNode = new ChordNode(host, port); this.m_HasReJoinRun = false; this.m_SeedNode = seed; // cache the seed node so the ring may re-form itself in case of partition or damage // safely establish finger table (startvalues and successors, using LocalNode as the default successor) this.FingerTable = new ChordFingerTable(ChordServer.LocalNode); // establish successor cache initially with all entries pointing loally this.SuccessorCache = new ChordNode[3]; // TODO: make this configurable for (int i = 0; i < this.SuccessorCache.Length; i++) { this.SuccessorCache[i] = ChordServer.LocalNode; } if (seed != null) // join an existing chord ring { ChordServer.Log(LogLevel.Info, "Navigation", "Joining Ring @ {0}:{1}.", seed.Host, seed.PortNumber); // first, establish successor via seed node ChordInstance instance = ChordServer.GetInstance(seed); if (ChordServer.IsInstanceValid(instance)) { try { this.Successor = instance.FindSuccessor(this.ID); // NOTE: this conceivably could be replaced with ChordServer.FindSuccessor() // disabled: a clever trick that requires only one remote network call is to // append the successor's successor cache (minus its last entry) to the local // successor cache, starting at the second entry in the local successor cache. // during churn, this can break down, so instead the successor cache is populated // and maintained lazily by maintenance. // as the successor cache was initialized with the LocalNode as the default // instance values, "misses" on successor cache entries are gracefully handled by // simply being forwarded (via the local node) on to the successor node where // better information may be found and utilized. // //this.SuccessorCache = ChordServer.GetSuccessorCache(this.Successor); } catch (Exception e) { ChordServer.Log(LogLevel.Error, "Navigation", "Error setting Successor Node ({0}).", e.Message); return(false); } } else { ChordServer.Log(LogLevel.Error, "Navigation", "Invalid Node Seed."); return(false); } } else // start a new ring { // not much needs to happen - successor is already established as // ChordServer.LocalNode,everything else takes place lazily as part of maintenance ChordServer.Log(LogLevel.Info, "Navigation", "Starting Ring @ {0}:{1}.", this.Host, this.Port); } // everything that needs to be populated or kept up-to-date // lazily is handled via background maintenance threads running periodically StartMaintenance(); return(true); }