[Linux] fix: make textures thread-safe on linux (#40478)

Make textures GHashTable thread-safe
diff --git a/shell/platform/linux/fl_texture_registrar.cc b/shell/platform/linux/fl_texture_registrar.cc
old mode 100644
new mode 100755
index 0851d22..d22e24c
--- a/shell/platform/linux/fl_texture_registrar.cc
+++ b/shell/platform/linux/fl_texture_registrar.cc
@@ -34,6 +34,9 @@
   // plugins.  The keys are directly stored int64s. The values are stored
   // pointer to #FlTexture.  This table is freed by the responder.
   GHashTable* textures;
+
+  // The mutex guard to make `textures` thread-safe.
+  GMutex textures_mutex;
 };
 
 static void fl_texture_registrar_impl_iface_init(
@@ -57,21 +60,26 @@
   self->engine = nullptr;
 
   // Unregister any textures.
+  g_mutex_lock(&self->textures_mutex);
   g_autoptr(GHashTable) textures = self->textures;
   self->textures = g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr,
                                          g_object_unref);
   g_hash_table_remove_all(textures);
+  g_mutex_unlock(&self->textures_mutex);
 }
 
 static void fl_texture_registrar_impl_dispose(GObject* object) {
   FlTextureRegistrarImpl* self = FL_TEXTURE_REGISTRAR_IMPL(object);
 
+  g_mutex_lock(&self->textures_mutex);
   g_clear_pointer(&self->textures, g_hash_table_unref);
+  g_mutex_unlock(&self->textures_mutex);
 
   if (self->engine != nullptr) {
     g_object_weak_unref(G_OBJECT(self->engine), engine_weak_notify_cb, self);
     self->engine = nullptr;
   }
+  g_mutex_clear(&self->textures_mutex);
 
   G_OBJECT_CLASS(fl_texture_registrar_impl_parent_class)->dispose(object);
 }
@@ -93,8 +101,10 @@
     int64_t id = self->next_id++;
     if (fl_engine_register_external_texture(self->engine, id)) {
       fl_texture_set_id(texture, id);
+      g_mutex_lock(&self->textures_mutex);
       g_hash_table_insert(self->textures, GINT_TO_POINTER(id),
                           g_object_ref(texture));
+      g_mutex_unlock(&self->textures_mutex);
       return TRUE;
     } else {
       return FALSE;
@@ -108,8 +118,11 @@
 static FlTexture* lookup_texture(FlTextureRegistrar* registrar,
                                  int64_t texture_id) {
   FlTextureRegistrarImpl* self = FL_TEXTURE_REGISTRAR_IMPL(registrar);
-  return reinterpret_cast<FlTexture*>(
+  g_mutex_lock(&self->textures_mutex);
+  FlTexture* texture = reinterpret_cast<FlTexture*>(
       g_hash_table_lookup(self->textures, GINT_TO_POINTER(texture_id)));
+  g_mutex_unlock(&self->textures_mutex);
+  return texture;
 }
 
 static gboolean mark_texture_frame_available(FlTextureRegistrar* registrar,
@@ -135,10 +148,12 @@
   gboolean result = fl_engine_unregister_external_texture(
       self->engine, fl_texture_get_id(texture));
 
+  g_mutex_lock(&self->textures_mutex);
   if (!g_hash_table_remove(self->textures,
                            GINT_TO_POINTER(fl_texture_get_id(texture)))) {
     g_warning("Unregistering a non-existent texture %p", texture);
   }
+  g_mutex_unlock(&self->textures_mutex);
 
   return result;
 }
@@ -155,6 +170,8 @@
   self->next_id = 1;
   self->textures = g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr,
                                          g_object_unref);
+  // Initialize the mutex for textures.
+  g_mutex_init(&self->textures_mutex);
 }
 
 G_MODULE_EXPORT gboolean
diff --git a/shell/platform/linux/fl_texture_registrar_test.cc b/shell/platform/linux/fl_texture_registrar_test.cc
index 17bac59..7f9fdb4 100644
--- a/shell/platform/linux/fl_texture_registrar_test.cc
+++ b/shell/platform/linux/fl_texture_registrar_test.cc
@@ -13,11 +13,13 @@
 #include <epoxy/gl.h>
 
 #include <gmodule.h>
+#include <pthread.h>
 
 static constexpr uint32_t kBufferWidth = 4u;
 static constexpr uint32_t kBufferHeight = 4u;
 static constexpr uint32_t kRealBufferWidth = 2u;
 static constexpr uint32_t kRealBufferHeight = 2u;
+static constexpr uint64_t kThreadCount = 16u;
 
 G_DECLARE_FINAL_TYPE(FlTestRegistrarTexture,
                      fl_test_registrar_texture,
@@ -64,6 +66,14 @@
       g_object_new(fl_test_registrar_texture_get_type(), nullptr));
 }
 
+static void* add_mock_texture_to_registrar(void* pointer) {
+  g_return_val_if_fail(FL_TEXTURE_REGISTRAR(pointer), ((void*)NULL));
+  FlTextureRegistrar* registrar = FL_TEXTURE_REGISTRAR(pointer);
+  g_autoptr(FlTexture) texture = FL_TEXTURE(fl_test_registrar_texture_new());
+  fl_texture_registrar_register_texture(registrar, texture);
+  pthread_exit(NULL);
+}
+
 // Checks can make a mock registrar.
 TEST(FlTextureRegistrarTest, MockRegistrar) {
   g_autoptr(FlTexture) texture = FL_TEXTURE(fl_test_registrar_texture_new());
@@ -104,3 +114,25 @@
   EXPECT_TRUE(
       fl_texture_registrar_mark_texture_frame_available(registrar, texture));
 }
+
+// Test the textures can be accessed via multiple threads without
+// synchronization issues.
+TEST(FlTextureRegistrarTest, RegistrarRegisterTextureInMultipleThreads) {
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  g_autoptr(FlTextureRegistrar) registrar = fl_texture_registrar_new(engine);
+  pthread_t threads[kThreadCount];
+
+  for (uint64_t t = 0; t < kThreadCount; t++) {
+    EXPECT_EQ(pthread_create(&threads[t], NULL, add_mock_texture_to_registrar,
+                             (void*)registrar),
+              0);
+  }
+  for (uint64_t t = 0; t < kThreadCount; t++) {
+    pthread_join(threads[t], NULL);
+  };
+  // Check the texture named from [1, threadCount].
+  for (uint64_t t = 1; t <= kThreadCount; t++) {
+    EXPECT_TRUE(fl_texture_registrar_lookup_texture(registrar, (int64_t)t) !=
+                NULL);
+  };
+}