/// <summary> /// Writes a message file. /// </summary> /// <param name="path">The file path.</param> /// <param name="msg">The message.</param> /// <param name="msgInfo">The message metadata.</param> internal void WriteMessage(string path, QueuedMsg msg, QueuedMsgInfo msgInfo) { byte[] md5Hash; using (var fsMsg = new EnhancedFileStream(path, FileMode.Create, FileAccess.ReadWrite)) { // Write the file header fsMsg.WriteInt32(MsgMagic); // Magic Number fsMsg.WriteInt32(0); // Format Version fsMsg.WriteInt32(0); // Reserved fsMsg.WriteBytesNoLen(md5Zeros); // MD5 hash placeholder // Write the message body. fsMsg.WriteBytes32(msg.BodyRaw); // Write the metadata. fsMsg.WriteString16(msgInfo.ToString()); // Compute and save the MD5 hash fsMsg.Position = MsgHeaderSize; md5Hash = MD5Hasher.Compute(fsMsg, fsMsg.Length - fsMsg.Position); fsMsg.Position = MsgMD5Offset; fsMsg.WriteBytesNoLen(md5Hash); } }
/// <summary> /// Updates a message file's metadata. /// </summary> /// <param name="path">The file path.</param> /// <param name="msgInfo">The message metadata.</param> internal void WriteMessageMetadata(string path, QueuedMsgInfo msgInfo) { using (var fsMsg = new EnhancedFileStream(path, FileMode.Open, FileAccess.ReadWrite)) { // Seek past the message's header and body. int cbBody; byte[] md5Hash; fsMsg.Position = MsgHeaderSize; cbBody = fsMsg.ReadInt32(); fsMsg.Position = fsMsg.Position + cbBody; // Write the metadata and truncate the file. fsMsg.WriteString16(msgInfo.ToString()); fsMsg.SetLength(fsMsg.Position); // Regenerate the MD5 Hash fsMsg.Position = MsgMD5Offset + MD5Hasher.DigestSize; md5Hash = MD5Hasher.Compute(fsMsg, fsMsg.Length - fsMsg.Position); fsMsg.Position = MsgMD5Offset; fsMsg.WriteBytesNoLen(md5Hash); } }
/// <summary> /// Reads the metadata from a message file. /// </summary> /// <param name="msgID">The message ID.</param> /// <param name="path">Fully qualified path to the message file.</param> /// <returns>The <see cref="QueuedMsgInfo" />.</returns> internal QueuedMsgInfo ReadMessageMetadata(Guid msgID, string path) { QueuedMsgInfo msgInfo; int cbBody; byte[] md5Hash; long savePos; using (var fsMsg = new EnhancedFileStream(path, FileMode.Open, FileAccess.ReadWrite)) { try { // Read the message file header if (fsMsg.ReadInt32() != MsgMagic) // Magic Number { throw new Exception(); } if (fsMsg.ReadInt32() != 0) // Format Version { throw new Exception(); } fsMsg.ReadInt32(); // Reserved // Verify that the MD5 hash saved in then file matches the // hash computed for the remainder of the file as it exists // right now. md5Hash = fsMsg.ReadBytes(MD5Hasher.DigestSize); savePos = fsMsg.Position; if (!Helper.ArrayEquals(md5Hash, MD5Hasher.Compute(fsMsg, fsMsg.Length - fsMsg.Position))) { throw new FormatException(string.Format("Message file [{0}] is corrupt. MD5 digests do not match.", path)); } fsMsg.Position = savePos; // Skip over the message body data cbBody = fsMsg.ReadInt32(); fsMsg.Position = fsMsg.Position + cbBody; // Read the metadata and add the provider specific information msgInfo = new QueuedMsgInfo(fsMsg.ReadString16()); msgInfo.PersistID = msgInfo.ID; msgInfo.ProviderData = path; return(msgInfo); } catch { throw new FormatException(string.Format("Bad message file [{0}].", path)); } } }
/// <summary> /// Adds the message information to the end other queued messages with the same priority. /// </summary> /// <param name="transaction">The current <see cref="BaseTransaction" /> (or <c>null</c>).</param> /// <param name="msgInfo">The message information.</param> public void Enqueue(BaseTransaction transaction, QueuedMsgInfo msgInfo) { var queue = queuesByPriority[(int)msgInfo.Priority]; var lockID = transaction != null ? transaction.ID : Guid.Empty; msgInfo.LockID = lockID; queue.Enqueue(msgInfo); messages.Add(msgInfo.ID, msgInfo); count++; }
/// <summary> /// Adds a message to the backing store and updates the <see cref="QueuedMsgInfo.PersistID" /> /// and <see cref="QueuedMsgInfo.ProviderData" /> fields in the <see cref="QueuedMsgInfo" /> /// instance passed. /// </summary> /// <param name="msgInfo">The message metadata.</param> /// <param name="msg">The message.</param> public void Add(QueuedMsgInfo msgInfo, QueuedMsg msg) { using (TimedLock.Lock(this)) { if (messages == null) { throw new ObjectDisposedException(this.GetType().Name); } msgInfo.PersistID = msg.ID; msgInfo.ProviderData = msg; messages[msg.ID] = msgInfo; } }
/// <summary> /// Adds a message to the backing store and updates the <see cref="QueuedMsgInfo.PersistID" /> /// and <see cref="QueuedMsgInfo.ProviderData" /> fields in the <see cref="QueuedMsgInfo" /> /// instance passed. /// </summary> /// <param name="msgInfo">The message metadata.</param> /// <param name="msg">The message.</param> public void Add(QueuedMsgInfo msgInfo, QueuedMsg msg) { using (TimedLock.Lock(this)) { if (messages == null) { throw new ObjectDisposedException(this.GetType().Name); } string msgPath; msgPath = GetMessagePath(msgInfo.ID, true); WriteMessage(msgPath, msg, msgInfo); msgInfo.PersistID = msg.ID; msgInfo.ProviderData = msgPath; messages[msg.ID] = msgInfo; } }
/// <summary> /// Removes a specific message from the queue. /// </summary> /// <param name="msgInfo">Information about the message being removed.</param> /// <returns><c>true</c> if the message was found and returned.</returns> /// <remarks> /// This method is used during transactional processing when undoing or redoing /// transactions. /// </remarks> public bool Remove(QueuedMsgInfo msgInfo) { // This method will probably be called most often to removed a message // enqueued during a transaction that is being rolled back. In this // case, the transaction will be at the end of the queue so to optimize // performance I'm going to search from the end of the queues to the // beginning. var queue = queuesByPriority[(int)msgInfo.Priority]; for (int i = queue.Count - 1; i >= 0; i--) { if (queue[i].ID == msgInfo.ID) { queue.RemoveAt(i); return(true); } } return(false); }
/// <summary> /// Constructor. /// </summary> /// <param name="msgInfo">A <see cref="QueuedMsgInfo" /> instance with the header information.</param> /// <param name="bodyRaw">The raw message body.</param> /// <param name="deserialize"> /// Pass <c>true</c> to deserialize the message body, <c>false</c> to limit /// deserialization to the message headers. /// </param> /// <remarks> /// <note> /// This constructor is provided for applications that need to implement /// a custom <see cref="IMsgQueueStore" />. /// </note> /// </remarks> public QueuedMsg(QueuedMsgInfo msgInfo, byte[] bodyRaw, bool deserialize) { this.id = msgInfo.ID; this.targetEP = msgInfo.TargetEP; this.responseEP = msgInfo.ResponseEP; this.sessionID = msgInfo.SessionID; this.sendTime = msgInfo.SendTime; this.expireTime = msgInfo.ExpireTime; this.flags = msgInfo.Flags; this.priority = msgInfo.Priority; this.bodyRaw = bodyRaw; if (deserialize) { body = Serialize.FromBinary(bodyRaw); } else { body = null; } }
/// <summary> /// Returns a shallowm clone of this instance. /// </summary> /// <returns>The cloned <see cref="QueuedMsgInfo" />.</returns> public QueuedMsgInfo Clone() { var clone = new QueuedMsgInfo(); clone.PersistID = this.PersistID; clone.ID = this.ID; clone.SessionID = this.SessionID; clone.TargetEP = this.TargetEP; clone.ResponseEP = this.ResponseEP; clone.Priority = this.Priority; clone.Flags = this.Flags; clone.SendTime = this.SendTime; clone.ExpireTime = this.ExpireTime; clone.DeliveryTime = this.DeliveryTime; clone.BodySize = this.BodySize; clone.DeliveryAttempts = this.DeliveryAttempts; clone.LockID = this.LockID; clone.ProviderData = this.ProviderData; return(clone); }
public FlushInfo(string queuedEP, QueuedMsgInfo msgInfo) { this.QueueEP = queuedEP; this.MsgInfo = msgInfo; }
/// <summary> /// Loads the message index file. If the index does not exist or is corrupt then /// the index will be rebuilt by performing a full scan of message files under /// the root folder. /// </summary> /// <param name="newIndexFile">Pass as <c>true</c> if a new index file is being created.</param> private void LoadIndex(bool newIndexFile) { string formatErr = string.Format("Invalid or missing index file [{0}].", indexPath); int cMessages; string[] files; Dictionary <Guid, string> msgFiles; TimedLock.AssertLocked(this); messages = new Dictionary <Guid, QueuedMsgInfo>(); // List the message files and build a hash table mapping each message GUID // the fully qualified path to the file. files = Helper.GetFilesByPattern(root + "*.msg", SearchOption.AllDirectories); msgFiles = new Dictionary <Guid, string>(files.Length); foreach (string path in files) { string file = Path.GetFileName(path).ToLowerInvariant(); Guid msgID; if (!file.EndsWith(".msg")) { continue; } try { msgID = new Guid(file.Substring(0, file.Length - 4)); } catch { continue; } msgFiles[msgID] = path; } // Load the index file. try { // Parse the index file header fsIndex.Position = 0; if (fsIndex.ReadInt32() != IndexMagic) // Magic Number { throw new FormatException(formatErr); } if (fsIndex.ReadInt32() != 0) // Format Version { throw new FormatException(formatErr); } fsIndex.ReadInt32(); // Reserved if (fsIndex.ReadInt32() != 0) { throw new FormatException(string.Format("Index file [{0}] was not closed properly. Full message folder scan will be performed.", indexPath)); } cMessages = fsIndex.ReadInt32(); // Message Count // Parse the message metadata for (int i = 0; i < cMessages; i++) { QueuedMsgInfo msgInfo; msgInfo = new QueuedMsgInfo(fsIndex.ReadString16()); msgInfo.PersistID = msgInfo.ID; msgInfo.ProviderData = root + fsIndex.ReadString16(); // Make the paths absolute messages[msgInfo.ID] = msgInfo; } // Perform an extra consistency check by listing all of the message files // under the root folder and comparing the message GUIDs encoded into the // file names with the GUIDs loaded from the index file and then bringing // the index into sync with the actual message files. bool updated = false; int cLoaded = 0; // Delete any metadata for messages that exist in the index // but don't exist on the file system. var delList = new List <Guid>(); foreach (Guid msgID in messages.Keys) { if (!msgFiles.ContainsKey(msgID)) { delList.Add(msgID); } } foreach (Guid msgID in delList) { messages.Remove(msgID); } if (delList.Count > 0) { updated = true; SysLog.LogWarning(string.Format("Message index [{0}] has message metadata for messages that do not exist. [{1}] messages will be removed from the index.", indexPath, delList.Count)); } // Load metadata for messages that exist in the file system // but were not in the index. foreach (Guid msgID in msgFiles.Keys) { if (!messages.ContainsKey(msgID)) { string path = msgFiles[msgID]; try { messages[msgID] = ReadMessageMetadata(msgID, path); cLoaded++; } catch (Exception e) { SysLog.LogException(e); } } } if (cLoaded > 0) { updated = true; SysLog.LogWarning(string.Format("Message index [{0}] is missing metadata for [{1}] messages. Missing entries will be added.", indexPath, cLoaded)); } if (updated) { SaveIndex(true); } // Mark the index as "open" for crash detection. fsIndex.Position = OpenFlagOffset; fsIndex.WriteInt32(1); fsIndex.Flush(); } catch { if (newIndexFile) { SysLog.LogWarning("Rebuilding missing message index file [{0}].", indexPath); } else { SysLog.LogWarning("Rebuilding corrupt message index file [{0}].", indexPath); } // Clear the index file if there was a serious error and then // rebuild it from scratch by scanning the message metadata. fsIndex.SetLength(0); fsIndex.Flush(); messages.Clear(); foreach (Guid msgID in msgFiles.Keys) { try { messages.Add(msgID, ReadMessageMetadata(msgID, msgFiles[msgID])); } catch (Exception e2) { SysLog.LogException(e2); } } // Save the index, marking the file as "open" for crash detection SaveIndex(true); } }