internal NotificationEventArgs(DataEntry entry, string userKey, NotificationReason reason, object oldValue) { this._Entry = entry; this._reason = reason; this._userKey = userKey; this._owner = entry.lastOwnerID; this._value = entry.value; this._oldValue = oldValue; }
///// <summary> ///// respond only by header ///// </summary> ///// <param name="verb">GET or HEAD</param> ///// <param name="resource">Same as key or contentLocation</param> ///// <param name="senderList"></param> ///// <param name="contentLength"></param> ///// <param name="mimeType"></param> ///// <param name="lastVer"></param> ///// <param name="isDeleted">Indicates that the current entry is deleted</param> ///// <param name="willClose">Writes a close message in the header</param> ///// <param name="bufferedOutput">A memory buffer that will be filled with contents produced ///// from this method</param> //private void ResponseHeadStub(string verb, string resource, List<int> senderList, List<int> proxyResponsePath, int contentLength, // string mimeType, ETag lastVer, bool isDeleted, bool willClose, MemoryStream bufferedOutput) //{ // StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII); // writer.NewLine = NEWLINE; // if (isDeleted) // { // WriteResponseDeleted(bufferedOutput, resource, senderList, proxyResponsePath, lastVer.UID, lastVer.Revision); // } // else // { // WriteResponseHeader(writer, resource, mimeType, contentLength, lastVer.UID, lastVer.Revision, senderList, proxyResponsePath, verb, willClose); // } //} // respond to a GET request, HEAD request, and push data on wire private void Response(string verb, string resource, List<int> senderList,List<int> proxyResponsePath, DataEntry entry, MemoryStream bufferedOutput, bool willClose) { lock (entry) { if (entry.IsEmpty) { WriteResponseDeleted(bufferedOutput, resource, senderList, proxyResponsePath, entry.lastOwnerID, entry.lastOwnerRevision); } else if (entry.IsSimpleValue || entry.type == DataEntry.ValueType.String) { StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII); writer.NewLine = NEWLINE; string translation =""; if (entry.value != null) translation = entry.value.ToString(); byte[] bytesToWrite = System.Text.Encoding.UTF8.GetBytes(translation); WriteResponseHeader(writer, resource, entry.GetMime(), bytesToWrite.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose); writer.Flush(); if (verb == GET) { bufferedOutput.Write(bytesToWrite, 0, bytesToWrite.Length); //StreamWriter tw = new StreamWriter(bufferedOutput, Encoding.UTF8); // tw.NewLine = NEWLINE; // tw.BaseStream.Seek(0, SeekOrigin.End); // tw.Write(translation); // tw.Flush(); } writer.Flush(); } else if (entry.IsComplexValue) { if (entry.type == DataEntry.ValueType.Binary) { StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII); writer.NewLine = NEWLINE; byte[] bentry = (byte[])entry.value; WriteResponseHeader(writer, resource, entry.GetMime(), bentry.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose); writer.Flush(); if (verb == GET) { bufferedOutput.Write(bentry, 0, bentry.Length); } } else if (entry.type == DataEntry.ValueType.Object) { StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII); writer.NewLine = NEWLINE; MemoryStream bentry; try { bentry = new MemoryStream(); BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(bentry, entry.value); } catch { throw; } System.Diagnostics.Debug.Assert(bentry.Length <= int.MaxValue); WriteResponseHeader(writer, resource, entry.GetMime(), (int)bentry.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose); writer.Flush(); if (verb == GET) { bentry.WriteTo(bufferedOutput); } } else if (entry.type == Data.ValueType.Unknown) { StreamWriter writer = new StreamWriter(bufferedOutput, Encoding.ASCII); writer.NewLine = NEWLINE; byte[] bentry = ((DataUnsupported) entry.value).GetPayload(); WriteResponseHeader(writer, resource, entry.GetMime(), bentry.Length, entry.lastOwnerID, entry.lastOwnerRevision, senderList, proxyResponsePath, verb, willClose); writer.Flush(); if (verb == GET) { bufferedOutput.Write(bentry, 0, bentry.Length); } } else { throw new NotImplementedException(); } } else { throw new NotImplementedException(); } } }
/// <summary> /// TODO: add in some code to reduce round-trips to simple data types /// </summary> /// <param name="reader">File to read</param> /// <param name="sentFromList">Sent from list</param> /// <param name="getEntriesFromSender">This function fills in a list of entries that need to be requested from the sender</param> /// <param name="addEntriesToSender">These are entries that the sender does not know about</param> /// <seealso cref="ReadHeadStub"/> private void ReadDictionaryTextFile(StreamReader reader, List<int> sentFromList, List<DataHeader> getEntriesFromSender, List<SendMemoryToPeer> addEntriesToSender) { // 0 - key name // 1 - owner // 2 - revision // 3 - rw flag // 4 - MIME type // WriteDebug(this.local_uid + " ReadDictionaryTextFile"); string nsLine = reader.ReadLine(); string[] nsLineParts = nsLine.Split('\t'); System.Diagnostics.Debug.Assert(nsLineParts[0] == DATA_NAMESPACE + "/"); // if the owner of the dictionary is the same as myself, skip reading the changes if (nsLineParts[1] == this.local_uid.ToString()) { throw new NotSupportedException("ReadDictionaryTextFile cannot read itself");// i want to see if this actually can happen (only when multiple connections happen on the same server) } int itemCount = int.Parse(nsLineParts[4]); // count of all the items in the dictionary List<DataEntry> entriesCovered = new List<DataEntry>(itemCount + this.data.Count); for (int i = 0; i < itemCount; i++) { nsLine = reader.ReadLine(); nsLineParts = nsLine.Split('\t'); //WriteDebug(nsLine); DataHeader getEntry = null; SendMemoryToPeer addEntryToSender = null; ETag tag = new ETag(int.Parse(nsLineParts[1]), int.Parse(nsLineParts[2])); // this entry is used only to call ReadMimeSimpleData DataEntry fakeEntry = new DataEntry("/fake", tag, new List<int>(0)); fakeEntry.ReadMimeSimpleData(nsLineParts[4]); dataLock.EnterReadLock(); try { if (this.data.ContainsKey(nsLineParts[0])) { entriesCovered.Add(this.data[nsLineParts[0]]); } } finally { dataLock.ExitReadLock(); } // the dictionary does not report the current sender so let's tack it on List<int> listOfSenders = new List<int>(GetArrayOf(nsLineParts[5])); if (!listOfSenders.Contains(this.remote_uid)) listOfSenders.Add(this.remote_uid); ResponseAction action = ReadDataStub(nsLineParts[0], fakeEntry.GetMime(), nsLineParts[1] + "." + nsLineParts[2], new List<int>( listOfSenders), out getEntry, out addEntryToSender); if (getEntry != null) { getEntriesFromSender.Add(getEntry); } if (addEntryToSender != null) { addEntriesToSender.Add(addEntryToSender); } if (action == ResponseAction.ForwardToAll) { listOfSenders.Clear(); } if (action != ResponseAction.DoNotForward) { DataEntry get = P2PDictionary.GetEntry( this.data, this.dataLock, nsLineParts[0]); System.Diagnostics.Debug.Assert(get != null); listOfSenders.Add(this.local_uid); SendBroadcastMemory msg = new SendBroadcastMemory(get.key , listOfSenders); WriteMethodPush(get.key, listOfSenders, null, 0, get.GetMime(), get.GetETag(), get.IsEmpty, false, msg.MemBuffer); this.controller.BroadcastToWire(msg); } } // now check to see which dictionary entries that the sender does not have; i'll send my entries to the caller this.dataLock.EnterWriteLock(); try { foreach (KeyValuePair<string, DataEntry> senderDoesNotHave in this.data.SkipWhile(x => entriesCovered.Contains(x.Value))) { DataEntry get = senderDoesNotHave.Value; // i know about something that the sender does not know SendMemoryToPeer mem = new SendMemoryToPeer(get.key, sentFromList); WriteMethodPush(get.key, GetListOfThisLocalID(), null, 0, get.GetMime(), get.GetETag(), get.IsEmpty, false, mem.MemBuffer); addEntriesToSender.Add(mem); } } finally { this.dataLock.ExitWriteLock();} }
private ResponseAction ReadDelete(string contentLocation, string eTag, List<int> senderPath) { ResponseAction success = ResponseAction.DoNotForward; ETag tag = ReadETag(eTag); bool upgradeable = true; this.dataLock.EnterUpgradeableReadLock(); try { if (this.data.ContainsKey(contentLocation)) { DataEntry get = this.data[contentLocation]; object oldValue = null; // exit lock this.dataLock.ExitUpgradeableReadLock(); upgradeable = false; lock (get) { ETagCompare compareResult = ETag.CompareETags(tag, get.GetETag()); if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Conflict || compareResult == ETagCompare.Same) { oldValue = get.value; if (compareResult == ETagCompare.Conflict) { success = ResponseAction.ForwardToAll; tag.UID = this.local_uid; tag.Revision = IncrementRevisionRandomizer(tag.Revision); } else if (DataMissing.IsSingleton(oldValue)) { success = ResponseAction.ForwardToSuccessor; } else if (compareResult == ETagCompare.Same) { success = ResponseAction.DoNotForward; } else//first is newer { success = ResponseAction.ForwardToSuccessor; } get.lastOwnerID = tag.UID; get.lastOwnerRevision = tag.Revision; get.Delete(); if (compareResult != ETagCompare.Same) { get.senderPath = senderPath; } if (!get.subscribed) { get.value = DataMissing.Singleton; } } else//if (compareResult == ETagCompare.SecondIsNewer) { // return to sender success = ResponseAction.ForwardToAll; } } // end lock // notify to subscribers if (success != ResponseAction.DoNotForward && get.subscribed) this.controller.Notified(new NotificationEventArgs(get, "", NotificationReason.Remove, oldValue)); } else { // create a stub of the item DataEntry create = new DataEntry(contentLocation, tag, senderPath); create.Delete(); create.subscribed = keysToListen.IsSubscribed(contentLocation); if (!create.subscribed) { create.value = DataMissing.Singleton; } dataLock.EnterWriteLock(); try { this.data.Add(contentLocation, create); } finally { this.dataLock.ExitWriteLock(); } if (create.subscribed) { // notify for subscribers this.controller.Notified(new NotificationEventArgs(create, "", NotificationReason.Remove, null)); } success = ResponseAction.ForwardToSuccessor; } } finally { if (upgradeable) this.dataLock.ExitUpgradeableReadLock(); } return success; }
/// <summary> /// Reads data using only header information. Can be used by ReadDictionary /// so it handles deleted content too. /// </summary> /// <param name="contentLocation">Location of the data item without /proxy</param> /// <param name="contentType">GetMime()</param> /// <param name="eTag">Version number</param> /// <param name="addEntryToSender">These are entries that the sender does not know about</param> /// <param name="getEntryFromSender">This function fills in a list of entries that need to be requested from the sender</param> private ResponseAction ReadDataStub(string contentLocation, string contentType, string eTag, List<int> sentFromList, out DataHeader getEntryFromSender, out SendMemoryToPeer addEntryToSender) { ResponseAction success = ResponseAction.DoNotForward; ETag tag = ReadETag(eTag); DataEntry get = null; getEntryFromSender = null; addEntryToSender = null; // create a stub of the item DataEntry create = new DataEntry(contentLocation, tag, sentFromList); create.subscribed = keysToListen.IsSubscribed(contentLocation); create.SetMime(contentType); // manually erase the value (TODO, don't erase the value) // always default to singleton because we assume that a GET is required to complete the request if (create.type != Data.ValueType.Removed) create.value = DataMissing.Singleton; this.dataLock.EnterUpgradeableReadLock(); try { if (this.data.ContainsKey(contentLocation)) { // update the version number of the stub get = this.data[contentLocation]; } else { this.dataLock.EnterWriteLock(); try { this.data.Add(contentLocation, create); } finally { this.dataLock.ExitWriteLock(); } if (create.subscribed && DataMissing.IsSingleton(create.value)) { // we'll have to wait for the data to arrive on the wire // actually get the data getEntryFromSender = new DataHeader(contentLocation, tag, sentFromList); success = ResponseAction.DoNotForward; } else { success = ResponseAction.ForwardToSuccessor; } } } finally { this.dataLock.ExitUpgradeableReadLock(); } if (get != null) { lock (get) { if (create.subscribed) { ETagCompare compareResult = ETag.CompareETags(tag, get.GetETag()); if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Same || compareResult == ETagCompare.Conflict) { getEntryFromSender = new DataHeader(create.key, create.GetETag(), sentFromList); success = ResponseAction.DoNotForward; } else //if (compareResult == ETagCompare.SecondIsNewer ) { // i know about something newer than the sender, tell the sender //SendMemoryToPeer mem = new SendMemoryToPeer(get.key,sentFromList); //ResponseHeadStub(HEAD, get.key, GetListOfThisLocalID(), 0, get.GetMime(), get.GetETag(), get.IsEmpty, mem.MemBuffer, false); //addEntryToSender = mem; // well, predecessor already been handled above, so we only need to tell // the others success = ResponseAction.ForwardToAll; } } else { // not subscribed // just record the fact that there is newer data on the wire; cannot resolve conflicts without being a subscriber ETagCompare compareResult = ETag.CompareETags(create.GetETag(), get.GetETag()); if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Same || compareResult == ETagCompare.Conflict) { get.lastOwnerID = create.lastOwnerID; get.lastOwnerRevision = create.lastOwnerRevision; get.value = DataMissing.Singleton; if (compareResult != ETagCompare.Same) { get.senderPath = create.senderPath; success = ResponseAction.ForwardToSuccessor; } else success = ResponseAction.DoNotForward; } else // if (compareResult == ETagCompare.SecondIsNewer ) { //// i know about something newer than the sender //SendMemoryToPeer mem = new SendMemoryToPeer(get.key,sentFromList); //ResponseHeadStub(HEAD, get.key, GetListOfThisLocalID(), 0, get.GetMime(), get.GetETag(), get.IsEmpty, mem.MemBuffer, false); //addEntryToSender = mem; // tell the others too (already told predecessor above) success = ResponseAction.ForwardToAll; } } } } return success; }
/// <summary> /// reads all sorts of data types /// </summary> /// <param name="contentLocation">location of the data</param> /// <param name="eTag">latest version of data being read</param> /// <param name="contentType"></param> /// <param name="dataOnWire">data read</param> /// <returns></returns> private ResponseAction ReadData(string contentLocation, string eTag, string contentType, List<int> senders, byte[] dataOnWire) { ResponseAction success = ResponseAction.DoNotForward; ETag tag = ReadETag(eTag); // constitute object DataEntry create = new DataEntry(contentLocation, tag, senders); create.subscribed = this.keysToListen.IsSubscribed(contentLocation); create.ReadBytesUsingMime(contentType, dataOnWire); bool upgradeable = true; DataEntry get = null; this.dataLock.EnterUpgradeableReadLock(); try { if (this.data.ContainsKey(contentLocation)) { // update exisitng entry get = this.data[contentLocation]; } if (get == null) { this.dataLock.EnterWriteLock(); try { // don't save the value if not subscribed if (!create.subscribed) { create.value = DataMissing.Singleton; } this.data.Add(contentLocation, create); } finally { this.dataLock.ExitWriteLock(); this.dataLock.ExitUpgradeableReadLock(); upgradeable = false; } if (create.subscribed) { // notify API user this.controller.Notified(new NotificationEventArgs(create, contentLocation, NotificationReason.Add, null)); } // never seen before, thus tell others success = ResponseAction.ForwardToSuccessor; } } finally { if (upgradeable) this.dataLock.ExitUpgradeableReadLock(); } if (get != null) { object oldValue = null; lock (get) { if (create.subscribed) { ETagCompare compareResult = ETag.CompareETags(create.GetETag(), get.GetETag()); if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Conflict || compareResult == ETagCompare.Same) { oldValue = get.value; if (compareResult == ETagCompare.Conflict) { success = ResponseAction.ForwardToAll; // increment the revision and take ownership create.lastOwnerID = this.local_uid; create.lastOwnerRevision = IncrementRevisionRandomizer(create.lastOwnerRevision); } else if (DataMissing.IsSingleton(oldValue)) { success = ResponseAction.ForwardToSuccessor; } else if (compareResult == ETagCompare.Same) { success = ResponseAction.DoNotForward; } else//first is newer { success = ResponseAction.ForwardToSuccessor; } get.lastOwnerID = create.lastOwnerID; get.lastOwnerRevision = create.lastOwnerRevision; get.type = create.type; get.value = create.value; } else // SecondIsNewer { // return this data to the sender success = ResponseAction.ForwardToAll; } } else { ETagCompare compareResult = ETag.CompareETags(create.GetETag(), get.GetETag()); if (compareResult == ETagCompare.FirstIsNewer || compareResult == ETagCompare.Conflict || compareResult == ETagCompare.Same) { if (compareResult == ETagCompare.Conflict) { success = ResponseAction.ForwardToAll; } else if (compareResult == ETagCompare.Same) { success = ResponseAction.DoNotForward; } else//first is newer { success = ResponseAction.ForwardToSuccessor; } if (compareResult != ETagCompare.Same) { get.lastOwnerID = create.lastOwnerID; get.lastOwnerRevision = create.lastOwnerRevision; get.type = create.type; get.value = DataMissing.Singleton; get.senderPath = create.senderPath; } System.Diagnostics.Debug.Assert(get.type != Data.ValueType.Removed); } else // second is newer { // return this data to the sender success = ResponseAction.ForwardToAll; } } }//lock // notify API user if (success != ResponseAction.DoNotForward && get.subscribed && !DataMissing.IsSingleton(get.value)) { get.senderPath = create.senderPath; this.controller.Notified(new NotificationEventArgs(get,contentLocation, NotificationReason.Change, oldValue)); } } // else if return success; }