aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/test_deadlock.cpp
diff options
context:
space:
mode:
authorPaul Adenot <[email protected]>2023-11-10 17:00:17 +0100
committerPaul Adenot <[email protected]>2023-11-10 17:00:19 +0100
commit30efcd1cdf8a794e918fe2c6e9b74c8ea8bbcdd6 (patch)
tree5706d988a678166ad5ddc39a5ed8476e52783a10 /test/test_deadlock.cpp
parent2f01da4a8a8aa55916ec3ac84aa7908665b12899 (diff)
downloadcubeb-30efcd1cdf8a794e918fe2c6e9b74c8ea8bbcdd6.tar.gz
cubeb-30efcd1cdf8a794e918fe2c6e9b74c8ea8bbcdd6.zip
Remove test_deadlock.cpp file, hasn't been compiled since March 2018
Removed in 789eaa.
Diffstat (limited to 'test/test_deadlock.cpp')
-rw-r--r--test/test_deadlock.cpp277
1 files changed, 0 insertions, 277 deletions
diff --git a/test/test_deadlock.cpp b/test/test_deadlock.cpp
deleted file mode 100644
index 8684881..0000000
--- a/test/test_deadlock.cpp
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright © 2017 Mozilla Foundation
- *
- * This program is made available under an ISC-style license. See the
- * accompanying file LICENSE for details.
- *
- *
- * Purpose
- * =============================================================================
- * In CoreAudio, the data callback will holds a mutex shared with AudioUnit
- * (mutex_AU). Thus, if the callback request another mutex M held by the another
- * function, without releasing mutex_AU, then it will cause a deadlock when the
- * another function, which holds the mutex M, request to use AudioUnit.
- *
- * The following figure illustrates the deadlock in bug 1337805:
- * https://bugzilla.mozilla.org/show_bug.cgi?id=1337805
- * (The detail analysis can be found on bug 1350511:
- * https://bugzilla.mozilla.org/show_bug.cgi?id=1350511)
- *
- * holds
- * data_callback <---------- mutext_AudioUnit(mutex_AU)
- * | ^
- * | |
- * | request | request
- * | |
- * v holds |
- * mutex_cubeb ------------> get_channel_layout
- *
- * In this example, the "audiounit_get_channel_layout" in f4edfb8:
- * https://github.com/kinetiknz/cubeb/blob/f4edfb8eea920887713325e44773f3a2d959860c/src/cubeb_audiounit.cpp#L2725
- * requests the mutex_AU to create an AudioUnit, when it holds a mutex for cubeb
- * context. Meanwhile, the data callback who holds the mutex_AU requests the
- * mutex for cubeb context. As a result, it causes a deadlock.
- *
- * The problem is solve by pull 236: https://github.com/kinetiknz/cubeb/pull/236
- * We store the latest channel layout and return it when there is an active
- * AudioUnit, otherwise, we will create an AudioUnit to get it.
- *
- * Although the problem is solved, to prevent it happens again, we add the test
- * here in case someone without such knowledge misuses the AudioUnit in
- * get_channel_layout. Moreover, it's a good way to record the known issues
- * to warn other developers.
- */
-
-#include "gtest/gtest.h"
-// #define ENABLE_NORMAL_LOG
-// #define ENABLE_VERBOSE_LOG
-#include "common.h" // for layout_infos
-#include "cubeb/cubeb.h" // for cubeb utils
-#include "cubeb_utils.h" // for owned_critical_section, auto_lock
-#include <atomic> // for std::atomic
-#include <iostream> // for fprintf
-#include <pthread.h> // for pthread
-#include <signal.h> // for signal
-#include <stdexcept> // for std::logic_error
-#include <string> // for std::string
-#include <unistd.h> // for sleep, usleep
-
-// The signal alias for calling our thread killer.
-#define CALL_THREAD_KILLER SIGUSR1
-
-// This indicator will become true when our pending task thread is killed by
-// ourselves.
-bool killed = false;
-
-// This indicator will become true when the assigned task is done.
-std::atomic<bool> task_done{false};
-
-// Indicating the data callback is fired or not.
-bool called = false;
-
-// Toggle to true when running data callback. Before data callback gets
-// the mutex for cubeb context, it toggles back to false.
-// The task to get channel layout should be executed when this is true.
-std::atomic<bool> callbacking_before_getting_context{false};
-
-owned_critical_section context_mutex;
-cubeb * context = nullptr;
-
-cubeb *
-get_cubeb_context_unlocked()
-{
- if (context) {
- return context;
- }
-
- int r = CUBEB_OK;
- r = common_init(&context, "Cubeb deadlock test");
- if (r != CUBEB_OK) {
- context = nullptr;
- }
-
- return context;
-}
-
-cubeb *
-get_cubeb_context()
-{
- auto_lock lock(context_mutex);
- return get_cubeb_context_unlocked();
-}
-
-void
-state_cb_audio(cubeb_stream * /*stream*/, void * /*user*/,
- cubeb_state /*state*/)
-{
-}
-
-// Fired by coreaudio's rendering mechanism. It holds a mutex shared with the
-// current used AudioUnit.
-template <typename T>
-long
-data_cb(cubeb_stream * /*stream*/, void * /*user*/,
- const void * /*inputbuffer*/, void * outputbuffer, long nframes)
-{
- called = true;
-
- uint64_t tid; // Current thread id.
- pthread_threadid_np(NULL, &tid);
- fprintf(stderr, "Audio output is on thread %llu\n", tid);
-
- if (!task_done) {
- callbacking_before_getting_context = true;
- fprintf(stderr, "[%llu] time to switch thread\n", tid);
- // Force to switch threads by sleeping 10 ms. Notice that anything over
- // 10ms would create a glitch. It's intended here for test, so the delay
- // is ok.
- usleep(10000);
- callbacking_before_getting_context = false;
- }
-
- fprintf(stderr, "[%llu] try getting backend id ...\n", tid);
-
- // Try requesting mutex for context by get_cubeb_context()
- // when holding a mutex for AudioUnit.
- char const * backend_id = cubeb_get_backend_id(get_cubeb_context());
- fprintf(stderr, "[%llu] callback on %s\n", tid, backend_id);
-
- // Mute the output (or get deaf)
- memset(outputbuffer, 0, nframes * 2 * sizeof(float));
- return nframes;
-}
-
-// Called by wait_to_get_layout, which is run out of main thread.
-void
-get_preferred_channel_layout()
-{
- auto_lock lock(context_mutex);
- cubeb * context = get_cubeb_context_unlocked();
- ASSERT_TRUE(!!context);
-
- // We will cause a deadlock if cubeb_get_preferred_channel_layout requests
- // mutex for AudioUnit when it holds mutex for context.
- cubeb_channel_layout layout;
- int r = cubeb_get_preferred_channel_layout(context, &layout);
- ASSERT_EQ(r == CUBEB_OK, layout != CUBEB_LAYOUT_UNDEFINED);
- fprintf(stderr, "layout is %s\n", layout_infos[layout].name);
-}
-
-void *
-wait_to_get_layout(void *)
-{
- uint64_t tid; // Current thread id.
- pthread_threadid_np(NULL, &tid);
-
- while (!callbacking_before_getting_context) {
- fprintf(stderr, "[%llu] waiting for data callback ...\n", tid);
- usleep(1000); // Force to switch threads by sleeping 1 ms.
- }
-
- fprintf(stderr, "[%llu] try getting channel layout ...\n", tid);
- get_preferred_channel_layout(); // Deadlock checkpoint.
- task_done = true;
-
- return NULL;
-}
-
-void *
-watchdog(void * s)
-{
- uint64_t tid; // Current thread id.
- pthread_threadid_np(NULL, &tid);
-
- pthread_t subject = *((pthread_t *)s);
- uint64_t stid; // task thread id.
- pthread_threadid_np(subject, &stid);
-
- unsigned int sec = 2;
- fprintf(stderr,
- "[%llu] sleep %d seconds before checking task for thread %llu\n", tid,
- sec, stid);
- sleep(sec); // Force to switch threads.
-
- fprintf(stderr, "[%llu] check task for thread %llu now\n", tid, stid);
- if (!task_done) {
- fprintf(stderr, "[%llu] kill the task thread %llu\n", tid, stid);
- pthread_kill(subject, CALL_THREAD_KILLER);
- pthread_detach(subject);
- // pthread_kill doesn't release the mutex held by the killed thread,
- // so we need to unlock it manually.
- context_mutex.unlock();
- }
- fprintf(stderr, "[%llu] the assigned task for thread %llu is %sdone\n", tid,
- stid, (task_done) ? "" : "not ");
-
- return NULL;
-}
-
-void
-thread_killer(int signal)
-{
- ASSERT_EQ(signal, CALL_THREAD_KILLER);
- fprintf(stderr, "task thread is killed!\n");
- killed = true;
-}
-
-TEST(cubeb, run_deadlock_test)
-{
-#if !defined(__APPLE__)
- FAIL() << "Deadlock test is only for OSX now";
-#endif
-
- cubeb * ctx = get_cubeb_context();
- ASSERT_TRUE(!!ctx);
-
- std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
- ctx, cubeb_destroy);
-
- cubeb_stream_params params;
- params.format = CUBEB_SAMPLE_FLOAT32NE;
- params.rate = 44100;
- params.channels = 2;
- params.layout = CUBEB_LAYOUT_STEREO;
- params.prefs = CUBEB_STREAM_PREF_NONE;
-
- cubeb_stream * stream = NULL;
- int r =
- cubeb_stream_init(ctx, &stream, "test deadlock", NULL, NULL, NULL,
- &params, 512, &data_cb<float>, state_cb_audio, NULL);
- ASSERT_EQ(r, CUBEB_OK);
-
- std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
- cleanup_stream_at_exit(stream, cubeb_stream_destroy);
-
- // Install signal handler.
- signal(CALL_THREAD_KILLER, thread_killer);
-
- pthread_t subject, detector;
- pthread_create(&subject, NULL, wait_to_get_layout, NULL);
- pthread_create(&detector, NULL, watchdog, (void *)&subject);
-
- uint64_t stid, dtid;
- pthread_threadid_np(subject, &stid);
- pthread_threadid_np(detector, &dtid);
- fprintf(stderr, "task thread %llu, monitor thread %llu are created\n", stid,
- dtid);
-
- cubeb_stream_start(stream);
-
- pthread_join(subject, NULL);
- pthread_join(detector, NULL);
-
- ASSERT_TRUE(called);
-
- fprintf(stderr, "\n%sDeadlock detected!\n",
- (called && !task_done.load()) ? "" : "No ");
-
- // Check the task is killed by ourselves if deadlock happends.
- // Otherwise, thread_killer should not be triggered.
- ASSERT_NE(task_done.load(), killed);
-
- ASSERT_TRUE(task_done.load());
-
- cubeb_stream_stop(stream);
-}
-
-#undef CALL_THREAD_KILLER