Use semaphore instead of condition variable

BackgroundExecutor uses a condition variable to synchronize processing
to a work queue, which means that there is possible mutex contention on
the main thread in rare scenarios.

Instead, we can make the main thread non-blocking with a thread-safe
queue, and use semaphores for signaling the background thread to wake
up. Using semaphores saves a small number of instructions, but overall
reduces contention

Bug: 232113929
Test: bouncy ball

Change-Id: Iced25e8349bdb2a70cac1ed681059a0b14258407
diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h
index 6db7dda..eeaf3bd 100644
--- a/services/surfaceflinger/BackgroundExecutor.h
+++ b/services/surfaceflinger/BackgroundExecutor.h
@@ -16,9 +16,11 @@
 
 #pragma once
 
+#include <Tracing/LocklessStack.h>
 #include <android-base/thread_annotations.h>
+#include <ftl/small_vector.h>
+#include <semaphore.h>
 #include <utils/Singleton.h>
-#include <condition_variable>
 #include <mutex>
 #include <queue>
 #include <thread>
@@ -30,13 +32,26 @@
 public:
     BackgroundExecutor();
     ~BackgroundExecutor();
-    void execute(std::function<void()>);
+    using Callbacks = ftl::SmallVector<std::function<void()>, 10>;
+    // Queues callbacks onto a work queue to be executed by a background thread.
+    // Note that this is not thread-safe - a single producer is assumed.
+    void sendCallbacks(Callbacks&& tasks);
 
 private:
-    std::mutex mMutex;
-    std::condition_variable mWorkAvailableCv GUARDED_BY(mMutex);
-    bool mDone GUARDED_BY(mMutex) = false;
-    std::vector<std::function<void()>> mTasks GUARDED_BY(mMutex);
+    sem_t mSemaphore;
+    std::atomic_bool mDone = false;
+
+    // Sequence number for work items.
+    // Work items are batched by sequence number. Work items for earlier sequence numbers are
+    // executed first. Work items with the same sequence number are executed in the same order they
+    // were added to the stack (meaning the stack must reverse the order after popping from the
+    // queue)
+    int32_t mSequence = 0;
+    struct Work {
+        int32_t sequence = 0;
+        Callbacks tasks;
+    };
+    LocklessStack<Work> mWorks;
     std::thread mThread;
 };