rtworkq: don't hold queues_section while shutting down the system queues

MFShutdown() held queues_section while waiting for pool callbacks, and a
source reader callback unlocking its user queue waited for the same
lock, hanging the Kingdom Hearts 358/2 Days port after its first
cutscene. Shut the system queues down outside the lock, like
unlock_user_queue() already does for user queues.

https://github.com/GloriousEggroll/proton-ge-custom/issues/899

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
GloriousEggrollandClaude Opus 5.5 committed 2026-11-02 19:29:51 -07:00
1 parent 2aab9cfd2e
commit ce40c340cc
2 files changed
+77

No files matched your search

+4
View File
@@ -645,6 +645,10 @@ apply_all_in_dir() {
echo "WINE: -WAYLAND- winewayland: animate animated cursors (game cursors static on Wine-Wayland, The Sims 3)"
apply_patch "../patches/wine-hotfixes/pending/winewayland-animate-animated-cursors.patch"
# https://github.com/GloriousEggroll/proton-ge-custom/issues/899
echo "WINE: -GAME FIXES- rtworkq: don't hold queues_section while shutting down the system queues (MFShutdown deadlock, Kingdom Hearts 358/2 Days port hangs after the first cutscene)"
apply_patch "../patches/wine-hotfixes/pending/rtworkq-dont-hold-queues-section-during-system-queue-shutdown.patch"
# https://github.com/GloriousEggroll/proton-ge-custom/issues/881
echo "WINE: -HOTFIX- win32u: defer destroying NVIDIA Wayland host Vulkan instances while another is alive (NVIDIA driver clears its global libwayland table, EA app crash on winewayland)"
apply_patch "../patches/wine-hotfixes/pending/win32u-defer-nvidia-wayland-host-instance-destroy.patch"
@@ -0,0 +1,73 @@
From: GloriousEggroll <[email protected]>
Date: Mon, 2 Nov 2026 20:00:00 -0700
Subject: [PATCH] rtworkq: Don't hold queues_section while shutting down the
system queues.
shutdown_system_queues() held queues_section across shutdown_queue(),
which waits for the running pool callbacks. A callback that unlocks a
user queue takes queues_section too, so MFShutdown() deadlocked with it:
thread A: MFShutdown -> RtwqShutdown -> shutdown_system_queues
(holds queues_section, waits for the callback)
thread B: source reader callback -> RtwqUnlockWorkQueue
(waits for queues_section)
unlock_user_queue() already shuts its queue down outside the lock.
Shut the standard queue down first, without the lock. grab_queue() then
fails, and it now rechecks that under the lock, so no new system queue
is created behind us; taking the lock once waits for one still being
created. Then shut the other system queues down, also without the lock.
https://github.com/GloriousEggroll/proton-ge-custom/issues/899
---
--- a/dlls/rtworkq/queue.c
+++ b/dlls/rtworkq/queue.c
@@ -665,6 +665,18 @@
struct queue_desc desc;
EnterCriticalSection(&queues_section);
+ /* Don't create a system queue after shutdown_system_queues() went past it. */
+ if (!system_queues[SYS_QUEUE_STANDARD].pool)
+ {
+ LeaveCriticalSection(&queues_section);
+ return RTWQ_E_SHUTDOWN;
+ }
+ if (queue->pool)
+ {
+ LeaveCriticalSection(&queues_section);
+ *ret = queue;
+ return S_OK;
+ }
switch (queue_id)
{
case RTWQ_CALLBACK_QUEUE_IO:
@@ -1310,17 +1322,26 @@
unsigned int i;
HRESULT hr;
+ /* Shutting down a pool queue waits for its running callbacks, which may
+ * need queues_section themselves, e.g. to unlock a user queue. Don't hold
+ * it while waiting.
+ *
+ * Once the standard queue is shut down, grab_queue() fails and creates no
+ * more system queues. Taking the lock waits for one that is still being
+ * created, so that it is shut down below. */
+ shutdown_queue(&system_queues[SYS_QUEUE_STANDARD]);
+
EnterCriticalSection(&queues_section);
+ LeaveCriticalSection(&queues_section);
for (i = 0; i < ARRAY_SIZE(system_queues); ++i)
{
+ if (i == SYS_QUEUE_STANDARD) continue;
shutdown_queue(&system_queues[i]);
}
if (FAILED(hr = CoDecrementMTAUsage(mta_cookie)))
WARN("Failed to uninitialize MTA, hr %#lx.\n", hr);
-
- LeaveCriticalSection(&queues_section);
}
HRESULT WINAPI RtwqShutdown(void)