Repository: dotnet/roslyn
File: src/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/RequestExecutionQueue.cs (C#)

Code before:
                    }

                    // Restore our activity id so that logging/tracking works across asynchronous calls.
                    Trace.CorrelationManager.ActivityId = activityId;

                    using var loggerScope = _logger.CreateContext(work.MethodName);

                    // Serially in the queue determine which language is appropriate for handling the request (based on the request URI).
                    //
                    // The client can send us the language associated with a URI in the didOpen notification.  It is important that all prior didOpen
                    // notifications have been completed by the time we attempt to determine the language, so we have the up to date map of URI to language.
                    // Since didOpen notifications are marked as mutating, the queue will not advance to the next request until the server has finished processing
                    // the didOpen, ensuring that this line will only run once all prior didOpens have completed.
                    var didGetLanguage = _languageServer.TryGetLanguageForRequest(work.MethodName, work.SerializedRequest, out var language);

                    using var languageScope = _logger.CreateLanguageContext(language);

                    // Now that we know the actual language, we can deserialize the request and start creating the request context.
                    var (metadata, handler, methodInfo) = GetHandlerForRequest(work, language ?? LanguageServerConstants.DefaultLanguageName);

                    // We had an issue determining the language.  Generally this is very rare and only occurs
                    // when a client sends us requests for files where we haven't saved the languageId.
                    // We should only crash if this was a mutating method.
                    //
                    // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didClose
                    // > Note that a server’s ability to fulfill requests is independent of whether a text document is open or closed.
                    if (!didGetLanguage && handler.MutatesSolutionState)
                    {
                        throw new InvalidOperationException($"Failed to get language for {work.MethodName}");
                    }

                    // We now have the actual handler and language, so we can process the work item using the concrete types defined by the metadata.
                    await InvokeProcessCoreAsync(work, metadata, handler, methodInfo, concurrentlyExecutingTasks, currentWorkCts, cancellationToken).ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    // Explicitly ignore this exception as cancellation occurred as a result of our linked cancellation token.
                    // This means either the queue is shutting down or the request itself was cancelled.
                    //   1.  If the queue is shutting down, then while loop will exit before the next iteration since it checks for cancellation.
                    //   2.  Request cancellations are normal so no need to report anything there.
                }
            }
        }
        catch (Exception ex)
        {
            // We encountered an unexpected exception in processing the queue or in a mutating request.
            // Log it, shutdown the queue, and exit the loop.
            _logger.LogException(ex);
            var message = $"Error occurred processing queue: {ex.Message}.";
            if (lspServices is not null)
            {

Review comment (bug):
This change basically means that we will use the default handler for non-mutating requests where we can't determine a language, which I think generally doesn't give much/any info.

When I stepped through the new test, I found it sent a request to `PublicDocumentPullDiagnosticsHandler`, which ended up having no sources for diagnostics, so it finally yielded no diagnostics to the client.

While it's possible that if we loaded the document as csharp instead, we would end up getting some meaningful diagnostics, I think our actual answer to this request isn't important, what matters is that we just don't fail, don't show a spurious error popup, and allow things to keep going.

If the client didn't give us the necessary information to see this document as csharp, for example, then it's unlikely that they need csharp-specific info. It's possible that even asking for diagnostics at all in this "closing file" scenario is just a "quirk"/misfire in the client, but, the spec does appear to allow it, so we might as well handle it as gracefully as possible.

IIRC, the previous behavior, where we probably were calling `work.FailRequest`, not only would show an unwanted error popup, but seemed to bring down the LS anyway. Editor features would entirely stop working even in the "new" .cs document, which was unrelated to the untitled document associated with the failure.

Code after:
                    }

                    // Restore our activity id so that logging/tracking works across asynchronous calls.
                    Trace.CorrelationManager.ActivityId = activityId;

                    using var loggerScope = _logger.CreateContext(work.MethodName);

                    // Serially in the queue determine which language is appropriate for handling the request (based on the request URI).
                    //
                    // The client can send us the language associated with a URI in the didOpen notification.  It is important that all prior didOpen
                    // notifications have been completed by the time we attempt to determine the language, so we have the up to date map of URI to language.
                    // Since didOpen notifications are marked as mutating, the queue will not advance to the next request until the server has finished processing
                    // the didOpen, ensuring that this line will only run once all prior didOpens have completed.
                    var didGetLanguage = _languageServer.TryGetLanguageForRequest(work.MethodName, work.SerializedRequest, out var language);

                    using var languageScope = _logger.CreateLanguageContext(language);

                    // Now that we know the actual language, we can deserialize the request and start creating the request context.
                    var (metadata, handler, methodInfo) = GetHandlerForRequest(work, language ?? LanguageServerConstants.DefaultLanguageName);

                    // We had an issue determining the language.  Generally this is very rare and only occurs
                    // when a client sends us requests for files where we haven't saved the languageId.
                    // We should only crash if this was a mutating method.
                    //
                    // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didClose
                    // > Note that a server’s ability to fulfill requests is independent of whether a text document is open or closed.
                    if (!didGetLanguage)
                    {
                        var message = $"Failed to get language for {work.MethodName}";
                        if (handler.MutatesSolutionState)
                        {
                            throw new InvalidOperationException(message);
                        }
                        else
                        {
                            work.FailRequest(message);
                            continue;
                        }
                    }

                    // We now have the actual handler and language, so we can process the work item using the concrete types defined by the metadata.
                    await InvokeProcessCoreAsync(work, metadata, handler, methodInfo, concurrentlyExecutingTasks, currentWorkCts, cancellationToken).ConfigureAwait(false);
                }
                catch (OperationCanceledException)
                {
                    // Explicitly ignore this exception as cancellation occurred as a result of our linked cancellation token.
                    // This means either the queue is shutting down or the request itself was cancelled.
                    //   1.  If the queue is shutting down, then while loop will exit before the next iteration since it checks for cancellation.
                    //   2.  Request cancellations are normal so no need to report anything there.
                }
            }