Add support for intermittent filesystems to the analysis server, this PR is a synced version of Devons PR https://dart-review.googlesource.com/c/sdk/+/72980, without the DAS protocol change.

Change-Id: I6ad1d423c2616ae31fc3f5954782d05e3d8adbda
Reviewed-on: https://dart-review.googlesource.com/73689
Commit-Queue: Jaime Wren <jwren@google.com>
Reviewed-by: Jaime Wren <jwren@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Devon Carew <devoncarew@google.com>
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 9b26c7f..2069432 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -38,6 +38,7 @@
 import 'package:analysis_server/src/plugin/plugin_watcher.dart';
 import 'package:analysis_server/src/protocol_server.dart' as server;
 import 'package:analysis_server/src/search/search_domain.dart';
+import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
 import 'package:analysis_server/src/server/diagnostic_server.dart';
 import 'package:analysis_server/src/services/correction/namespace.dart';
 import 'package:analysis_server/src/services/search/element_visitors.dart';
@@ -338,6 +339,8 @@
    */
   DiagnosticServer diagnosticServer;
 
+  final DetachableFileSystemManager detachableFileSystemManager;
+
   /**
    * Initialize a newly created server to receive requests from and send
    * responses to the given [channel].
@@ -347,12 +350,17 @@
    * exceptions to show up in unit tests, but it should be set to false when
    * running a full analysis server.
    */
-  AnalysisServer(this.channel, this.resourceProvider, this.options,
-      this.sdkManager, this.instrumentationService,
-      {this.diagnosticServer,
-      ResolverProvider fileResolverProvider: null,
-      ResolverProvider packageResolverProvider: null})
-      : notificationManager =
+  AnalysisServer(
+    this.channel,
+    this.resourceProvider,
+    this.options,
+    this.sdkManager,
+    this.instrumentationService, {
+    this.diagnosticServer,
+    ResolverProvider fileResolverProvider: null,
+    ResolverProvider packageResolverProvider: null,
+    this.detachableFileSystemManager: null,
+  }) : notificationManager =
             new NotificationManager(channel, resourceProvider) {
     _performance = performanceDuringStartup;
 
@@ -1006,7 +1014,7 @@
     return contextManager.isInAnalysisRoot(file);
   }
 
-  Future<void> shutdown() async {
+  Future<void> shutdown() {
     running = false;
 
     if (options.analytics != null) {
@@ -1017,12 +1025,18 @@
       });
     }
 
+    if (options.enableUXExperiment2) {
+      detachableFileSystemManager?.dispose();
+    }
+
     // Defer closing the channel and shutting down the instrumentation server so
     // that the shutdown response can be sent and logged.
     new Future(() {
       instrumentationService.shutdown();
       channel.close();
     });
+
+    return new Future.value();
   }
 
   /**
@@ -1147,7 +1161,7 @@
     return flutterServices[service]?.contains(file) ?? false;
   }
 
-  _scheduleAnalysisImplementedNotification() async {
+  Future<void> _scheduleAnalysisImplementedNotification() async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
     Set<String> files = analysisServices[AnalysisService.IMPLEMENTED];
@@ -1197,6 +1211,12 @@
    * the priority files' containing directory.
    */
   bool enableUXExperiment1 = false;
+
+  /**
+   * User Experience, Experiment #2. This experiment introduces the notion of an
+   * intermittent file system.
+   */
+  bool enableUXExperiment2 = false;
 }
 
 /**
diff --git a/pkg/analysis_server/lib/src/domain_analysis.dart b/pkg/analysis_server/lib/src/domain_analysis.dart
index 79c4663..8cfedbb 100644
--- a/pkg/analysis_server/lib/src/domain_analysis.dart
+++ b/pkg/analysis_server/lib/src/domain_analysis.dart
@@ -384,9 +384,17 @@
         return new Response.invalidFilePathFormat(request, path);
       }
     }
-    // continue in server
-    server.setAnalysisRoots(request.id, includedPathList, excludedPathList,
-        params.packageRoots ?? <String, String>{});
+    Map<String, String> packageRoots =
+        params.packageRoots ?? <String, String>{};
+
+    if (server.options.enableUXExperiment2 &&
+        server.detachableFileSystemManager != null) {
+      server.detachableFileSystemManager.setAnalysisRoots(
+          request.id, includedPathList, excludedPathList, packageRoots);
+    } else {
+      server.setAnalysisRoots(
+          request.id, includedPathList, excludedPathList, packageRoots);
+    }
     return new AnalysisSetAnalysisRootsResult().toResponse(request.id);
   }
 
diff --git a/pkg/analysis_server/lib/src/server/detachable_filesystem_manager.dart b/pkg/analysis_server/lib/src/server/detachable_filesystem_manager.dart
new file mode 100644
index 0000000..dc2cead
--- /dev/null
+++ b/pkg/analysis_server/lib/src/server/detachable_filesystem_manager.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/analysis_server.dart';
+
+/**
+ * A class that can be used to confirm an analysis server instance to better
+ * support intermittent file systems.
+ *
+ * See also [AnalysisServerOptions.detachableFileSystemManager].
+ */
+abstract class DetachableFileSystemManager {
+  /**
+   * Indicate that the [DetachableFileSystemManager] and the containing analysis
+   * server are being shut down.
+   */
+  void dispose();
+
+  /**
+   * Forward on the 'analysis.setAnalysisRoots' request.
+   *
+   * This class can choose to pass through all [setAnalysisRoots] calls to the
+   * underlying analysis server, it can choose to modify the given
+   * [includedPaths] and other parameters, or it could choose to delays calls to
+   * [setAnalysisRoots].
+   */
+  void setAnalysisRoots(String requestId, List<String> includedPaths,
+      List<String> excludedPaths, Map<String, String> packageRoots);
+
+  /**
+   * Called exactly once before any calls to [setAnalysisRoots].
+   */
+  void setAnalysisServer(AnalysisServer server);
+}
diff --git a/pkg/analysis_server/lib/src/server/driver.dart b/pkg/analysis_server/lib/src/server/driver.dart
index 2fbce49..0ee7c49 100644
--- a/pkg/analysis_server/lib/src/server/driver.dart
+++ b/pkg/analysis_server/lib/src/server/driver.dart
@@ -7,6 +7,7 @@
 import 'dart:math';
 
 import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
 import 'package:analysis_server/src/server/dev_server.dart';
 import 'package:analysis_server/src/server/diagnostic_server.dart';
 import 'package:analysis_server/src/server/http_server.dart';
@@ -272,6 +273,12 @@
   static const String UX_EXPERIMENT_1 = "ux-experiment-1";
 
   /**
+   * User Experience, Experiment #2. This experiment introduces the notion of an
+   * intermittent file system.
+   */
+  static const String UX_EXPERIMENT_2 = "ux-experiment-2";
+
+  /**
    * The instrumentation server that is to be used by the analysis server.
    */
   InstrumentationServer instrumentationServer;
@@ -288,6 +295,12 @@
    */
   ResolverProvider packageResolverProvider;
 
+  /***
+   * An optional manager to handle file systems which may not always be
+   * available.
+   */
+  DetachableFileSystemManager detachableFileSystemManager;
+
   SocketServer socketServer;
 
   HttpAnalysisServer httpServer;
@@ -316,6 +329,7 @@
     analysisServerOptions.cacheFolder = results[CACHE_FOLDER];
     analysisServerOptions.useFastaParser = results[USE_FASTA_PARSER];
     analysisServerOptions.enableUXExperiment1 = results[UX_EXPERIMENT_1];
+    analysisServerOptions.enableUXExperiment2 = results[UX_EXPERIMENT_2];
 
     bool disableAnalyticsForSession = results[SUPPRESS_ANALYTICS_FLAG];
     if (results.wasParsed(TRAIN_USING)) {
@@ -439,7 +453,8 @@
         instrumentationService,
         diagnosticServer,
         fileResolverProvider,
-        packageResolverProvider);
+        packageResolverProvider,
+        detachableFileSystemManager);
     httpServer = new HttpAnalysisServer(socketServer);
 
     diagnosticServer.httpServer = httpServer;
@@ -473,6 +488,8 @@
         }
         await instrumentationService.shutdown();
 
+        socketServer.analysisServer.shutdown();
+
         try {
           tempDriverDir.deleteSync(recursive: true);
         } catch (_) {
@@ -487,10 +504,12 @@
         stdioServer.serveStdio().then((_) async {
           // TODO(brianwilkerson) Determine whether this await is necessary.
           await null;
+
           if (serve_http) {
             httpServer.close();
           }
           await instrumentationService.shutdown();
+          socketServer.analysisServer.shutdown();
           exit(0);
         });
       },
@@ -601,6 +620,11 @@
             "this experiment changes the notion of analysis roots and priority "
             "files.",
         hide: true);
+    parser.addFlag(UX_EXPERIMENT_2,
+        help: "User Experience, Experiment #2, "
+            "this experiment introduces the notion of an intermittent file "
+            "system.",
+        hide: true);
 
     return parser;
   }
diff --git a/pkg/analysis_server/lib/src/socket_server.dart b/pkg/analysis_server/lib/src/socket_server.dart
index 250c072..4fd78db 100644
--- a/pkg/analysis_server/lib/src/socket_server.dart
+++ b/pkg/analysis_server/lib/src/socket_server.dart
@@ -6,6 +6,7 @@
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analysis_server/src/channel/channel.dart';
+import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
 import 'package:analysis_server/src/server/diagnostic_server.dart';
 import 'package:analyzer/file_system/physical_file_system.dart';
 import 'package:analyzer/instrumentation/instrumentation.dart';
@@ -31,6 +32,7 @@
   final DiagnosticServer diagnosticServer;
   final ResolverProvider fileResolverProvider;
   final ResolverProvider packageResolverProvider;
+  final DetachableFileSystemManager detachableFileSystemManager;
 
   /**
    * The analysis server that was created when a client established a
@@ -45,7 +47,8 @@
       this.instrumentationService,
       this.diagnosticServer,
       this.fileResolverProvider,
-      this.packageResolverProvider);
+      this.packageResolverProvider,
+      this.detachableFileSystemManager);
 
   /**
    * Create an analysis server which will communicate with the client using the
@@ -75,10 +78,17 @@
           'File read mode was set to the unknown mode: $analysisServerOptions.fileReadMode');
     }
 
-    analysisServer = new AnalysisServer(serverChannel, resourceProvider,
-        analysisServerOptions, sdkManager, instrumentationService,
-        diagnosticServer: diagnosticServer,
-        fileResolverProvider: fileResolverProvider,
-        packageResolverProvider: packageResolverProvider);
+    analysisServer = new AnalysisServer(
+      serverChannel,
+      resourceProvider,
+      analysisServerOptions,
+      sdkManager,
+      instrumentationService,
+      diagnosticServer: diagnosticServer,
+      fileResolverProvider: fileResolverProvider,
+      packageResolverProvider: packageResolverProvider,
+      detachableFileSystemManager: detachableFileSystemManager,
+    );
+    detachableFileSystemManager?.setAnalysisServer(analysisServer);
   }
 }
diff --git a/pkg/analysis_server/lib/src/status/diagnostics.dart b/pkg/analysis_server/lib/src/status/diagnostics.dart
index 7fc00fd..4628c6f 100644
--- a/pkg/analysis_server/lib/src/status/diagnostics.dart
+++ b/pkg/analysis_server/lib/src/status/diagnostics.dart
@@ -1307,6 +1307,14 @@
         diagnosticsSite.socketServer.analysisServerOptions.useFastaParser));
     buf.writeln(writeOption('Instrumentation enabled',
         AnalysisEngine.instance.instrumentationService.isActive));
+    bool uxExp1 =
+        diagnosticsSite.socketServer.analysisServerOptions.enableUXExperiment1;
+    bool uxExp2 =
+        diagnosticsSite.socketServer.analysisServerOptions.enableUXExperiment2;
+    if (uxExp1 || uxExp2) {
+      buf.writeln(writeOption('UX Experiment 1', uxExp1));
+      buf.writeln(writeOption('ux Experiment 2', uxExp2));
+    }
     buf.writeln(writeOption('Server process ID', pid));
     buf.writeln('</div>');
 
diff --git a/pkg/analysis_server/lib/starter.dart b/pkg/analysis_server/lib/starter.dart
index 5059a59..aa4a30e 100644
--- a/pkg/analysis_server/lib/starter.dart
+++ b/pkg/analysis_server/lib/starter.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
 import 'package:analysis_server/src/server/driver.dart';
 import 'package:analyzer/instrumentation/instrumentation.dart';
 import 'package:analyzer/src/plugin/resolver_provider.dart';
@@ -19,6 +20,12 @@
    */
   factory ServerStarter() = Driver;
 
+  /***
+   * An optional manager to handle file systems which may not always be
+   * available.
+   */
+  void set detachableFileSystemManager(DetachableFileSystemManager manager);
+
   /**
    * Set the file resolver provider used to override the way file URI's
    * are resolved in some contexts. The provider should return `null` if the
diff --git a/pkg/analysis_server/test/socket_server_test.dart b/pkg/analysis_server/test/socket_server_test.dart
index 931d87e7..d6957be 100644
--- a/pkg/analysis_server/test/socket_server_test.dart
+++ b/pkg/analysis_server/test/socket_server_test.dart
@@ -117,6 +117,7 @@
         InstrumentationService.NULL_SERVICE,
         null,
         null,
+        null,
         null);
   }
 }