From 8bac82b6943f0064e5fa2ca2f6cce163d0065985 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 18 Feb 2016 00:47:57 -0500 Subject: [PATCH] Fix deadlock on self-initiated connection termination --- input.cpp | 4 +- main.cpp | 95 +++++++++++++++++++++++++++++++--------------- moonlight-common-c | 2 +- moonlight.hpp | 13 +++++++ 4 files changed, 80 insertions(+), 34 deletions(-) diff --git a/input.cpp b/input.cpp index 50bbbac..7f1bde8 100644 --- a/input.cpp +++ b/input.cpp @@ -130,8 +130,8 @@ bool MoonlightInstance::HandleInputEvent(const pp::InputEvent& event) { if (m_KeyModifiers == (MODIFIER_ALT | MODIFIER_CTRL | MODIFIER_SHIFT)) { if (keyboardEvent.GetKeyCode() == 0x51) { // Q key - // Call our connection listener to do the cleanup for us - MoonlightInstance::ClConnectionTerminated(0); + // Terminate the connection + StopConnection(); return true; } else { diff --git a/main.cpp b/main.cpp index 78ce30a..a6f5d91 100644 --- a/main.cpp +++ b/main.cpp @@ -6,8 +6,6 @@ #include "ppapi/cpp/input_event.h" -static char s_Host[256]; -static STREAM_CONFIGURATION s_StreamConfig; // you pair to a target #define PAIR_DIRECTIVE "pair:" // you need to show the apps of a target @@ -38,38 +36,81 @@ void MoonlightInstance::OnConnectionStarted(uint32_t unused) { } void MoonlightInstance::OnConnectionStopped(uint32_t error) { + // Not running anymore + g_Instance->m_Running = false; + // Stop receiving input events g_Instance->ClearInputEventRequest(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL | PP_INPUTEVENT_CLASS_KEYBOARD); // Unlock the mouse g_Instance->UnlockMouse(); + // Join threads + pthread_join(g_Instance->m_ConnectionThread, NULL); + pthread_join(g_Instance->m_GamepadThread, NULL); + pp::Var response("Connection terminated"); g_Instance->PostMessage(response); + + printf("Connection teardown complete\n"); +} + +void MoonlightInstance::StopConnection() { + pthread_t t; + + // Stopping needs to happen in a separate thread to avoid a potential deadlock + // caused by us getting a callback to the main thread while inside LiStopConnection. + pthread_create(&t, NULL, MoonlightInstance::StopThreadFunc, NULL); + + // We'll need to call the listener ourselves since our connection terminated callback + // won't be invoked for a manually requested termination. + OnConnectionStopped(0); +} + +void* MoonlightInstance::StopThreadFunc(void* context) { + // Stop the connection + LiStopConnection(); + return NULL; +} + +void* MoonlightInstance::GamepadThreadFunc(void* context) { + MoonlightInstance* me = (MoonlightInstance*)context; + + while (me->m_Running) { + me->PollGamepads(); + + // Poll every 10 ms + usleep(10 * 1000); + } + + return NULL; } void* MoonlightInstance::ConnectionThreadFunc(void* context) { MoonlightInstance* me = (MoonlightInstance*)context; int err; - err = LiStartConnection(s_Host, - &s_StreamConfig, + // Post a status update before we begin + pp::Var response("Starting connection to " + me->m_Host); + me->PostMessage(response); + + err = LiStartConnection(me->m_Host.c_str(), + &me->m_StreamConfig, &MoonlightInstance::s_ClCallbacks, &MoonlightInstance::s_DrCallbacks, &MoonlightInstance::s_ArCallbacks, - NULL, 0, 4); + NULL, 0, + me->m_ServerMajorVersion); if (err != 0) { pp::Var response("Starting connection failed"); - g_Instance->PostMessage(response); + me->PostMessage(response); return NULL; } - for (;;) { - me->PollGamepads(); - - // Poll every 10 ms - usleep(10 * 1000); - } + // Set running state before starting connection-specific threads + me->m_Running = true; + + pthread_create(&me->m_GamepadThread, NULL, MoonlightInstance::GamepadThreadFunc, me); return NULL; } @@ -110,29 +151,21 @@ void MoonlightInstance::handleShowGames(std::string showGamesMessage) { void MoonlightInstance::handleStartStream(std::string startStreamMessage) { // Populate the stream configuration - LiInitializeStreamConfiguration(&s_StreamConfig); - s_StreamConfig.width = 1280; - s_StreamConfig.height = 720; - s_StreamConfig.fps = 60; - s_StreamConfig.bitrate = 15000; // kilobits per second - s_StreamConfig.packetSize = 1024; - s_StreamConfig.streamingRemotely = 0; - s_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO; + m_StreamConfig.width = 1280; + m_StreamConfig.height = 720; + m_StreamConfig.fps = 60; + m_StreamConfig.bitrate = 15000; // kilobits per second + m_StreamConfig.packetSize = 1024; + m_StreamConfig.streamingRemotely = 0; + m_StreamConfig.audioConfiguration = AUDIO_CONFIGURATION_STEREO; + + m_ServerMajorVersion = 4; // Store the host, which is between two colons - std::string host = startStreamMessage.substr(strlen(START_STREAM_DIRECTIVE), startStreamMessage.substr(strlen(START_STREAM_DIRECTIVE)).find(":")); - strcpy(s_Host, host.c_str()); - - // store the gameID to start, which is after the last colon - std::string gameID = startStreamMessage.substr(startStreamMessage.find(host) + host.length() + 1); // +1 for the colon delimiter - - // Post a status update before we begin - pp::Var response("Starting connection to " + host + " to play game ID " + gameID); - PostMessage(response); + m_Host = startStreamMessage.substr(strlen(START_STREAM_DIRECTIVE), startStreamMessage.substr(strlen(START_STREAM_DIRECTIVE)).find(":")); // Start the worker thread to establish the connection - pthread_t t; - pthread_create(&t, NULL, MoonlightInstance::ConnectionThreadFunc, this); + pthread_create(&m_ConnectionThread, NULL, MoonlightInstance::ConnectionThreadFunc, this); } void MoonlightInstance::handleStopStream(std::string stopStreamMessage) { diff --git a/moonlight-common-c b/moonlight-common-c index c680a76..c9d3320 160000 --- a/moonlight-common-c +++ b/moonlight-common-c @@ -1 +1 @@ -Subproject commit c680a76289a59bbb8ada33ac348586981f0ebc28 +Subproject commit c9d332089fbd0662afb1386fcb4ae10979dc4f67 diff --git a/moonlight.hpp b/moonlight.hpp index 47e30e0..d46ff0d 100644 --- a/moonlight.hpp +++ b/moonlight.hpp @@ -47,6 +47,8 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock { // This function MUST be used otherwise sockets don't work (nacl_io_init() doesn't work!) nacl_io_init_ppapi(pp_instance(), pp::Module::Get()->get_browser_interface()); + LiInitializeStreamConfiguration(&m_StreamConfig); + m_GamepadApi = static_cast(pp::Module::Get()->GetBrowserInterface(PPB_GAMEPAD_INTERFACE)); } @@ -70,11 +72,14 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock { void OnConnectionStopped(uint32_t unused); void OnConnectionStarted(uint32_t error); + void StopConnection(); void DidChangeView(const pp::Rect& position, const pp::Rect& clip_ignored); static void* ConnectionThreadFunc(void* context); + static void* GamepadThreadFunc(void* context); + static void* StopThreadFunc(void* context); static void ClStageStarting(int stage); static void ClStageFailed(int stage, long errorCode); @@ -103,6 +108,14 @@ class MoonlightInstance : public pp::Instance, public pp::MouseLock { static CONNECTION_LISTENER_CALLBACKS s_ClCallbacks; static DECODER_RENDERER_CALLBACKS s_DrCallbacks; static AUDIO_RENDERER_CALLBACKS s_ArCallbacks; + + std::string m_Host; + STREAM_CONFIGURATION m_StreamConfig; + int m_ServerMajorVersion; + bool m_Running; + + pthread_t m_ConnectionThread; + pthread_t m_GamepadThread; pp::Graphics3D m_Graphics3D; pp::VideoDecoder* m_VideoDecoder;