예제 #1
0
        /// <summary>
        /// Perform the actual package queuing and wait for it to be committed.
        /// </summary>
        /// <remarks>This must be done within the message queue lock.  This method may return a null envelope if called
        /// on a thread which must not block and the packet had to be discarded due to an overflow condition.</remarks>
        /// <param name="packet">The packet to be queued</param>
        /// <param name="writeThrough">True if the call should block the current thread until the packet has been committed,
        /// false otherwise.</param>
        /// <returns>The packet envelope for the packet that was queued, or null if the packet was discarded.</returns>
        private PacketEnvelope QueuePacket(IMessengerPacket packet, bool writeThrough)
        {
            //even though the packet might already have a timestamp that's preferable to ours, we're deciding we're the judge of order to ensure it aligns with sequence.
            packet.Timestamp = DateTimeOffset.Now; //we convert to UTC during serialization, we want local time.

            //wrap it in a packet envelope and indicate we're in write through mode.
            PacketEnvelope packetEnvelope = new PacketEnvelope(packet, writeThrough);

            //But what queue do we put the packet in?
            if ((m_MessageOverflowQueue.Count > 0) || (m_MessageQueue.Count > m_MessageQueueMaxLength))
            {
                // We are currently using the overflow queue, so we'll put it there.
                // However, if we were called by a must-not-block thread, we want to discard overflow packets...
                // unless it's a command packet, which is too important to discard (it just won't wait on pending).
                if (t_ThreadMustNotBlock && !packetEnvelope.IsCommand)
                {
                    packetEnvelope = null; // We won't queue this packet, so there's no envelope to hang onto.
                }
                else
                {
                    m_MessageOverflowQueue.Enqueue(packetEnvelope);

                    //and set that it's pending so our caller knows they need to wait for it.
                    packetEnvelope.IsPending = true;
                }
            }
            else
            {
                //just queue the packet, we don't want to wait.
                m_MessageQueue.Enqueue(packetEnvelope);
            }

            return(packetEnvelope);
        }
예제 #2
0
        /// <summary>
        /// Suspends the calling thread until the provided packet is no longer pending.
        /// </summary>
        /// <remarks>This method performs its own synchronization and should not be done within a lock.</remarks>
        /// <param name="packetEnvelope">The packet that must be submitted</param>
        private static void WaitOnPending(PacketEnvelope packetEnvelope)
        {
            //we are monitoring for pending by using object locking, so get the lock...
            lock (packetEnvelope)
            {
                //and now we wait for it to be submitted...
                while (packetEnvelope.IsPending)
                {
                    // This releases the envelope lock, only reacquiring it after being woken up by a call to Pulse
                    System.Threading.Monitor.Wait(packetEnvelope);
                }

                //as we exit, we need to pulse the packet envelope in case there is another thread waiting
                //on it as well.
                System.Threading.Monitor.PulseAll(packetEnvelope);
            }
        }
예제 #3
0
        /// <summary>
        /// Publish the provided batch of packets.
        /// </summary>
        /// <param name="packetArray">An array of packets to publish as a batch.</param>
        /// <param name="writeThrough">True if the information contained in packet should be committed synchronously,
        /// false if the publisher can use write caching (when available).</param>
        public void Publish(IMessengerPacket[] packetArray, bool writeThrough)
        {
            // Sanity-check the most likely no-op cases before we bother with the lock
            if (packetArray == null)
            {
                return;
            }

            // Check for nulls from the end to find the last valid packet.
            int count     = packetArray.Length;
            int lastIndex = count - 1;

            while (lastIndex >= 0 && packetArray[lastIndex] == null)
            {
                lastIndex--;
            }

            if (lastIndex < 0)
            {
                return; // An array of only null packets (or empty), just quick bail.  Don't bother with the lock.
            }
            //resolve users...
            var        resolver  = m_PrincipalResolver;
            IPrincipal principal = null;

            if (resolver != null)
            {
                //and set that user to each packet that wants to track the current user (and doesn't have one manually set)
                foreach (var packet in packetArray.AsEnumerable().OfType <IUserPacket>().Where(p => p.Principal == null))
                {
                    //we only want to resolve the principal once per block, even if there are multiple messages.
                    if (principal == null)
                    {
                        //before we resolve the principal make sure our thread isn't *currently* trying to resolve a principal.
                        if (!t_ThreadMustNotResolvePrincipal)
                        {
                            try
                            {
                                t_ThreadMustNotResolvePrincipal = true;
                                var resolved = resolver.TryResolveCurrentPrincipal(out principal);
                                if (resolved == false)
                                {
                                    principal = null;                    //in case they broke the contract..
                                }
                            }
                            catch (Exception ex)
                            {
                                Log.DebugBreak();
                                GC.KeepAlive(ex);
                            }
                            finally
                            {
                                t_ThreadMustNotResolvePrincipal = false;
                            }
                        }

                        if (principal == null)
                        {
                            break; //no point in keeping trying if we filed to resolve the principal..
                        }
                    }
                    packet.Principal = principal;
                }
            }

            PacketEnvelope lastPacketEnvelope = null;

            bool effectiveWriteThrough;
            bool isPending;
            int  queuedCount = 0;

            // Get the queue lock.
            lock (m_MessageQueueLock)
            {
                if (m_Shutdown) // If we're already shut down, just bail.  We'll never process it anyway.
                {
                    return;
                }

                // Check to see if either the overall force write through or the local write through are set...
                // or if we are in ExitingMode.  In those cases, we'll want to block until the packet is committed.
                effectiveWriteThrough = (m_ForceWriteThrough || writeThrough || m_ExitingMode);
                for (int i = 0; i < count; i++)
                {
                    IMessengerPacket packet = packetArray[i];

                    // We have to double-check each element for null, or QueuePacket() would barf on it.
                    if (packet != null)
                    {
                        // We have a real packet, so queue it.  Only WriteThrough for the last packet, to flush the rest.
                        PacketEnvelope packetEnvelope = QueuePacket(packet, effectiveWriteThrough && i >= lastIndex);

                        // If a null is returned, the packet wasn't queued, so don't overwrite lastPacketEnvelope.
                        if (packetEnvelope != null)
                        {
                            queuedCount++;
                            lastPacketEnvelope = packetEnvelope; // Keep track of the last one queued.

                            if (!m_ExitMode && packetEnvelope.IsCommand)
                            {
                                CommandPacket commandPacket = (CommandPacket)packet;
                                if (commandPacket.Command == MessagingCommand.ExitMode)
                                {
                                    // Once we *receive* an ExitMode command, all subsequent messages queued
                                    // need to block, to make sure the process stays alive for any final logging
                                    // foreground threads might have.  We will be switching the Publisher to a
                                    // background thread when we process the ExitMode command so we don't hold
                                    // up the process beyond its own foreground threads.
                                    m_ExitingMode = true; // Force writeThrough blocking from now on.

                                    // Set the ending status, if it needs to be (probably won't).
                                    SessionStatus endingStatus = (SessionStatus)commandPacket.State;
                                    if (m_SessionSummary.Status < endingStatus)
                                    {
                                        m_SessionSummary.Status = endingStatus;
                                    }
                                }
                            }
                        }
                    }
                }

                if (effectiveWriteThrough && t_ThreadMustNotBlock == false && queuedCount > 0 &&
                    (lastPacketEnvelope == null || ReferenceEquals(lastPacketEnvelope.Packet, packetArray[lastIndex]) == false))
                {
                    // The expected WriteThrough packet got dropped because of overflow?  But we still need to block until
                    // those queued have completed, so issue a specific Flush command packet, which should not get dropped.
                    CommandPacket  flushPacket   = new CommandPacket(MessagingCommand.Flush);
                    PacketEnvelope flushEnvelope = QueuePacket(flushPacket, true);
                    if (flushEnvelope != null)
                    {
                        lastPacketEnvelope = flushEnvelope;
                    }
                }

                // Grab the pending flag before we release the lock so we know we have a consistent view.
                // If we didn't queue any packets then lastPacketEnvelope will be null and there's nothing to be pending.
                isPending = (lastPacketEnvelope == null) ? false : lastPacketEnvelope.IsPending;

                // Now signal our next thread that might be waiting that the lock will be released.
                System.Threading.Monitor.PulseAll(m_MessageQueueLock);
            }

            // Make sure our dispatch thread is still going.  This has its own independent locking (when necessary),
            // so we don't need to hold up other threads that are publishing.
            EnsureMessageDispatchThreadIsValid();

            if (lastPacketEnvelope == null || t_ThreadMustNotBlock)
            {
                // If we had no actual packets queued (e.g. shutdown, or no packets to queue), there's nothing to wait on.
                // Also, special case for must-not-block threads.  Once it's on the queue (or not), don't wait further.
                // We need the thread to get back to processing stuff off the queue or we're deadlocked!
                return;
            }

            // See if we need to wait because we've degraded to synchronous message handling due to a backlog of messages
            if (isPending)
            {
                // This routine does its own locking so we don't need to interfere with the nominal case of
                // not needing to pend.
                WaitOnPending(lastPacketEnvelope);
            }

            // Finally, if we need to wait on the write to complete now we want to stall.  We had to do this outside of
            // the message queue lock to ensure we don't block other threads.
            if (effectiveWriteThrough)
            {
                WaitOnPacket(lastPacketEnvelope);
            }
        }
예제 #4
0
        /// <summary>
        /// Send the packet to every current messenger and add it to the packet cache if it's cachable
        /// </summary>
        /// <param name="envelope"></param>
        private void DispatchPacket(PacketEnvelope envelope)
        {
            IMessengerPacket packet;

            lock (envelope)
            {
                packet = envelope.Packet; // rather than dig it out each time
                bool writeThrough = envelope.WriteThrough;

                // Any special handling for this packet?
                if (envelope.IsCommand)
                {
                    //this is a command packet, we process it as a command instead of just a data message
                    CommandPacket commandPacket = (CommandPacket)packet;

                    // Is this our exit or shutdown packet?  We need to handle those here.
                    if (commandPacket.Command == MessagingCommand.ExitMode)
                    {
                        m_ExitMode = true; // Mark us in ExitMode.  We will be by the time this method returns.
                        // Make sure we block until each messenger flushes, even if we weren't already in writeThrough mode.
                        writeThrough = true;
                    }
                    else if (commandPacket.Command == MessagingCommand.CloseMessenger)
                    {
                        m_Shutdown = true; // Mark us as shut down.  We will be by the time this method returns.
                        // Make sure we block until each messenger closes, even if we weren't already in writeThrough mode.
                        writeThrough = true;
                    }
                }
                else
                {
                    // Not a command, so it must be a Gibraltar data packet of some type.

                    //stamp the packet, and all of its dependent packets (this sets the sequence number)
                    StampPacket(packet, packet.Timestamp);

                    GibraltarPacket gibraltarPacket = packet as GibraltarPacket;
                    if (gibraltarPacket != null)
                    {
                        //this is a gibraltar packet so lets go ahead and fix the data in place now that we're on the background thread.
                        gibraltarPacket.FixData();
                    }

                    //resolve the application user if feasible..
                    if (packet is IUserPacket userPacket && userPacket.Principal != null)
                    {
                        var userResolver = m_ApplicationUserProvider;
                        if (userResolver != null)
                        {
                            ResolveApplicationUser(userResolver, userPacket);
                        }
                    }

                    //and finally run it through our filters..
                    var cancel  = false;
                    var filters = m_Filters;
                    if (filters != null)
                    {
                        foreach (var filter in filters)
                        {
                            try
                            {
                                filter.Process(packet, ref cancel);
                                if (cancel)
                                {
                                    break;
                                }
                            }
                            catch (Exception)
                            {
                                Log.DebugBreak(); // Catch this in the debugger, but otherwise swallow any errors.
                            }
                        }
                    }

                    //if a filter canceled then we can't write out this packet.
                    if (cancel)
                    {
                        envelope.IsCommitted = true; //so people waiting on us don't stall..
                        return;
                    }
                }

                //If this is a header packet we want to put it in the header list now - that way
                //if any messenger recycles while we are writing to the messengers it will be there.
                //(Better to pull the packet forward than to risk having it in an older stream but not a newer stream)
                if (envelope.IsHeader)
                {
                    lock (m_HeaderPacketsLock)
                    {
                        m_HeaderPackets.Add((ICachedMessengerPacket)packet);
                        System.Threading.Monitor.PulseAll(m_HeaderPacketsLock);
                    }
                }

                // Data message or Command packet - either way, send it on to each messenger.
                foreach (IMessenger messenger in m_Messengers)
                {
                    //we don't want an exception with one messenger to cause us a problem, so each gets its own try/catch
                    try
                    {
                        messenger.Write(packet, writeThrough);
                    }
                    catch (Exception)
                    {
                        Log.DebugBreak(); // Stop in debugger, ignore in production.
                    }
                }

                //if this was a write through packet we need to let the caller know that it was committed.
                envelope.IsCommitted = true; //under the covers this does a pulse on the threads waiting on this envelope.
            }

            // Now that it's committed, finally send it to any Notifiers that may be subscribed.
            QueueToNotifier(packet);

            //we only need to do this here if the session file writer is disabled; otherwise it's doing it at the best boundary.
            if ((m_Configuration.SessionFile.Enabled == false) &&
                (packet.Sequence % 8192 == 0))
            {
                StringReference.Pack();
            }
        }
예제 #5
0
        /// <summary>
        /// The main method of the message dispatch thread.
        /// </summary>
        private void MessageDispatchMain()
        {
            try
            {
                // Before initialization... We must never allow this thread (which processes the queue!) to block
                // when adding items to our queue, or we would deadlock.  (Does not need the lock to set this.)
                ThreadMustNotBlock();

                bool backgroundThread;

                // Now we need to make sure we're initialized.
                lock (m_MessageDispatchThreadLock)
                {
                    //are we initialized?
                    EnsureInitialized();

                    backgroundThread = m_MessageDispatchThread.IsBackground; // distinguish which we are.

                    System.Threading.Monitor.PulseAll(m_MessageDispatchThreadLock);
                }

                // Enter our main loop - dequeue packets and write them to all of the messengers.
                // Foreground thread should exit when we process exit, but a background thread should continue.
                while (m_Shutdown == false && (!m_ExitMode || backgroundThread))
                {
                    PacketEnvelope currentPacket = null;
                    lock (m_MessageQueueLock)
                    {
                        // Is this check needed?  We check m_ExitMode above outside the lock, so it may not have been
                        // up to date, but now we have the lock it should be.  If we're still on the foreground thread
                        // with m_ExitMode set to true, we want to exit the thread and create a background thread to
                        // continue dispatching, which we do upon exiting the while loop.
                        if (m_ExitMode && !backgroundThread)
                        {
                            break;
                        }

                        // If the queue is empty, wait for an item to be added
                        // This is a while loop, as we may be pulsed but not wake up before another thread has come in and
                        // consumed the newly added object or done something to modify the queue. In that case, we'll have to wait for another pulse.
                        while ((m_MessageQueue.Count == 0) && (m_Shutdown == false))
                        {
                            // This releases the message queue lock, only reacquiring it after being woken up by a call to Pulse
                            System.Threading.Monitor.Wait(m_MessageQueueLock);
                        }

                        if (m_MessageQueue.Count > 0)
                        {
                            //if we got here then there was an item in the queue AND we have the lock.  Dequeue the item and then we want to release our lock.
                            currentPacket = m_MessageQueue.Dequeue();

                            //and are we now below the maximum packet queue?  if so we can release the pending items.
                            while ((m_MessageOverflowQueue.Count > 0) && (m_MessageQueue.Count < m_MessageQueueMaxLength))
                            {
                                //we still have an item in the overflow queue and we have room for it, so lets add it.
                                PacketEnvelope currentOverflowEnvelope = m_MessageOverflowQueue.Dequeue();

                                m_MessageQueue.Enqueue(currentOverflowEnvelope);

                                //and indicate that we've submitted this queue item. This does a thread pulse under the covers,
                                //and gets its own lock so we should NOT lock the envelope.
                                currentOverflowEnvelope.IsPending = false;
                            }
                        }

                        //now pulse the next waiting thread there are that we've dequeued the packet.
                        System.Threading.Monitor.PulseAll(m_MessageQueueLock);
                    }

                    //We have a packet and have released the lock (so others can queue more packets while we're dispatching items.
                    if (currentPacket != null)
                    {
                        lock (m_ConfigLock)
                        {
                            DispatchPacket(currentPacket);
                        }
                    }
                }

                // We only get here if we exited the loop because a foreground thread sees we are in ExitMode,
                // or if we are completely shut down.

                // Clear the dispatch thread variable since we're about to exit it and...
                m_MessageDispatchThread = null;
                if (m_Shutdown == false)
                {
                    CreateMessageDispatchThread(); // Recreate one as a background thread, if we aren't shut down.
                }
            }
            catch (Exception ex)
            {
                lock (m_MessageDispatchThreadLock)
                {
                    //clear the dispatch thread variable since we're about to exit.
                    m_MessageDispatchThread = null;

                    //we want to write out that we had a problem and mark that we're failed so we'll get restarted.
                    m_MessageDispatchThreadFailed = true;

                    System.Threading.Monitor.PulseAll(m_MessageDispatchThreadLock);
                }

                OnThreadAbort();

                GC.KeepAlive(ex);
            }
        }