private HttpStatusCode CancelCrossing(CrossingRequest cross) { m_crossLock.EnterUpgradeableReadLock(); try { if (m_crossings.ContainsKey(cross.uuid)) { m_crossLock.EnterWriteLock(); try { m_crossings.Remove(cross.uuid); return(HttpStatusCode.OK); } finally { m_crossLock.ExitWriteLock(); } } else { return(HttpStatusCode.NotFound); } } finally { m_crossLock.ExitUpgradeableReadLock(); } }
/// <summary> /// Used for cancelling a crossing underway. Should be used before the crossing message starts propagating. /// </summary> /// <param name="context"></param> /// <param name="actorRequest"></param> private void HandleCancelCrossing(HttpListenerContext context, HttpListenerRequest actorRequest) { StreamReader actorReader = new StreamReader(actorRequest.InputStream); XmlSerializer deserializer = new XmlSerializer(typeof(CrossingRequest)); CrossingRequest cross = (CrossingRequest)deserializer.Deserialize(actorReader); HttpStatusCode status = CancelCrossing(cross); HttpListenerResponse actorResponse = context.Response; actorResponse.StatusCode = (int)status; actorResponse.Close(); }
/// <summary> /// Check if the requested crossing moves the object out of an active quark to non-active quark. /// If not, a simple udpdate property could be used instead, with the added curquark and prevquark info. /// </summary> /// <param name="cross"></param> /// <returns></returns> private HttpStatusCode ValidateCrossing(CrossingRequest cross) { bool crossing = false; HashSet <string> subscribedActors = new HashSet <string>(); subscribedActors.UnionWith(m_quarkSubscriptions[cross.prevQuark].ActiveSubscribers); subscribedActors.UnionWith(m_quarkSubscriptions[cross.curQuark].ActiveSubscribers); foreach (string strActor in subscribedActors) { if (!(m_actor[strActor].ActiveQuarks.Contains(cross.prevQuark) && m_actor[strActor].ActiveQuarks.Contains(cross.curQuark))) { crossing = true; break; } } if (crossing == false) { return(HttpStatusCode.OK); } // Crossing is already underway for this object if (m_crossings.ContainsKey(cross.uuid)) { // If incoming crossing is older than current, discard, return forbidden. // This could happen if two crossings were generated while one is in progress. The crossing with timestamp // in between will simply be overwritten if (cross.timestamp < m_crossings[cross.uuid].cross.timestamp) { return(HttpStatusCode.Forbidden); } else if (cross.timestamp == m_crossings[cross.uuid].cross.timestamp) { return(HttpStatusCode.Forbidden); } else { // Tell actor there was a conflict. Actor will wait until it receives the first crossing message before trying again. // Note: This could be made more efficient if it's possible to remember the cross requests and only accept the next earliest crossing. return(HttpStatusCode.Conflict); } } else { return(HttpStatusCode.Created); } }
/// <summary> /// Handles a quark crossing request. Checks to see if the request is possible, and returns true or false based on /// the validity of the crossing to the actor who requested. This is performed in 3 steps: /// Step 1: Tell root to be ready to receive crossing requests for the object /// Root will also do sanity checks (e.g. object was deleted before crossing), so checking actorStatus code /// is important. /// Step 2: Root provides URL for uploading updated properties and for downloading said object. /// Step 3: Tell actor about the URL where it may contact the root directly. /// TODO: This can be optimized if we are allowed to remember or calculate the URL. We could respond immediately with the URL /// and the actor would keep trying it until root accepts it. For now, we avoid concurrency. /// </summary> /// <param name="context"></param> /// <param name="actorRequest"></param> private void HandleCrossing(HttpListenerContext context, HttpListenerRequest actorRequest) { StreamReader actorReader = new StreamReader(actorRequest.InputStream); XmlSerializer deserializer = new XmlSerializer(typeof(CrossingRequest)); CrossingRequest cross = (CrossingRequest)deserializer.Deserialize(actorReader); QuarkPublisher curQp = null, prevQp = null; string url = ""; HttpStatusCode status; m_crossLock.EnterReadLock(); try { m_log.InfoFormat("{0}: Handling Crossing. Time: {1}", LogHeader, DateTime.Now.Ticks); if (m_quarkSubscriptions.TryGetValue(cross.curQuark, out curQp) && m_quarkSubscriptions.TryGetValue(cross.prevQuark, out prevQp)) { if (curQp.RootActorID != prevQp.RootActorID) { // TODO: Inter-root communication } else { // Actor's response variables HttpListenerResponse actorResponse = context.Response; Stream actorOutput = actorResponse.OutputStream; // Root's request variables RootInfo root = (RootInfo)m_actor[curQp.RootActorID]; HttpWebRequest rootRequest = (HttpWebRequest)WebRequest.Create("http://" + root.quarkAddress + "/cross/"); rootRequest.Credentials = CredentialCache.DefaultCredentials; rootRequest.Method = "POST"; rootRequest.ContentType = "text/json"; Stream rootOutput = rootRequest.GetRequestStream(); status = ValidateCrossing(cross); if (status != HttpStatusCode.Created) { actorResponse.StatusCode = (int)status; actorOutput.Close(); actorResponse.Close(); return; } // From here on, I might have to write, make sure we only do one of these at a time. // Can't go in UpgradeableLock with a ReadLock, so let go first. m_crossLock.ExitReadLock(); m_crossLock.EnterUpgradeableReadLock(); try { // First we double check nothing changed while we were waiting for the lock, and we are still valid to cross. status = ValidateCrossing(cross); if (status != HttpStatusCode.Created) { actorResponse.StatusCode = (int)status; actorOutput.Close(); actorResponse.Close(); return; } // Step 1: Tell root to be ready to receive crossing requests for the object // Root will also do sanity checks (e.g. object was deleted before crossing), so checking actorStatus code // is important. OSDMap DataMap = new OSDMap(); DataMap["uuid"] = OSD.FromUUID(cross.uuid); DataMap["pq"] = OSD.FromString(cross.prevQuark); DataMap["cq"] = OSD.FromString(cross.curQuark); DataMap["ts"] = OSD.FromLong(cross.timestamp); string encodedMap = OSDParser.SerializeJsonString(DataMap, true); byte[] rootData = System.Text.Encoding.ASCII.GetBytes(encodedMap); int rootDataLength = rootData.Length; rootOutput.Write(rootData, 0, rootDataLength); rootOutput.Close(); // Step 2: Root provides URL for uploading updated properties and for downloading said object. HttpWebResponse response = (HttpWebResponse)rootRequest.GetResponse(); if (HttpStatusCode.OK == response.StatusCode) { m_crossLock.EnterWriteLock(); try { m_crossings[cross.uuid] = new CurrentCrossings(); m_crossings[cross.uuid].cross = cross; m_crossings[cross.uuid].actors.UnionWith(m_quarkSubscriptions[cross.prevQuark].GetAllQuarkSubscribers()); m_crossings[cross.uuid].actors.UnionWith(m_quarkSubscriptions[cross.curQuark].GetAllQuarkSubscribers()); // Remove the starting actor from the list of actors to ACK. m_crossings[cross.uuid].actors.Remove(cross.actorID); m_crossings[cross.uuid].rootHandler = root; } finally { m_crossLock.ExitWriteLock(); } Stream respData = response.GetResponseStream(); StreamReader rootReader = new StreamReader(respData); url = rootReader.ReadToEnd(); m_log.WarnFormat("{0}: Got URL for object request from server: {1}", LogHeader, url); if (url.Length > 0) { // Step 3: Tell actor about the URL where it may contact the root directly. // TODO: This can be optimized if we are allowed to remember or calculate the URL. We could respond immediately with the URL // and the actor would keep trying it until root accepts it. For now, we avoid concurrency. actorResponse.StatusCode = (int)HttpStatusCode.Created; byte[] actorUrlData = System.Text.Encoding.ASCII.GetBytes(url); int actorUrlDataLength = actorUrlData.Length; actorOutput.Write(actorUrlData, 0, actorUrlDataLength); actorOutput.Close(); actorResponse.Close(); } else { m_log.ErrorFormat("{0}: Received empty URL from Root", LogHeader); } } else { m_log.ErrorFormat("{0}: Failed to request crossing from root. Error Code: {1}", LogHeader, response.StatusCode); } } finally { m_crossLock.ExitUpgradeableReadLock(); } } } } catch (Exception e) { m_log.ErrorFormat("{0}: Failed to request crossing from root and forward to actor. Exception: {1}\n{2}", LogHeader, e, e.StackTrace); } finally { if (m_crossLock.IsReadLockHeld) { m_crossLock.ExitReadLock(); } } }
/// <summary> /// Used for actors ACKing received crossed messages. Once all expected actors have ACKEd the crossing message, /// the Sync Service informs the root actor to push a crossing finished. /// </summary> /// <param name="context"></param> /// <param name="request"></param> private void HandleAckCrossing(HttpListenerContext context, HttpListenerRequest request) { m_log.InfoFormat("{0}: HandleAckCrossing", LogHeader); StreamReader actorReader = new StreamReader(request.InputStream); XmlSerializer deserializer = new XmlSerializer(typeof(CrossingFinished)); CrossingFinished cf = (CrossingFinished)deserializer.Deserialize(actorReader); string ackActorID = cf.ackActorID; CrossingRequest cross = cf.cross; RootInfo root = null; bool allAcksReceived = false; try { HttpStatusCode actorStatus; m_crossings[cross.uuid].actors.Remove(ackActorID); m_log.InfoFormat("{0}: Ack received from {1}, {2} acks remaining.", LogHeader, ackActorID, m_crossings[cross.uuid].actors.Count); if (m_crossings[cross.uuid].actors.Count == 0) { root = m_crossings[cross.uuid].rootHandler; actorStatus = CancelCrossing(cross); allAcksReceived = true; } else { actorStatus = HttpStatusCode.OK; } HttpListenerResponse actorResponse = context.Response; actorResponse.StatusCode = (int)actorStatus; actorResponse.Close(); if (allAcksReceived) { if (actorStatus == HttpStatusCode.OK) { m_log.InfoFormat("{0}: Informing root that crossing is finished", LogHeader); // Now tell root to tell every actor that the crossing is finnished! // Root's request variables HttpWebRequest rootRequest = (HttpWebRequest)WebRequest.Create("http://" + root.quarkAddress + "/finished/"); rootRequest.Credentials = CredentialCache.DefaultCredentials; rootRequest.Method = "POST"; rootRequest.ContentType = "text/json"; Stream rootOutput = rootRequest.GetRequestStream(); OSDMap DataMap = new OSDMap(); DataMap["uuid"] = OSD.FromUUID(cross.uuid); DataMap["ts"] = OSD.FromLong(cross.timestamp); string encodedMap = OSDParser.SerializeJsonString(DataMap, true); byte[] rootData = System.Text.Encoding.ASCII.GetBytes(encodedMap); int rootDataLength = rootData.Length; rootOutput.Write(rootData, 0, rootDataLength); rootOutput.Close(); // Check if everything went OK try { HttpWebResponse response = (HttpWebResponse)rootRequest.GetResponse(); if (response.StatusCode == HttpStatusCode.OK) { m_log.InfoFormat("{0}: Successfully finished crossing.", LogHeader); return; } } catch (WebException we) { var resp = we.Response as HttpWebResponse; m_log.ErrorFormat("{0}: Sync Crossing finished fail with actorStatus code: {1}", LogHeader, resp.StatusCode); } } else { throw new Exception("Could not find the object in the crossing dictionary"); } } } catch (Exception e) { m_log.ErrorFormat("{0}: Unkown exception while handling ACK from actor. Exception {1}", LogHeader, e); } }