[metrics_center] await future in metrics_center (#1202)

diff --git a/.cirrus.yml b/.cirrus.yml
index f67d967..f0787fe 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -55,7 +55,7 @@
       always:
         format_script: ./script/tool_runner.sh format --fail-on-change
         license_script: dart pub global run flutter_plugin_tools license-check
-        analyze_script: ./script/tool_runner.sh analyze --custom-analysis=web_benchmarks/testing/test_app,flutter_lints/example,rfw/example
+        analyze_script: ./script/tool_runner.sh analyze --custom-analysis=web_benchmarks/testing/test_app,flutter_lints/example,rfw/example,metrics_center
         pubspec_script: ./script/tool_runner.sh pubspec-check
     - name: publishable
       env:
diff --git a/packages/metrics_center/CHANGELOG.md b/packages/metrics_center/CHANGELOG.md
index 3a7222a..d4727ad 100644
--- a/packages/metrics_center/CHANGELOG.md
+++ b/packages/metrics_center/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.0.4
+
+- Fix un-await-ed Future in `SkiaPerfDestination.update`.
+
 ## 1.0.3
 
 - Filter out host_name, load_avg and caches keys from context
diff --git a/packages/metrics_center/analysis_options.yaml b/packages/metrics_center/analysis_options.yaml
new file mode 100644
index 0000000..1abfc01
--- /dev/null
+++ b/packages/metrics_center/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: ../../analysis_options.yaml
+
+linter:
+  rules:
+    unawaited_futures: true
diff --git a/packages/metrics_center/lib/src/skiaperf.dart b/packages/metrics_center/lib/src/skiaperf.dart
index eb9cbe8..01f5ed9 100644
--- a/packages/metrics_center/lib/src/skiaperf.dart
+++ b/packages/metrics_center/lib/src/skiaperf.dart
@@ -407,6 +407,9 @@
       }
     }
 
+    // All created locks must be released before returning
+    final List<Future<void>> lockFutures = <Future<void>>[];
+
     // 2nd, read existing points from the gcs object and update with new ones.
     for (final String repo in pointMap.keys) {
       for (final String? revision in pointMap[repo]!.keys) {
@@ -418,18 +421,21 @@
         // json files according to task names. Skia perf read all json files in
         // the directory so one can use arbitrary names for those sharded json
         // file names.
-        _lock!.protectedRun('$objectName.lock', () async {
-          final List<SkiaPerfPoint> oldPoints =
-              await _gcs.readPoints(objectName);
-          for (final SkiaPerfPoint p in oldPoints) {
-            if (newPoints![p.id] == null) {
-              newPoints[p.id] = p;
+        lockFutures.add(
+          _lock!.protectedRun('$objectName.lock', () async {
+            final List<SkiaPerfPoint> oldPoints =
+                await _gcs.readPoints(objectName);
+            for (final SkiaPerfPoint p in oldPoints) {
+              if (newPoints![p.id] == null) {
+                newPoints[p.id] = p;
+              }
             }
-          }
-          await _gcs.writePoints(objectName, newPoints!.values.toList());
-        });
+            await _gcs.writePoints(objectName, newPoints!.values.toList());
+          }),
+        );
       }
     }
+    await Future.wait(lockFutures);
   }
 
   final SkiaPerfGcsAdaptor _gcs;
diff --git a/packages/metrics_center/pubspec.yaml b/packages/metrics_center/pubspec.yaml
index 6f1ce81..b95ab33 100644
--- a/packages/metrics_center/pubspec.yaml
+++ b/packages/metrics_center/pubspec.yaml
@@ -1,5 +1,5 @@
 name: metrics_center
-version: 1.0.3
+version: 1.0.4
 description:
   Support multiple performance metrics sources/formats and destinations.
 repository: https://github.com/flutter/packages/tree/main/packages/metrics_center
diff --git a/packages/metrics_center/test/flutter_test.dart b/packages/metrics_center/test/flutter_test.dart
index be13bd0..05dee1b 100644
--- a/packages/metrics_center/test/flutter_test.dart
+++ b/packages/metrics_center/test/flutter_test.dart
@@ -44,7 +44,7 @@
     final FlutterDestination dst =
         await FlutterDestination.makeFromCredentialsJson(credentialsJson!,
             isTesting: true);
-    dst.update(<FlutterEngineMetricPoint>[simplePoint],
+    await dst.update(<FlutterEngineMetricPoint>[simplePoint],
         DateTime.fromMillisecondsSinceEpoch(123), 'test');
   }, skip: credentialsJson == null);
 }
diff --git a/packages/metrics_center/test/skiaperf_test.dart b/packages/metrics_center/test/skiaperf_test.dart
index de1a842..c8fbcd8 100644
--- a/packages/metrics_center/test/skiaperf_test.dart
+++ b/packages/metrics_center/test/skiaperf_test.dart
@@ -4,6 +4,7 @@
 
 @Timeout(Duration(seconds: 3600))
 
+import 'dart:async';
 import 'dart:convert';
 
 import 'package:gcloud/storage.dart';
@@ -27,6 +28,12 @@
 }
 
 class MockSkiaPerfGcsAdaptor implements SkiaPerfGcsAdaptor {
+  MockSkiaPerfGcsAdaptor({
+    this.writePointsOverride,
+  });
+
+  final Future<void> Function()? writePointsOverride;
+
   @override
   Future<List<SkiaPerfPoint>> readPoints(String objectName) async {
     return _storage[objectName] ?? <SkiaPerfPoint>[];
@@ -35,6 +42,9 @@
   @override
   Future<void> writePoints(
       String objectName, List<SkiaPerfPoint> points) async {
+    if (writePointsOverride != null) {
+      return writePointsOverride!();
+    }
     _storage[objectName] = points.toList();
   }
 
@@ -545,6 +555,33 @@
     skip: testBucket == null,
   );
 
+  test('SkiaPerfDestination.update awaits locks', () async {
+    bool updateCompleted = false;
+    final Completer<void> callbackCompleter = Completer<void>();
+    final SkiaPerfGcsAdaptor mockGcs = MockSkiaPerfGcsAdaptor(
+      writePointsOverride: () => callbackCompleter.future,
+    );
+    final GcsLock mockLock = MockGcsLock();
+    final SkiaPerfDestination dst = SkiaPerfDestination(mockGcs, mockLock);
+    final Future<void> updateFuture = dst.update(
+      <MetricPoint>[cocoonPointRev1Metric1],
+      DateTime.fromMillisecondsSinceEpoch(123),
+      'test',
+    );
+    final Future<void> markedUpdateCompleted = updateFuture.then<void>((_) {
+      updateCompleted = true;
+    });
+
+    // spin event loop to make sure function hasn't done anything yet
+    await (Completer<void>()..complete()).future;
+
+    // Ensure that the .update() method is waiting for callbackCompleter
+    expect(updateCompleted, false);
+    callbackCompleter.complete();
+    await markedUpdateCompleted;
+    expect(updateCompleted, true);
+  });
+
   test('SkiaPerfDestination correctly updates points', () async {
     final SkiaPerfGcsAdaptor mockGcs = MockSkiaPerfGcsAdaptor();
     final GcsLock mockLock = MockGcsLock();