Ejemplo n.º 1
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);
            }
        }
Ejemplo n.º 2
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();
            }
        }