Made platform message responses threadsafe for macos. (#37607)

diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
index 47f9ca3..19ed29a 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
+++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
@@ -74,6 +74,13 @@
 @interface FlutterEngine () <FlutterBinaryMessenger>
 
 /**
+ * A mutable array that holds one bool value that determines if responses to platform messages are
+ * clear to execute. This value should be read or written only inside of a synchronized block and
+ * will return `NO` after the FlutterEngine has been dealloc'd.
+ */
+@property(nonatomic, strong) NSMutableArray<NSNumber*>* isResponseValid;
+
+/**
  * Sends the list of user-preferred locales to the Flutter engine.
  */
 - (void)sendUserLocales;
@@ -242,6 +249,8 @@
   _allowHeadlessExecution = allowHeadlessExecution;
   _semanticsEnabled = NO;
   _viewProvider = [[FlutterViewEngineProvider alloc] initWithEngine:self];
+  _isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
+  [_isResponseValid addObject:@YES];
 
   _embedderAPI.struct_size = sizeof(FlutterEngineProcTable);
   FlutterEngineGetProcAddresses(&_embedderAPI);
@@ -262,6 +271,10 @@
 }
 
 - (void)dealloc {
+  @synchronized(_isResponseValid) {
+    [_isResponseValid removeAllObjects];
+    [_isResponseValid addObject:@NO];
+  }
   [self shutDownEngine];
   if (_aotData) {
     _embedderAPI.CollectAOTData(_aotData);
@@ -639,17 +652,25 @@
   }
   NSString* channel = @(message->channel);
   __block const FlutterPlatformMessageResponseHandle* responseHandle = message->response_handle;
-
+  __block FlutterEngine* weakSelf = self;
+  NSMutableArray* isResponseValid = self.isResponseValid;
+  FlutterEngineSendPlatformMessageResponseFnPtr sendPlatformMessageResponse =
+      _embedderAPI.SendPlatformMessageResponse;
   FlutterBinaryReply binaryResponseHandler = ^(NSData* response) {
-    if (responseHandle) {
-      _embedderAPI.SendPlatformMessageResponse(self->_engine, responseHandle,
-                                               static_cast<const uint8_t*>(response.bytes),
-                                               response.length);
-      responseHandle = NULL;
-    } else {
-      NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response "
-             "on channel '%@'.",
-            channel);
+    @synchronized(isResponseValid) {
+      if (![isResponseValid[0] boolValue]) {
+        // Ignore, engine was killed.
+        return;
+      }
+      if (responseHandle) {
+        sendPlatformMessageResponse(weakSelf->_engine, responseHandle,
+                                    static_cast<const uint8_t*>(response.bytes), response.length);
+        responseHandle = NULL;
+      } else {
+        NSLog(@"Error: Message responses can be sent only once. Ignoring duplicate response "
+               "on channel '%@'.",
+              channel);
+      }
     }
   };
 
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
index e6a96d9..143b05b 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
+++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
@@ -614,6 +614,48 @@
   EXPECT_TRUE(value);
 }
 
+TEST_F(FlutterEngineTest, ResponseAfterEngineDied) {
+  FlutterEngine* engine = GetFlutterEngine();
+  FlutterBasicMessageChannel* channel = [[FlutterBasicMessageChannel alloc]
+         initWithName:@"foo"
+      binaryMessenger:engine.binaryMessenger
+                codec:[FlutterStandardMessageCodec sharedInstance]];
+  __block BOOL didCallCallback = NO;
+  [channel setMessageHandler:^(id message, FlutterReply callback) {
+    ShutDownEngine();
+    callback(nil);
+    didCallCallback = YES;
+  }];
+  EXPECT_TRUE([engine runWithEntrypoint:@"sendFooMessage"]);
+  engine = nil;
+
+  while (!didCallCallback) {
+    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+  }
+}
+
+TEST_F(FlutterEngineTest, ResponseFromBackgroundThread) {
+  FlutterEngine* engine = GetFlutterEngine();
+  FlutterBasicMessageChannel* channel = [[FlutterBasicMessageChannel alloc]
+         initWithName:@"foo"
+      binaryMessenger:engine.binaryMessenger
+                codec:[FlutterStandardMessageCodec sharedInstance]];
+  __block BOOL didCallCallback = NO;
+  [channel setMessageHandler:^(id message, FlutterReply callback) {
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+      callback(nil);
+      dispatch_async(dispatch_get_main_queue(), ^{
+        didCallCallback = YES;
+      });
+    });
+  }];
+  EXPECT_TRUE([engine runWithEntrypoint:@"sendFooMessage"]);
+
+  while (!didCallCallback) {
+    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+  }
+}
+
 }  // namespace flutter::testing
 
 // NOLINTEND(clang-analyzer-core.StackAddressEscape)
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h b/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h
index c2396bc..b52524c 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h
+++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h
@@ -23,6 +23,8 @@
 
   static void IsolateCreateCallback(void* user_data);
 
+  void ShutDownEngine();
+
  private:
   inline static std::shared_ptr<TestDartNativeResolver> native_resolver_;
 
diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm
index 8bc5727..8a535aa 100644
--- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm
+++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.mm
@@ -31,6 +31,11 @@
   native_resolver_.reset();
 }
 
+void FlutterEngineTest::ShutDownEngine() {
+  [engine_ shutDownEngine];
+  engine_ = nil;
+}
+
 void FlutterEngineTest::IsolateCreateCallback(void* user_data) {
   native_resolver_->SetNativeResolverForIsolate();
 }
diff --git a/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart b/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart
index afdc327..835588f 100644
--- a/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart
+++ b/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:io';
+import 'dart:typed_data';
 import 'dart:ui';
 
 @pragma('vm:external-name', 'SignalNativeTest')
@@ -63,3 +64,8 @@
   PlatformDispatcher.instance.views.first.render(SceneBuilder().build());
   signalNativeTest(); // should look black
 }
+
+@pragma('vm:entry-point')
+void sendFooMessage() {
+  PlatformDispatcher.instance.sendPlatformMessage('foo', null, (ByteData? result) {});
+}