File: RoslynRequestExecutionQueue.cs
Web Access
Project: src\src\LanguageServer\Protocol\Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj (Microsoft.CodeAnalysis.LanguageServer.Protocol)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Globalization;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CommonLanguageServerProtocol.Framework;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.LanguageServer
{
    internal sealed class RoslynRequestExecutionQueue : RequestExecutionQueue<RequestContext>
    {
        private readonly IInitializeManager _initializeManager;
        private readonly IAsynchronousOperationListener _listener;
 
        /// <summary>
        /// Serial access is guaranteed by the queue.
        /// </summary>
        private CultureInfo? _cultureInfo;
 
        public RoslynRequestExecutionQueue(AbstractLanguageServer<RequestContext> languageServer, ILspLogger logger, AbstractHandlerProvider handlerProvider, IAsynchronousOperationListenerProvider provider)
            : base(languageServer, logger, handlerProvider)
        {
            _initializeManager = languageServer.GetLspServices().GetRequiredService<IInitializeManager>();
            _listener = provider.GetListener(FeatureAttribute.LanguageServer);
        }
 
        public override async Task WrapStartRequestTaskAsync(Task nonMutatingRequestTask, bool rethrowExceptions)
        {
            using var token = _listener.BeginAsyncOperation(nameof(WrapStartRequestTaskAsync));
            if (rethrowExceptions)
            {
                try
                {
                    await nonMutatingRequestTask.ConfigureAwait(false);
                }
                catch (StreamJsonRpc.LocalRpcException localRpcException) when (localRpcException.ErrorCode == LspErrorCodes.ContentModified)
                {
                    // Content modified exceptions are expected and should not be reported as NFWs.
                    throw;
                }
                // If we had an exception, we want to record a NFW for it AND propogate it out to the queue so it can be handled appropriately.
                catch (Exception ex) when (FatalError.ReportAndPropagateUnlessCanceled(ex, ErrorSeverity.Critical))
                {
                    throw ExceptionUtilities.Unreachable();
                }
            }
            else
            {
                // The caller has asked us to not rethrow, so record a NFW and swallow.
                try
                {
                    await nonMutatingRequestTask.ConfigureAwait(false);
                }
                catch (StreamJsonRpc.LocalRpcException localRpcException) when (localRpcException.ErrorCode == LspErrorCodes.ContentModified)
                {
                    // Content modified exceptions are expected and should not be reported as NFWs.
                }
                catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex, ErrorSeverity.Critical))
                {
                    // Swallow the exception so it does not bubble up into the queue.
                }
            }
        }
 
        protected internal override void BeforeRequest<TRequest>(TRequest request)
        {
            // Update the locale for this request to the desired LSP locale.
            CultureInfo.CurrentUICulture = GetCultureForRequest();
        }
 
        /// <summary>
        /// Serial access is guaranteed by the queue.
        /// </summary>
        private CultureInfo GetCultureForRequest()
        {
            if (_cultureInfo != null)
            {
                return _cultureInfo;
            }
 
            var initializeParams = _initializeManager.TryGetInitializeParams();
            if (initializeParams == null)
            {
                // Initialize has not been called yet, no culture to set.
                // Don't update the _cultureInfo since we don't know what it should be.
                return CultureInfo.CurrentUICulture;
            }
 
            var locale = initializeParams.Locale;
            if (string.IsNullOrWhiteSpace(locale))
            {
                // The client did not provide a culture, use the OS configured value
                // and remember that so we can short-circuit from now on.
                _cultureInfo = CultureInfo.CurrentUICulture;
                return _cultureInfo;
            }
 
            try
            {
                // Parse the LSP locale into a culture and remember it for future requests.
                _cultureInfo = CultureInfo.CreateSpecificCulture(locale);
                return _cultureInfo;
            }
            catch (CultureNotFoundException)
            {
                // We couldn't parse the culture, log a warning and fallback to the OS configured value.
                // Also remember the fallback so we don't warn on every request.
                _logger.LogWarning($"Culture {locale} was not found, falling back to OS culture");
                _cultureInfo = CultureInfo.CurrentUICulture;
                return _cultureInfo;
            }
        }
    }
}