예제 #1
0
        /// <summary>
        /// DevDiv:1192272
        ///
        /// This function has been changed to avoid re-entrancy issues.  Previously, the
        /// PenThread selection depended on calls to AddPenContext in the selection mechanism
        /// or when creating a new PenThread.  Since AddPenContext will wait on operations done
        /// on a PenThread, this would allow re-entrant calls to occur.  These calls had the
        /// potential to generate redundant PenThreads that could cause performance and functional
        /// issues in touch-enabled applications.
        ///
        /// By removing calls to AddPenContext from the selection loops, we can be certain that there is
        /// no re-entrancy possible during this part of the code.  The call to AddPenContext is now done
        /// post thread selection/creation.  While this is still re-entrant, we handle the possible issues
        /// from that case by retrying thread selection for any failed AddPenContext calls, ignoring the
        /// specific thread that failed.  After MAX_PENTHREAD_RETRIES, we exit and log an error.
        /// </summary>
        private PenThread GetPenThreadForPenContextHelper(PenContext penContext)
        {
            // A list of full PenThreads that we should ignore when attempting to select a thread
            // for this context.
            List <PenThread> ignoredThreads = new List <PenThread>();

            PenThread selectedPenThread = null;

            // We have gone over the max retries, something is forcing a huge amount
            // of re-entrancy.  In this case, break the loop and exit even if we might
            // have some issues with missing touch contexts and bad touch behavior.
            while (ignoredThreads.Count < MAX_PENTHREAD_RETRIES)
            {
                // Scan existing penthreads to find one to add the context to
                // We scan back to front to enable list cleanup.
                for (int i = _penThreadWeakRefList.Count - 1; i >= 0; i--)
                {
                    PenThread candidatePenThread = null;

                    // Select a thread if it's a valid WeakReference and we're not ignoring it
                    // Allow selection to happen multiple times so we get the first valid candidate
                    // in forward order.
                    if (_penThreadWeakRefList[i].TryGetTarget(out candidatePenThread) &&
                        !ignoredThreads.Contains(candidatePenThread))
                    {
                        selectedPenThread = candidatePenThread;
                    }
                    // This is an invalid WeakReference and should be removed
                    else if (candidatePenThread == null)
                    {
                        _penThreadWeakRefList.RemoveAt(i);
                    }
                }

                // If no valid thread was found, create a new one and add to the pool
                if (selectedPenThread == null)
                {
                    selectedPenThread = new PenThread();

                    _penThreadWeakRefList.Add(new WeakReference <PenThread>(selectedPenThread));
                }

                // If we have no context or we can successfully add to it, then end with this thread
                if (penContext == null || selectedPenThread.AddPenContext(penContext))
                {
                    break;
                }
                // If the add wasn't successful, this thread is full, so try again and ignore it
                else
                {
                    ignoredThreads.Add(selectedPenThread);

                    selectedPenThread = null;

                    // Log re-entrant calls
                    StylusTraceLogger.LogReentrancy();
                }
            }

            //  If we're here due to max retries, log errors appropriately
            if (selectedPenThread == null)
            {
                StylusTraceLogger.LogReentrancyRetryLimitReached();

                Debug.Assert(false, "Retry limit reached when acquiring PenThread");
            }

            return(selectedPenThread);
        }
예제 #2
0
        internal void UpdateTablets()
        {
            if (_tablets == null)
            {
                throw new ObjectDisposedException("TabletDeviceCollection");
            }

            // This method can be re-entered in a way that can cause deadlock
            // (Dev11 960656).  This can happen if multiple WM_DEVICECHANGE
            // messages are pending and we have not yet built the tablet collection.
            // Here's how:
            // 1. First WM_DEVICECHANGE message enters here, starts to launch pen thread
            // 2.   Penimc.UnsafeNativeMethods used for the first time, start its
            //              static constructor.
            // 3.     The static cctor starts to create a PimcManager via COM CLSID.
            // 4.       COM pumps message, a second WM_DEVICECHANGE re-enters here
            //              and starts to launch another pen thread.
            // 5.         The static cctor is skipped (although step 3 hasn't finished),
            //                  and the pen thread is started
            // 6.           The PenThreadWorker (on the UI thread) waits for the
            //                  PenThread to respond.
            // 7. Meanwhile the pen thread's code refers to Pimc.UnsafeNativeMethods.
            //      It's on a separate thread, so it waits for the static cctor to finish.
            // Deadlock:  UI thread is waiting for PenThread, but holding the CLR's
            //  lock on the static cctor.  The Pen thread is waiting for the static cctor.
            //
            // Multiple WM_DEVICECHANGE messages also cause re-entrancy even if the
            // static cctor has already finished.  There's no deadlock in that case,
            // but we end up with multiple pen threads (steps 1 and 4).  Besides being
            // redundant, that can cause problems when the threads collide with each
            // other or do work twice.
            //
            // In any case, the re-entrancy is harmful.  So we avoid it in the usual
            // way - setting a flag on entry and early-exit if the flag is set.
            //
            // Usually the outermost call will leave the tablet collection in the
            // right state, but there's a small chance that the OS or pen-input service
            // will change something while the outermost call is in progress, so that
            // the inner (re-entrant) call really would have picked up new information.
            // To handle that case, we'll simply re-run the outermost call if any
            // re-entrancy is detected.  Usually that will have no real effect, as
            // the code here detects when no change is made to the tablet collection.

            if (_inUpdateTablets)
            {
                // Log re-entrant calls
                StylusTraceLogger.LogReentrancy();

                // this is a re-entrant call.  Note that it happened, but do no work.
                _hasUpdateTabletsBeenCalledReentrantly = true;
                return;
            }

            try
            {
                _inUpdateTablets = true;
                do
                {
                    _hasUpdateTabletsBeenCalledReentrantly = false;

                    // do the real work
                    UpdateTabletsImpl();

                    // if re-entrancy happened, start over
                    // This could loop forever, but only if we get an unbounded
                    // number of re-entrant events;  that would have looped
                    // forever even without this re-entrancy logic.
                } while (_hasUpdateTabletsBeenCalledReentrantly);
            }
            finally
            {
                // when we're done (either normally or via exception)
                // reset the re-entrancy state
                _inUpdateTablets = false;
                _hasUpdateTabletsBeenCalledReentrantly = false;
            }
        }