aaudio test: test hang in error callback

Call illegal functions that should return an error
but can hang if a race condition is forced.

This test also verifies that the error callback
is not running after the close() is finished.

Bug: 184370309
Test: Manual test. Enter:
Test:     adb shell test_disconnect_race
Test: Then plug/unplug headphones as directed.

Change-Id: I75f2d94329cd1b29cc3e6d275907172a7d7ac592
diff --git a/media/libaaudio/tests/Android.bp b/media/libaaudio/tests/Android.bp
index 62c9b46..f9eebd7 100644
--- a/media/libaaudio/tests/Android.bp
+++ b/media/libaaudio/tests/Android.bp
@@ -250,3 +250,16 @@
         "libutils",
     ],
 }
+
+
+cc_test {
+    name: "test_disconnect_race",
+    defaults: ["libaaudio_tests_defaults"],
+    srcs: ["test_disconnect_race.cpp"],
+    shared_libs: [
+        "libaaudio",
+        "libbinder",
+        "libcutils",
+        "libutils",
+    ],
+}
diff --git a/media/libaaudio/tests/test_disconnect_race.cpp b/media/libaaudio/tests/test_disconnect_race.cpp
new file mode 100644
index 0000000..6dbe165
--- /dev/null
+++ b/media/libaaudio/tests/test_disconnect_race.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Test whether an error callback is joined before the close finishes.
+ *
+ * Start a stream with a callback.
+ * The callback just sleeps for a long time.
+ * While the callback is sleeping, close() the stream from the main thread.
+ * Then check to make sure the callback was joined before the close() returns.
+ *
+ * This can hang if there are deadlocks. So make sure you get a PASSED result.
+ */
+
+#include <atomic>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <aaudio/AAudio.h>
+
+// Sleep long enough that the foreground has a chance to call close.
+static constexpr int kCallbackSleepMillis = 1000;
+static constexpr int kPollSleepMillis     =  100;
+
+static int sErrorCount = 0;
+
+#define MY_ASSERT_TRUE(statement) \
+    if (!(statement)) { \
+        printf("ERROR line:%d - " #statement "\n", __LINE__); \
+        sErrorCount++; \
+        return false; \
+    }
+
+#define MY_ASSERT_EQ(aa,bb) MY_ASSERT_TRUE(((aa) == (bb)))
+#define MY_ASSERT_NE(aa,bb) MY_ASSERT_TRUE(((aa) != (bb)))
+
+class AudioEngine {
+public:
+
+    // Check for a crash or late callback if we close without stopping.
+    bool checkCloseJoins(aaudio_direction_t direction,
+                             aaudio_performance_mode_t perfMode,
+                             bool callStopFromCallback) {
+        mCallStopFromCallback = callStopFromCallback;
+
+        if (!startStreamForStall(direction, perfMode)) return false;
+
+        printf("--------------------------------------------------------\n");
+        printf("%s() - direction = %d, perfMode = %d, callStop = %d\n",
+            __func__, direction, perfMode, callStopFromCallback);
+
+        // When the callback starts it will go to sleep.
+        if (!waitForCallbackToStart()) return false;
+
+        printf("call AAudioStream_close()\n");
+        MY_ASSERT_TRUE(!mCallbackFinished); // Still sleeping?
+        aaudio_result_t result = AAudioStream_close(mStream); // May hang here!
+        if (mCallbackStarted) {
+            MY_ASSERT_TRUE(mCallbackFinished);
+        }
+        MY_ASSERT_EQ(AAUDIO_OK, result);
+        printf("AAudioStream_close() returned %d\n", result);
+
+        MY_ASSERT_EQ(AAUDIO_ERROR_DISCONNECTED, mError.load());
+        if (mCallStopFromCallback) {
+            // Did calling stop() from callback fail? It should have.
+            MY_ASSERT_NE(AAUDIO_OK, mStopResult.load());
+        }
+
+        return true;
+    }
+
+private:
+    bool startStreamForStall(aaudio_direction_t direction,
+                             aaudio_performance_mode_t perfMode) {
+        AAudioStreamBuilder* builder = nullptr;
+        aaudio_result_t result = AAUDIO_OK;
+
+        // Use an AAudioStreamBuilder to contain requested parameters.
+        result = AAudio_createStreamBuilder(&builder);
+        MY_ASSERT_EQ(AAUDIO_OK, result);
+
+        // Request stream properties.
+        AAudioStreamBuilder_setDirection(builder, direction);
+        AAudioStreamBuilder_setPerformanceMode(builder, perfMode);
+        AAudioStreamBuilder_setDataCallback(builder, s_myDataCallbackProc, this);
+        AAudioStreamBuilder_setErrorCallback(builder, s_myErrorCallbackProc, this);
+
+        // Create an AAudioStream using the Builder.
+        result = AAudioStreamBuilder_openStream(builder, &mStream);
+        AAudioStreamBuilder_delete(builder);
+        MY_ASSERT_EQ(AAUDIO_OK, result);
+
+        // Check to see what kind of stream we actually got.
+        int32_t deviceId = AAudioStream_getDeviceId(mStream);
+        aaudio_performance_mode_t
+            actualPerfMode = AAudioStream_getPerformanceMode(mStream);
+        printf("-------- opened: deviceId = %3d, perfMode = %d\n",
+               deviceId,
+               actualPerfMode);
+
+        // Start stream.
+        result = AAudioStream_requestStart(mStream);
+        MY_ASSERT_EQ(AAUDIO_OK, result);
+
+        return true;
+    }
+
+    bool waitForCallbackToStart() {
+        // Wait for callback to say it has been called.
+        int countDown = 10 * 1000 / kPollSleepMillis;
+        while (!mCallbackStarted && countDown > 0) {
+            if ((countDown % 5) == 0) {
+                printf("===== Please PLUG or UNPLUG headphones! ======= %d\n", countDown);
+            }
+            usleep(kPollSleepMillis * 1000);
+            countDown--;
+        }
+        MY_ASSERT_TRUE(countDown > 0);
+        MY_ASSERT_TRUE(mCallbackStarted);
+        return true;
+    }
+
+// Callback function that fills the audio output buffer.
+    static aaudio_data_callback_result_t s_myDataCallbackProc(
+            AAudioStream * /* stream */,
+            void * /* userData */,
+            void * /* audioData */,
+            int32_t /* numFrames */
+    ) {
+        return AAUDIO_CALLBACK_RESULT_CONTINUE;
+    }
+
+    static void s_myErrorCallbackProc(
+                AAudioStream * stream,
+                void *userData,
+                aaudio_result_t error) {
+        AudioEngine *engine = (AudioEngine *)userData;
+        engine->mError = error;
+        engine->mCallbackStarted = true;
+        usleep(kCallbackSleepMillis * 1000);
+        // it is illegal to call stop() from the callback. It should
+        // return an error and not hang.
+        if (engine->mCallStopFromCallback) {
+            engine->mStopResult = AAudioStream_requestStop(stream);
+        }
+        engine->mCallbackFinished = true;
+    }
+
+    AAudioStream* mStream = nullptr;
+
+    std::atomic<aaudio_result_t> mError{AAUDIO_OK}; // written by error callback
+    std::atomic<bool> mCallStopFromCallback{false};
+    std::atomic<bool> mCallbackStarted{false};   // written by error callback
+    std::atomic<bool> mCallbackFinished{false};  // written by error callback
+    std::atomic<aaudio_result_t> mStopResult{AAUDIO_OK};
+};
+
+int main(int, char **) {
+    // Parameters to test.
+    static aaudio_direction_t directions[] = {AAUDIO_DIRECTION_OUTPUT,
+                                            AAUDIO_DIRECTION_INPUT};
+    static aaudio_performance_mode_t perfModes[] =
+        {AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, AAUDIO_PERFORMANCE_MODE_NONE};
+    static bool callStops[] = { false, true };
+
+    // Make printf print immediately so that debug info is not stuck
+    // in a buffer if we hang or crash.
+    setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+
+    printf("Test Disconnect Race V1.0\n");
+    printf("\n");
+
+    for (auto callStop : callStops) {
+        for (auto direction : directions) {
+            for (auto perfMode : perfModes) {
+                AudioEngine engine;
+                engine.checkCloseJoins(direction, perfMode, callStop);
+            }
+        }
+    }
+
+    printf("Error Count = %d, %s\n", sErrorCount,
+           ((sErrorCount == 0) ? "PASS" : "FAIL"));
+}