[analysis_server] Allow allowOverlappingHandlers to be controlled via initializationOptions

For troubleshooting, this allows the client to override this flag.

I'd originally planned to use ClientConfiguration for this and allow it to be changed on-the-fly, however this can result in a mix of requests and therefore I decided it best to only support setting once during startup (which is part of initialization, and therefore before general requests start being sent).

This will require some Dart-Code work to provide a value here (and without a value, it will always be the servers default).

See https://github.com/dart-lang/sdk/issues/60440

Change-Id: Ie9843543d6d491afb046f3d1106211b7db852605
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419541
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
index 94e1951..1a636d8 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
@@ -422,11 +422,14 @@
   void handleClientConnection(
     ClientCapabilities capabilities,
     ClientInfo? clientInfo,
-    Object? initializationOptions,
+    Object? rawInitializationOptions,
   ) {
     _clientCapabilities = LspClientCapabilities(capabilities);
     _clientInfo = clientInfo;
-    _initializationOptions = LspInitializationOptions(initializationOptions);
+    var initializationOptions =
+        _initializationOptions = LspInitializationOptions(
+          rawInitializationOptions,
+        );
 
     /// Enable virtual file support.
     var supportsVirtualFiles =
@@ -437,6 +440,16 @@
       uriConverter = ClientUriConverter.withVirtualFileSupport(pathContext);
     }
 
+    // Set whether to allow interleaved requests.
+    if (initializationOptions.allowOverlappingHandlers
+        case var allowOverlappingHandlers?) {
+      MessageScheduler.allowOverlappingHandlers = allowOverlappingHandlers;
+      instrumentationService.logInfo(
+        'MessageScheduler.allowOverlappingHandlers set to '
+        '$allowOverlappingHandlers by LSP client initializationOptions',
+      );
+    }
+
     performanceAfterStartup = ServerPerformance();
     performance = performanceAfterStartup!;
 
@@ -1278,6 +1291,12 @@
   final int? completionBudgetMilliseconds;
   final bool allowOpenUri;
 
+  /// Whether the client has expressed an explicit preference for
+  /// overlapping message handlers.
+  ///
+  /// If `null`, the server default will be used.
+  final bool? allowOverlappingHandlers;
+
   /// A temporary flag passed by Dart-Code to enable using in-editor fixes for
   /// the "dart fix" prompt.
   ///
@@ -1307,6 +1326,7 @@
       completionBudgetMilliseconds =
           options['completionBudgetMilliseconds'] as int?,
       allowOpenUri = options['allowOpenUri'] == true,
+      allowOverlappingHandlers = options['allowOverlappingHandlers'] as bool?,
       useInEditorDartFixPrompt = options['useInEditorDartFixPrompt'] == true;
 }
 
diff --git a/pkg/analysis_server/test/lsp/initialization_test.dart b/pkg/analysis_server/test/lsp/initialization_test.dart
index 5d2b72f..91bce5b 100644
--- a/pkg/analysis_server/test/lsp/initialization_test.dart
+++ b/pkg/analysis_server/test/lsp/initialization_test.dart
@@ -9,6 +9,7 @@
 import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/lsp/server_capabilities_computer.dart';
 import 'package:analysis_server/src/plugin/plugin_manager.dart';
+import 'package:analysis_server/src/server/message_scheduler.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:language_server_protocol/json_parsing.dart';
@@ -27,6 +28,11 @@
 
 @reflectiveTest
 class InitializationTest extends AbstractLspAnalysisServerTest {
+  /// The default value of [MessageScheduler.allowOverlappingHandlers] before
+  /// and test sets it (so that [tearDown] can revert it).
+  final allowOverlappingHandlersDefault =
+      MessageScheduler.allowOverlappingHandlers;
+
   /// Waits for any in-progress analysis context rebuild.
   ///
   /// Pumps the event queue before and after, to ensure any server code that
@@ -93,6 +99,12 @@
     );
   }
 
+  @override
+  Future<void> tearDown() async {
+    await super.tearDown();
+    MessageScheduler.allowOverlappingHandlers = allowOverlappingHandlersDefault;
+  }
+
   Future<void> test_blazeWorkspace() async {
     var workspacePath = '/home/user/ws';
     // Make it a Blaze workspace.
@@ -1051,6 +1063,30 @@
     expect(server.contextManager.includedPaths, equals([projectFolderPath]));
   }
 
+  Future<void> test_interleavedRequests_default() async {
+    await initialize(rootUri: projectFolderUri, initializationOptions: {});
+    expect(
+      MessageScheduler.allowOverlappingHandlers,
+      allowOverlappingHandlersDefault,
+    );
+  }
+
+  Future<void> test_interleavedRequests_explicitFalse() async {
+    await initialize(
+      rootUri: projectFolderUri,
+      initializationOptions: {'allowOverlappingHandlers': false},
+    );
+    expect(MessageScheduler.allowOverlappingHandlers, isFalse);
+  }
+
+  Future<void> test_interleavedRequests_explicitTrue() async {
+    await initialize(
+      rootUri: projectFolderUri,
+      initializationOptions: {'allowOverlappingHandlers': true},
+    );
+    expect(MessageScheduler.allowOverlappingHandlers, isTrue);
+  }
+
   Future<void> test_invalidExperimental_commands() async {
     await expectInvalidExperimentalParams({
       'commands': 1,