#include "gtest/gtest.h"
#include "common.h"
#include "cubeb/cubeb.h"
#include "cubeb_utils.h"
#include <iostream>
#include <pthread.h>
#include <signal.h>
#include <stdexcept>
#include <string>
#include <unistd.h>
#include <atomic>
#define CALL_THREAD_KILLER SIGUSR1
bool killed = false;
std::atomic<bool> task_done{ false };
bool called = false;
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 * , void * , cubeb_state )
{
}
template<typename T>
long data_cb(cubeb_stream * , void * ,
const void * , void * outputbuffer, long nframes)
{
called = true;
uint64_t tid; 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);
usleep(10000);
callbacking_before_getting_context = false;
}
fprintf(stderr, "[%llu] try getting backend id ...\n", tid);
char const * backend_id = cubeb_get_backend_id(get_cubeb_context());
fprintf(stderr, "[%llu] callback on %s\n", tid, backend_id);
memset(outputbuffer, 0, nframes * 2 * sizeof(float));
return nframes;
}
void get_preferred_channel_layout()
{
auto_lock lock(context_mutex);
cubeb * context = get_cubeb_context_unlocked();
ASSERT_TRUE(!!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; pthread_threadid_np(NULL, &tid);
while(!callbacking_before_getting_context) {
fprintf(stderr, "[%llu] waiting for data callback ...\n", tid);
usleep(1000); }
fprintf(stderr, "[%llu] try getting channel layout ...\n", tid);
get_preferred_channel_layout(); task_done = true;
return NULL;
}
void * watchdog(void * s)
{
uint64_t tid; pthread_threadid_np(NULL, &tid);
pthread_t subject = *((pthread_t *) s);
uint64_t stid; 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);
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);
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,
¶ms, 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);
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 ");
ASSERT_NE(task_done.load(), killed);
ASSERT_TRUE(task_done.load());
cubeb_stream_stop(stream);
}