Ejemplo n.º 1
0
        public void ValidateReentrancyPreventsReentrancyWithFuncVariant()
        {
            var  reentrancyGuard     = new ReentrancyGuard();
            bool isInGuard           = false;
            bool executedGuardedCode = false;

            using (ManualResetEvent nonReentrantCode = new ManualResetEvent(false))
                using (ManualResetEvent nonReentrantCodeDone = new ManualResetEvent(false))
                {
                    reentrancyGuard.IsReentrancyPrevented.Should().BeFalse();
                    var allowedThread = new Thread(() =>
                    {
                        ReentrancyGuard.CallReentrancySafe(reentrancyGuard, () =>
                        {
                            nonReentrantCode.Set();
                            isInGuard = true;
                            nonReentrantCodeDone.WaitOne();
                            isInGuard           = false;
                            executedGuardedCode = true;
                            return(executedGuardedCode);
                        }).Should().Be(ReentrancyGuard.ReentrancyCallResult.Success);
                        executedGuardedCode.ShouldBeEquivalentTo(true);
                    });
                    allowedThread.Start();
                    nonReentrantCode.WaitOne(TimeSpan.FromSeconds(1)).Should().BeTrue();

                    for (int i = 0; i < 1000; i++)
                    {
                        reentrancyGuard.IsReentrancyPrevented.Should().BeTrue();
                        ReentrancyGuard.CallReentrancySafe(reentrancyGuard, () =>
                        {
                            throw new Exception("Reentered method despite guard being present");
#pragma warning disable 162
                            return(true);

#pragma warning restore 162
                        }).ShouldBeEquivalentTo(ReentrancyGuard.ReentrancyCallResult.GuardBlocked);
                    }
                    nonReentrantCodeDone.Set();
                    allowedThread.Join();
                    reentrancyGuard.IsReentrancyPrevented.ShouldBeEquivalentTo(false);
                    executedGuardedCode.ShouldBeEquivalentTo(true);
                }
        }
        public CommandState GetCommandState <T>(Func <ITextView, ITextBuffer, T> argsFactory, Func <CommandState> nextCommandHandler) where T : EditorCommandArgs
        {
            if (!_factory.JoinableTaskContext.IsOnMainThread)
            {
                throw new InvalidOperationException($"{nameof(IEditorCommandHandlerService.GetCommandState)} method shoudl only be called on the UI thread.");
            }

            // In Razor scenario it's possible that EditorCommandHandlerService is called re-entrantly,
            // first by contained language command filter and then by editor command chain.
            // To preserve Razor commanding semantics, only execute handlers once.
            if (IsReentrantCall())
            {
                return(nextCommandHandler?.Invoke() ?? CommandState.Unavailable);
            }

            using (var reentrancyGuard = new ReentrancyGuard(_textView))
            {
                // Build up chain of handlers per buffer
                Func <CommandState> handlerChain = nextCommandHandler ?? UnavalableCommandFunc;
                foreach (var bufferAndHandler in GetOrderedBuffersAndCommandHandlers <T>().Reverse())
                {
                    T args = null;
                    // Declare locals to ensure that we don't end up capturing the wrong thing
                    var nextHandler = handlerChain;
                    var handler     = bufferAndHandler.handler;
                    args = args ?? (args = argsFactory(_textView, bufferAndHandler.buffer));
                    if (args == null)
                    {
                        // Args factory failed, skip command handlers and just call next
                        return(handlerChain());
                    }

                    handlerChain = () => handler.GetCommandState(args, nextHandler);
                }

                // Kick off the first command handler
                return(handlerChain());
            }
        }
        public void Execute <T>(Func <ITextView, ITextBuffer, T> argsFactory, Action nextCommandHandler) where T : EditorCommandArgs
        {
            if (!_factory.JoinableTaskContext.IsOnMainThread)
            {
                throw new InvalidOperationException($"{nameof(IEditorCommandHandlerService.Execute)} method should only be called on the UI thread.");
            }

            // In contained languge (Razor) scenario it's possible that EditorCommandHandlerService is called re-entrantly
            // for the same command, first by contained language command filter and then by editor command chain.
            // To preserve Razor commanding semantics, only execute handlers once for the same command.
            if (IsReentrantCall <T>())
            {
                nextCommandHandler?.Invoke();
                return;
            }

            EditorCommandHandlerServiceState state = null;

            using (var reentrancyGuard = new ReentrancyGuard <T>(_textView))
            {
                // Build up chain of handlers per buffer
                Action handlerChain = nextCommandHandler ?? EmptyAction;
                // TODO: realize the chain dynamically and without Reverse()
                foreach (var bufferAndHandler in GetOrderedBuffersAndCommandHandlers <T>().Reverse())
                {
                    T args = null;
                    // Declare locals to ensure that we don't end up capturing the wrong thing
                    var nextHandler = handlerChain;
                    var handler     = bufferAndHandler.handler;
                    args = args ?? (args = argsFactory(_textView, bufferAndHandler.buffer));
                    if (args == null)
                    {
                        // Args factory failed, skip command handlers and just call next
                        handlerChain();
                        return;
                    }

                    if (handler is IDynamicCommandHandler <T> dynamicCommandHandler &&
                        !dynamicCommandHandler.CanExecuteCommand(args))
                    {
                        // Skip this one as it cannot execute the command.
                        continue;
                    }

                    if (state == null)
                    {
                        state = InitializeExecutionState(args);
                    }

                    handlerChain = () => _factory.GuardedOperations.CallExtensionPoint(handler,
                                                                                       () =>
                    {
                        state.OnExecutingCommandHandlerBegin(handler);
                        handler.ExecuteCommand(args, nextHandler, state.ExecutionContext);
                        state.OnExecutingCommandHandlerEnd(handler);
                    },
                                                                                       // Do not guard against cancellation exceptions, they are handled by ExecuteCommandHandlerChain
                                                                                       exceptionGuardFilter: (e) => !IsOperationCancelledException(e));
                }

                if (state == null)
                {
                    // No matching command handlers, just call next
                    handlerChain();
                    return;
                }

                _telemetrySession?.BeforeKeyProcessed();
                ExecuteCommandHandlerChain(state, handlerChain, nextCommandHandler);
                _telemetrySession?.AfterKeyProcessed();
            }
        }
 private bool IsReentrantCall <T>() where T : EditorCommandArgs
 {
     return(ReentrancyGuard <T> .IsReentrantCall(_textView));
 }