/// <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> /// 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> /// 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); }