mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-02 11:09:29 -06:00

Remove the redundant s_populate_mutex and only use ControllerInterface::m_devices_population_mutex instead to prevent a deadlock caused by locking them in opposite orders. The device population functions in the win32 InputBackend previously locked s_populate_mutex first before calling various functions that locked m_devices_population_mutex. This normally worked but ControllerInterface::RefreshDevices locks m_devices_population_mutex first and then calls HandleWindowChange which then locked s_populate_mutex, potentially causing the deadlock. Fix this by using PlatformPopulateDevices to lock m_devices_population_mutex before running the code that was previously protected by s_populate_mutex. The functions in question lock m_devices_population_mutex anyway, so this shouldn't meaningfully increase contention on the lock. Reproduction steps: * Let Dolphin finish startup. * In Win32.cpp::OnDevicesChanged set a breakpoint on the call to PlatformPopulateDevices. When the breakpoint is triggered the function will have locked s_populate_mutex, but since PlatformPopulateDevices won't have run yet m_devices_population_mutex will still be unlocked. * Unplug a device from your computer. * Wait for the breakpoint to trigger. (At this point you can plug the device back in). * Freeze the ntdll.dll!TppWorkerThread() that triggered the breakpoint. * Resume Dolphin and start a game. * Core::EmuThread will call ControllerInterface::ChangeWindow which calls RefreshDevices. It locks m_devices_population_mutex, then calls InputBackend::HandleWindowChange, which tries to lock s_populate_mutex. * Unfreeze ntdll.dll!TppWorkerThread(). At this point EmuThread and TppWorkerThread are deadlocked. The UI is still responsive since the Host thread is unaffected, but trying to stop the game or close Dolphin normally will fail since EmuThread is unable to stop.
129 lines
3.8 KiB
C++
129 lines
3.8 KiB
C++
// Copyright 2017 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "InputCommon/ControllerInterface/Win32/Win32.h"
|
|
|
|
#include <Windows.h>
|
|
#include <cfgmgr32.h>
|
|
// must be before hidclass
|
|
#include <initguid.h>
|
|
|
|
#include <hidclass.h>
|
|
|
|
#include "Common/Flag.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "InputCommon/ControllerInterface/DInput/DInput.h"
|
|
#include "InputCommon/ControllerInterface/WGInput/WGInput.h"
|
|
#include "InputCommon/ControllerInterface/XInput/XInput.h"
|
|
|
|
#pragma comment(lib, "OneCoreUAP.Lib")
|
|
|
|
// TODO is this really needed?
|
|
static Common::Flag s_first_populate_devices_asked;
|
|
static HCMNOTIFICATION s_notify_handle;
|
|
|
|
namespace ciface::Win32
|
|
{
|
|
class InputBackend final : public ciface::InputBackend
|
|
{
|
|
public:
|
|
InputBackend(ControllerInterface* controller_interface);
|
|
~InputBackend() override;
|
|
|
|
void PopulateDevices() override;
|
|
void HandleWindowChange() override;
|
|
HWND GetHWND();
|
|
};
|
|
} // namespace ciface::Win32
|
|
|
|
_Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALLBACK
|
|
OnDevicesChanged(_In_ HCMNOTIFICATION hNotify, _In_opt_ PVOID Context,
|
|
_In_ CM_NOTIFY_ACTION Action,
|
|
_In_reads_bytes_(EventDataSize) PCM_NOTIFY_EVENT_DATA EventData,
|
|
_In_ DWORD EventDataSize)
|
|
{
|
|
if (Action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
|
|
Action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL)
|
|
{
|
|
// Windows automatically sends this message before we ask for it and before we are "ready" to
|
|
// listen for it.
|
|
if (s_first_populate_devices_asked.IsSet())
|
|
{
|
|
// TODO: we could easily use the message passed alongside this event, which tells
|
|
// whether a device was added or removed, to avoid removing old, still connected, devices
|
|
g_controller_interface.PlatformPopulateDevices([&] {
|
|
ciface::DInput::PopulateDevices(
|
|
static_cast<ciface::Win32::InputBackend*>(Context)->GetHWND());
|
|
ciface::XInput::PopulateDevices();
|
|
});
|
|
}
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
namespace ciface::Win32
|
|
{
|
|
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
|
|
{
|
|
return std::make_unique<InputBackend>(controller_interface);
|
|
}
|
|
|
|
HWND InputBackend::GetHWND()
|
|
{
|
|
return static_cast<HWND>(GetControllerInterface().GetWindowSystemInfo().render_window);
|
|
}
|
|
|
|
InputBackend::InputBackend(ControllerInterface* controller_interface)
|
|
: ciface::InputBackend(controller_interface)
|
|
{
|
|
XInput::Init();
|
|
WGInput::Init();
|
|
|
|
CM_NOTIFY_FILTER notify_filter{.cbSize = sizeof(notify_filter),
|
|
.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE,
|
|
.u{.DeviceInterface{.ClassGuid = GUID_DEVINTERFACE_HID}}};
|
|
const CONFIGRET cfg_rv =
|
|
CM_Register_Notification(¬ify_filter, this, OnDevicesChanged, &s_notify_handle);
|
|
if (cfg_rv != CR_SUCCESS)
|
|
{
|
|
ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Register_Notification failed: {:x}", cfg_rv);
|
|
}
|
|
}
|
|
|
|
void InputBackend::PopulateDevices()
|
|
{
|
|
g_controller_interface.PlatformPopulateDevices([this] {
|
|
s_first_populate_devices_asked.Set();
|
|
ciface::DInput::PopulateDevices(GetHWND());
|
|
ciface::XInput::PopulateDevices();
|
|
ciface::WGInput::PopulateDevices();
|
|
});
|
|
}
|
|
|
|
void InputBackend::HandleWindowChange()
|
|
{
|
|
g_controller_interface.PlatformPopulateDevices(
|
|
[this] { ciface::DInput::ChangeWindow(GetHWND()); });
|
|
}
|
|
|
|
InputBackend::~InputBackend()
|
|
{
|
|
s_first_populate_devices_asked.Clear();
|
|
DInput::DeInit();
|
|
|
|
if (s_notify_handle)
|
|
{
|
|
const CONFIGRET cfg_rv = CM_Unregister_Notification(s_notify_handle);
|
|
if (cfg_rv != CR_SUCCESS)
|
|
{
|
|
ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Unregister_Notification failed: {:x}", cfg_rv);
|
|
}
|
|
s_notify_handle = nullptr;
|
|
}
|
|
|
|
XInput::DeInit();
|
|
WGInput::DeInit();
|
|
}
|
|
|
|
} // namespace ciface::Win32
|