rawinput: avoid deadlock when binding analog axis as a button (#809)

## Link to GitHub Issue or related Pull Request, if one exists
Regressed by #793

## Description of change
Due to lock inversion, when binding an analog axis as a button, spice
deadlocks. Fix that.

Also create a separate `unordered_map` that keeps track of device
handles so that `WM_INPUT` handle can look up devices without having to
acquire the larger `devices_mutex` which could be held by (potentially)
lengthy operations like hotplug.

Fix more synchronization issues around hotplug. Latent bug exposed by
MIDI 2.0 issues.

## Testing
This commit is contained in:
bicarus
2026-07-18 03:09:27 -07:00
committed by GitHub
parent e1d1b39567
commit c080bbe301
8 changed files with 160 additions and 78 deletions
+8 -4
View File
@@ -12,6 +12,7 @@
#include "device.h"
#include "hotplug.h"
#include "rawinput_handles.h"
#include "rawinput/xinput.h"
#include "util/scope_guard.h"
@@ -74,6 +75,9 @@ namespace rawinput {
std::list<Device> devices;
std::recursive_mutex devices_mutex;
// separate lookup isolated from devices_mutex for the latency-sensitive WM_INPUT path
RawInputHandles rawinput_handles;
WNDCLASSEX input_hwnd_class {};
std::thread *input_thread = nullptr;
std::thread *midi_thread = nullptr;
@@ -134,10 +138,9 @@ namespace rawinput {
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
static DeviceInfo get_device_info(const std::string &device_name);
// shared by the rawinput / xinput / midi device scans when replacing a slot
// in place; keeps the existing slot's mutex pair so snapshot pointers held by
// other threads stay valid (see the definition in rawinput.cpp)
static void reuse_device_mutexes(Device &replacement, const Device &existing);
// replace an already-destructed slot in place, holding both device locks so
// concurrent pollers and the output thread never see a half-copied Device
static void replace_device_slot(Device &existing, Device &replacement);
public:
@@ -193,6 +196,7 @@ namespace rawinput {
std::lock_guard<std::recursive_mutex> lock(devices_mutex);
for (auto &device : devices_get()) {
if (device.type == MIDI) {
std::lock_guard<std::mutex> device_lock(*device.mutex);
device.midiInfo->freeze = freeze;
if (!freeze) {
for (unsigned short index = 0; index < device.midiInfo->states.size(); index++) {