Example #1
0
        /// <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);
            }
        }
Example #2
0
        /// <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);
            }
        }
Example #3
0
        /// <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));
                }
            }
        }
Example #4
0
        /// <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++;
        }
Example #5
0
        /// <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;
            }
        }
Example #6
0
        /// <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;
            }
        }
Example #7
0
        /// <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);
        }
Example #8
0
        /// <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;
            }
        }
Example #9
0
        /// <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);
        }
Example #10
0
 public FlushInfo(string queuedEP, QueuedMsgInfo msgInfo)
 {
     this.QueueEP = queuedEP;
     this.MsgInfo = msgInfo;
 }
Example #11
0
        /// <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);
            }
        }