private void OnStatusChanged(object sender, bool started)
            {
                if (started)
                {
                    return;
                }

                if (_shutdownCancellationTokenSource.IsCancellationRequested)
                {
                    lock (_gate)
                    {
                        // RemoteHost has been disabled
                        _remoteClientTask = null;
                    }
                }
                else
                {
                    lock (_gate)
                    {
                        // save last remoteHostClient
                        s_lastRemoteClientTask = _remoteClientTask;

                        // save NoOpRemoteHostClient to remoteClient so that all RemoteHost call becomes
                        // No Op. this basically have same effect as disabling all RemoteHost features
                        _remoteClientTask = Task.FromResult <RemoteHostClient>(new RemoteHostClient.NoOpClient(_workspace));
                    }

                    // s_lastRemoteClientTask info should be saved in the dump
                    // report NFW when connection is closed unless it is proper shutdown
                    WatsonReporter.Report(new Exception("Connection to remote host closed"));

                    RemoteHostCrashInfoBar.ShowInfoBar(_workspace);
                }
            }
        public static async Task <Stream> RequestServiceAsync(
            Workspace workspace,
            HubClient client,
            string serviceName,
            HostGroup hostGroup,
            CancellationToken cancellationToken)
        {
            var descriptor = new ServiceDescriptor(serviceName)
            {
                HostGroup = hostGroup
            };

            try
            {
                return(await client.RequestServiceAsync(descriptor, cancellationToken).ConfigureAwait(false));
            }
            catch (Exception e) when(ReportNonFatalWatson(e, cancellationToken))
            {
                // TODO: Once https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1040692.
                // ServiceHub may throw non-cancellation exceptions if it is called after VS started to shut down,
                // even if our cancellation token is signaled. Cancel the operation and do not report an error in these cases.
                //
                // If ServiceHub did not throw non-cancellation exceptions when cancellation token is signaled,
                // we can assume that these exceptions indicate a failure and should be reported to the user.
                cancellationToken.ThrowIfCancellationRequested();

                RemoteHostCrashInfoBar.ShowInfoBar(workspace, e);

                // TODO: Propagate the original exception (see https://github.com/dotnet/roslyn/issues/40476)
                throw new SoftCrashException("Unexpected exception from HubClient", e, cancellationToken);
            }
            public static async Task <Stream> RequestServiceAsync(
                Workspace workspace,
                HubClient client,
                string serviceName,
                HostGroup hostGroup,
                TimeSpan timeout,
                CancellationToken cancellationToken)
            {
                const int MaxRetryAttempts = 10;
                var       retryDelay       = TimeSpan.FromMilliseconds(50);

                Exception lastException = null;

                var descriptor = new ServiceDescriptor(serviceName)
                {
                    HostGroup = hostGroup
                };

                // Call to get service can fail due to this bug - devdiv#288961 or more.
                // until root cause is fixed, we decide to have retry rather than fail right away
                //
                // We have double re-try here. We have these 2 separated since 2 retries are for different problems.
                // First retry most likely deal with real issue on ServiceHub, second retry (cancellation) is to deal with
                // ServiceHub behavior we don't want to use.
                for (var i = 0; i < MaxRetryAttempts; i++)
                {
                    try
                    {
                        return(await RequestServiceWithCancellationRetryAsync(
                                   workspace,
                                   client,
                                   descriptor,
                                   timeout,
                                   cancellationToken).ConfigureAwait(false));
                    }
                    catch (Exception ex) when(!(ex is OperationCanceledException))
                    {
                        // save info only if it failed with different issue than before.
                        if (lastException?.Message != ex.Message)
                        {
                            // RequestServiceAsync should never fail unless service itself is actually broken.
                            // So far, we catched multiple issues from this NFW. so we will keep this NFW.
                            ex.ReportServiceHubNFW("RequestServiceAsync Failed");

                            lastException = ex;
                        }
                    }

                    // wait before next try
                    await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false);
                }

                RemoteHostCrashInfoBar.ShowInfoBar(workspace, lastException);

                // raise soft crash exception rather than doing hard crash.
                // we had enough feedback from users not to crash VS on servicehub failure
                throw new SoftCrashException("RequestServiceAsync Failed", lastException, cancellationToken);
            }
Пример #4
0
        private void ThrowSoftCrashException(Exception ex, CancellationToken token)
        {
            RemoteHostCrashInfoBar.ShowInfoBar(Workspace);

            // log disconnect information before throw
            LogDisconnectInfo(_debuggingLastDisconnectReason, _debuggingLastDisconnectCallstack);

            // throw soft crash exception
            throw new SoftCrashException("remote host call failed", ex, token);
        }
            private static async Task <Stream> RequestServiceWithCancellationRetryAsync(
                Workspace workspace,
                HubClient client,
                ServiceDescriptor descriptor,
                TimeSpan timeout,
                CancellationToken cancellationToken)
            {
                var retryDelay = TimeSpan.FromMilliseconds(50);

                using (var pooledStopwatch = SharedPools.Default <Stopwatch>().GetPooledObject())
                {
                    var watch = pooledStopwatch.Object;
                    watch.Start();

                    while (watch.Elapsed < timeout)
                    {
                        cancellationToken.ThrowIfCancellationRequested();

                        try
                        {
                            return(await client.RequestServiceAsync(descriptor, cancellationToken).ConfigureAwait(false));
                        }
                        catch (OperationCanceledException) when(!cancellationToken.IsCancellationRequested)
                        {
                            // Retry on cancellation that is not sourced by our cancellation token.
                            // Since HubClient will throw when it can't connect to service hub service (e.g. timeout, disposal).
                        }

                        // wait before next try
                        await Task.Delay(retryDelay, cancellationToken).ConfigureAwait(false);

                        // if we tried for too long and still couldn't connect, report non-fatal Watson
                        if (!s_timeoutReported && watch.Elapsed > s_reportTimeout)
                        {
                            s_timeoutReported = true;

                            // report service hub logs along with dump
                            new Exception("RequestServiceAsync Timeout").ReportServiceHubNFW("RequestServiceAsync Timeout");
                        }
                    }
                }

                // operation timed out, more than we are willing to wait
                RemoteHostCrashInfoBar.ShowInfoBar(workspace);

                // throw soft crash exception to minimize hard crash. it doesn't
                // guarantee 100% hard crash free. but 99% it doesn't cause
                // hard crash
                throw new SoftCrashException("retry timed out", cancellationToken);
            }
        public static async Task <ServiceHubRemoteHostClient> CreateWorkerAsync(Workspace workspace, HubClient primary, TimeSpan timeout, CancellationToken cancellationToken)
        {
            ServiceHubRemoteHostClient client = null;

            try
            {
                // let each client to have unique id so that we can distinguish different clients when service is restarted
                var current = CreateClientId(Process.GetCurrentProcess().Id.ToString());

                var hostGroup = new HostGroup(current);

                // Create the RemotableDataJsonRpc before we create the remote host: this call implicitly sets up the remote IExperimentationService so that will be available for later calls
                var remotableDataRpc = new RemotableDataJsonRpc(
                    workspace, primary.Logger,
                    await Connections.RequestServiceAsync(workspace, primary, WellKnownServiceHubServices.SnapshotService, hostGroup, timeout, cancellationToken).ConfigureAwait(false));

                var remoteHostStream = await Connections.RequestServiceAsync(workspace, primary, WellKnownRemoteHostServices.RemoteHostService, hostGroup, timeout, cancellationToken).ConfigureAwait(false);

                var enableConnectionPool = workspace.Options.GetOption(RemoteHostOptions.EnableConnectionPool);
                var maxConnection        = workspace.Options.GetOption(RemoteHostOptions.MaxPoolConnection);

                var connectionManager = new ConnectionManager(primary, hostGroup, enableConnectionPool, maxConnection, timeout, new ReferenceCountedDisposable <RemotableDataJsonRpc>(remotableDataRpc));

                client = new ServiceHubRemoteHostClient(workspace, primary.Logger, connectionManager, remoteHostStream);

                var uiCultureLCID = CultureInfo.CurrentUICulture.LCID;
                var cultureLCID   = CultureInfo.CurrentCulture.LCID;

                // make sure connection is done right
                var host = await client._rpc.InvokeWithCancellationAsync <string>(
                    nameof(IRemoteHostService.Connect), new object[] { current, uiCultureLCID, cultureLCID, TelemetryService.DefaultSession.SerializeSettings() }, cancellationToken).ConfigureAwait(false);

                return(client);
            }
            catch (ConnectionLostException ex)
            {
                RemoteHostCrashInfoBar.ShowInfoBar(workspace, ex);

                Shutdown(client, ex, cancellationToken);

                // dont crash VS because OOP is failed to start. we will show info bar telling users to restart
                // but never physically crash VS.
                throw new SoftCrashException("Connection Lost", ex, cancellationToken);
            }
            catch (Exception ex)
            {
                Shutdown(client, ex, cancellationToken);
                throw;
            }
        /// <summary>
        /// call <paramref name="funcAsync"/> and retry up to <paramref name="timeout"/> if the call throws
        /// <typeparamref name="TException"/>. any other exception from the call won't be handled here.
        /// </summary>
        private static async Task <TResult> RetryRemoteCallAsync <TException, TResult>(
            Func <Task <TResult> > funcAsync,
            TimeSpan timeout,
            CancellationToken cancellationToken) where TException : Exception
        {
            const int retry_delayInMS = 50;

            using (var pooledStopwatch = SharedPools.Default <Stopwatch>().GetPooledObject())
            {
                var watch = pooledStopwatch.Object;
                watch.Start();

                while (watch.Elapsed < timeout)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    try
                    {
                        return(await funcAsync().ConfigureAwait(false));
                    }
                    catch (TException)
                    {
                        // throw cancellation token if operation is cancelled
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    // wait for retry_delayInMS before next try
                    await Task.Delay(retry_delayInMS, cancellationToken).ConfigureAwait(false);

                    ReportTimeout(watch);
                }
            }

            // operation timed out, more than we are willing to wait
            RemoteHostCrashInfoBar.ShowInfoBar();

            // user didn't ask for cancellation, but we can't fullfill this request. so we
            // create our own cancellation token and then throw it. this doesn't guarantee
            // 100% that we won't crash, but this is at least safest way we know until user
            // restart VS (with info bar)
            using (var ownCancellationSource = new CancellationTokenSource())
            {
                ownCancellationSource.Cancel();
                ownCancellationSource.Token.ThrowIfCancellationRequested();
            }

            throw ExceptionUtilities.Unreachable;
        }
Пример #8
0
        private SoftCrashException CreateSoftCrashException(Exception ex, CancellationToken cancellationToken)
        {
            // we are getting unexpected exception from service hub. rather than doing hard crash on unexpected exception,
            // we decided to do soft crash where we show info bar to users saying "VS got corrupted and users should save
            // their works and close VS"

            cancellationToken.ThrowIfCancellationRequested();

            LogError($"exception: {ex.ToString()}");
            RemoteHostCrashInfoBar.ShowInfoBar(Workspace, ex);

            // log disconnect information before throw
            LogDisconnectInfo(_debuggingLastDisconnectReason, _debuggingLastDisconnectCallstack);

            // throw soft crash exception
            return(new SoftCrashException("remote host call failed", ex, cancellationToken));
        }
Пример #9
0
 private async Task RpcInvokeAsync(string targetName, params object[] arguments)
 {
     // handle exception gracefully. don't crash VS due to this.
     // especially on shutdown time. because of pending async BG work such as
     // OnGlobalOperationStarted and more, we can get into a situation where either
     // we are in the middle of call when we are disconnected, or we runs
     // after shutdown.
     try
     {
         await _rpc.InvokeWithCancellationAsync(targetName, arguments?.AsArray(), _shutdownCancellationTokenSource.Token).ConfigureAwait(false);
     }
     catch (Exception ex) when(ReportUnlessCanceled(ex))
     {
         if (!_shutdownCancellationTokenSource.IsCancellationRequested)
         {
             RemoteHostCrashInfoBar.ShowInfoBar(Workspace, ex);
         }
     }
 }
Пример #10
0
            /// <summary>
            /// call <paramref name="funcAsync"/> and retry up to <paramref name="timeout"/> if the call throws
            /// <typeparamref name="TException"/>. any other exception from the call won't be handled here.
            /// </summary>
            public static async Task <TResult> RetryRemoteCallAsync <TException, TResult>(
                Workspace workspace,
                Func <Task <TResult> > funcAsync,
                TimeSpan timeout,
                CancellationToken cancellationToken) where TException : Exception
            {
                const int retry_delayInMS = 50;

                using (var pooledStopwatch = SharedPools.Default <Stopwatch>().GetPooledObject())
                {
                    var watch = pooledStopwatch.Object;
                    watch.Start();

                    while (watch.Elapsed < timeout)
                    {
                        cancellationToken.ThrowIfCancellationRequested();

                        try
                        {
                            return(await funcAsync().ConfigureAwait(false));
                        }
                        catch (TException)
                        {
                            // throw cancellation token if operation is cancelled
                            cancellationToken.ThrowIfCancellationRequested();
                        }

                        // wait for retry_delayInMS before next try
                        await Task.Delay(retry_delayInMS, cancellationToken).ConfigureAwait(false);

                        ReportTimeout(watch);
                    }
                }

                // operation timed out, more than we are willing to wait
                RemoteHostCrashInfoBar.ShowInfoBar(workspace);

                // throw soft crash exception to minimize hard crash. it doesn't
                // gurantee 100% hard crash free. but 99% it doesn't cause
                // hard crash
                throw new SoftCrashException("retry timed out", cancellationToken);
            }
        public static async Task <Stream> RequestServiceAsync(
            HostWorkspaceServices services,
            HubClient client,
            RemoteServiceName serviceName,
            HostGroup hostGroup,
            CancellationToken cancellationToken)
        {
            var is64bit = RemoteHostOptions.IsServiceHubProcess64Bit(services);

            // Make sure we are on the thread pool to avoid UI thread dependencies if external code uses ConfigureAwait(true)
            await TaskScheduler.Default;

            var descriptor = new ServiceDescriptor(serviceName.ToString(is64bit))
            {
                HostGroup = hostGroup
            };

            try
            {
                return(await client.RequestServiceAsync(descriptor, cancellationToken).ConfigureAwait(false));
            }
            catch (Exception e) when(ReportNonFatalWatson(e, cancellationToken))
            {
                // TODO: Once https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1040692.
                // ServiceHub may throw non-cancellation exceptions if it is called after VS started to shut down,
                // even if our cancellation token is signaled. Cancel the operation and do not report an error in these cases.
                //
                // If ServiceHub did not throw non-cancellation exceptions when cancellation token is signaled,
                // we can assume that these exceptions indicate a failure and should be reported to the user.
                cancellationToken.ThrowIfCancellationRequested();

                RemoteHostCrashInfoBar.ShowInfoBar(services, e);

                // TODO: Propagate the original exception (see https://github.com/dotnet/roslyn/issues/40476)
                throw new SoftCrashException("Unexpected exception from HubClient", e, cancellationToken);
            }
 private void OnUnexpectedExceptionThrown(Exception unexpectedException)
 => RemoteHostCrashInfoBar.ShowInfoBar(Workspace, unexpectedException);
Пример #13
0
        public static async Task <RemoteHostClient?> CreateAsync(Workspace workspace, CancellationToken cancellationToken)
        {
            using (Logger.LogBlock(FunctionId.ServiceHubRemoteHostClient_CreateAsync, cancellationToken))
            {
                var timeout = TimeSpan.FromMilliseconds(workspace.Options.GetOption(RemoteHostOptions.RequestServiceTimeoutInMS));
                var enableConnectionPool = workspace.Options.GetOption(RemoteHostOptions.EnableConnectionPool);
                var maxConnection        = workspace.Options.GetOption(RemoteHostOptions.MaxPoolConnection);

                // let each client to have unique id so that we can distinguish different clients when service is restarted
                var clientId = CreateClientId(Process.GetCurrentProcess().Id.ToString());

                var hostGroup = new HostGroup(clientId);
                var primary   = new HubClient("ManagedLanguage.IDE.RemoteHostClient");

                ServiceHubRemoteHostClient?client = null;
                try
                {
                    // Create the RemotableDataJsonRpc before we create the remote host: this call implicitly sets up the remote IExperimentationService so that will be available for later calls
                    var snapshotServiceStream = await Connections.RequestServiceAsync(workspace, primary, WellKnownServiceHubServices.SnapshotService, hostGroup, timeout, cancellationToken).ConfigureAwait(false);

                    var remoteHostStream = await Connections.RequestServiceAsync(workspace, primary, WellKnownRemoteHostServices.RemoteHostService, hostGroup, timeout, cancellationToken).ConfigureAwait(false);

                    var remotableDataRpc  = new RemotableDataJsonRpc(workspace, primary.Logger, snapshotServiceStream);
                    var connectionManager = new ConnectionManager(primary, hostGroup, enableConnectionPool, maxConnection, timeout, new ReferenceCountedDisposable <RemotableDataJsonRpc>(remotableDataRpc));

                    client = new ServiceHubRemoteHostClient(workspace, primary.Logger, connectionManager, remoteHostStream);

                    var uiCultureLCID = CultureInfo.CurrentUICulture.LCID;
                    var cultureLCID   = CultureInfo.CurrentCulture.LCID;

                    // make sure connection is done right
                    var host = await client._rpc.InvokeWithCancellationAsync <string>(
                        nameof(IRemoteHostService.Connect), new object[] { clientId, uiCultureLCID, cultureLCID, TelemetryService.DefaultSession.SerializeSettings() }, cancellationToken).ConfigureAwait(false);

                    client.Started();

                    return(client);
                }
                catch (ConnectionLostException ex)
                {
                    RemoteHostCrashInfoBar.ShowInfoBar(workspace, ex);

                    Shutdown(ex);

                    // dont crash VS because OOP is failed to start. we will show info bar telling users to restart
                    // but never physically crash VS.
                    return(null);
                }
                catch (SoftCrashException ex)
                {
                    Shutdown(ex);

                    // at this point, we should have shown info bar (RemoteHostCrashInfoBar.ShowInfoBar) to users
                    // returning null here will disable OOP for this VS session.
                    // * Note * this is not trying to recover the exception. but giving users to time
                    // to clean up before restart VS
                    return(null);
                }
                catch (Exception ex)
                {
                    Shutdown(ex);
                    throw;
                }

                void Shutdown(Exception ex)
                {
                    // make sure we shutdown client if initializing client has failed.
                    client?.Shutdown();

                    // translate to our own cancellation if it is raised.
                    cancellationToken.ThrowIfCancellationRequested();

                    // otherwise, report watson
                    ex.ReportServiceHubNFW("ServiceHub creation failed");
                }
            }
        }
Пример #14
0
 private void UnexpectedExceptionThrown(Exception exception)
 => RemoteHostCrashInfoBar.ShowInfoBar(_workspace, exception);
            public static async Task <Stream> RequestServiceAsync(
                Workspace workspace,
                HubClient client,
                string serviceName,
                HostGroup hostGroup,
                TimeSpan timeout,
                CancellationToken cancellationToken)
            {
                const int max_retry       = 10;
                const int retry_delayInMS = 50;

                RemoteInvocationException lastException = null;

                var descriptor = new ServiceDescriptor(serviceName)
                {
                    HostGroup = hostGroup
                };

                // call to get service can fail due to this bug - devdiv#288961 or more.
                // until root cause is fixed, we decide to have retry rather than fail right away
                for (var i = 0; i < max_retry; i++)
                {
                    try
                    {
                        // we are wrapping HubClient.RequestServiceAsync since we can't control its internal timeout value ourselves.
                        // we have bug opened to track the issue.
                        // https://devdiv.visualstudio.com/DefaultCollection/DevDiv/Editor/_workitems?id=378757&fullScreen=false&_a=edit

                        // retry on cancellation token since HubClient will throw its own cancellation token
                        // when it couldn't connect to service hub service for some reasons
                        // (ex, OOP process GC blocked and not responding to request)
                        //
                        // we have double re-try here. we have these 2 seperated since 2 retries are for different problems.
                        // as noted by 2 different issues above at the start of each 2 different retries.
                        // first retry most likely deal with real issue on servicehub, second retry (cancellation) is to deal with
                        // by design servicehub behavior we don't want to use.
                        return(await RetryRemoteCallAsync <OperationCanceledException, Stream>(
                                   workspace,
                                   () => client.RequestServiceAsync(descriptor, cancellationToken),
                                   timeout,
                                   cancellationToken).ConfigureAwait(false));
                    }
                    catch (RemoteInvocationException ex)
                    {
                        // save info only if it failed with different issue than before.
                        if (lastException?.Message != ex.Message)
                        {
                            // RequestServiceAsync should never fail unless service itself is actually broken.
                            // So far, we catched multiple issues from this NFW. so we will keep this NFW.
                            ex.ReportServiceHubNFW("RequestServiceAsync Failed");

                            lastException = ex;
                        }
                    }

                    // wait for retry_delayInMS before next try
                    await Task.Delay(retry_delayInMS, cancellationToken).ConfigureAwait(false);
                }

                RemoteHostCrashInfoBar.ShowInfoBar(workspace, lastException);

                // raise soft crash exception rather than doing hard crash.
                // we had enough feedback from users not to crash VS on servicehub failure
                throw new SoftCrashException("RequestServiceAsync Failed", lastException, cancellationToken);
            }
 private void OnUnexpectedExceptionThrown(Exception unexpectedException)
 => RemoteHostCrashInfoBar.ShowInfoBar(_services, unexpectedException);