iTunes hangs during playback, then asserts: main thread holds an iTunes.exe critical section (desktop, wine-11.20) #15

Open
opened 2026-10-29 07:36:26 +01:00 by The_Void · 2 comments
Owner

iTunes 12.13.11.1 64-bit on the desktop (Fedora wine-11.20 Staging, the fresh install.sh prefix from 2026-10-29 07:25) hung during playback, then crashed. The operator was only playing music (Apple Music streaming, judging by SubscriptionPlayCache), with no device plugged in.

Evidence is from journalctl --user, unit app-itunes\x2dwine@…, process env[18495]:

  • Up to 07:30:39.065, the main thread (0024) logs UiaRaiseAutomationPropertyChangedEvent … 30047 about every 500 ms with an increasing value, which is the playhead advancing. Then it goes silent and playback stops.
  • 07:30:44.42 / .47: 04f4 and 04dc both log err:sync:RtlpWaitForCriticalSection section 00000001420ADC10 "?" wait timed out …, blocked by 0024. Wine prints that after a 5 s wait, so they have been blocked since about 07:30:39. They log it again at 07:31:44.
    • The section is in iTunes.exe's own image (140000000–142593000).
    • 04a8 and 04e0 are the audio threads, the ones that call AvSetMmThreadCharacteristicsW("Pro Audio") for each track. 04dc was created just before 04e0.
  • 07:32:13: Assertion failed at address 0000000140B07004 (thread 0024) (STATUS_ASSERTION_FAILURE, which is iTunes' own check failing, not Wine's).
    • rbx=1420adc50, r14=1420adc00 and r15=1420adcf0 all point into the structure holding that critical section.
    • Backtrace: itunes+0xb07004 ← +0xb07190 ← +0xb077ae ← +0xb07fdd ← +0xaf5dd0 ← +0xcb2568 ← … ← corefoundation+0x8cee6 (a CFRunLoop callout) ← itunes+0x25e51d ← … ← kernel32.
  • Afterwards winedbg --auto stayed attached to the dead process until wineserver -k. The library (iTunes Library.itl) was last saved at 07:28:41.
  • The prefix uses DXVK, since Fedora's wine pulls in wine-dxvk, and a D3D11 device was created at 07:30:11. UNKNOWN whether that matters.

This is a different signature from the two crashes in KNOWLEDGE.md → Update check in CI, which were page faults.

Why it matters: playback is a required feature, and this kills it with no error message.

Done when: it is reproduced with WINEDEBUG=+seh,+sync (or +relay scoped to ntdll) and the lock holder's stack at the moment of the hang is known. Either a workaround is found (for example a Wine version, a DLL override, or DXVK off via WINEDLLOVERRIDES=d3d11,dxgi=b), or it is recorded as an UNKNOWN cause with the measured frequency.

iTunes 12.13.11.1 64-bit on the desktop (Fedora `wine-11.20` Staging, the fresh `install.sh` prefix from 2026-10-29 07:25) hung during playback, then crashed. The operator was only playing music (Apple Music streaming, judging by `SubscriptionPlayCache`), with no device plugged in. Evidence is from `journalctl --user`, unit `app-itunes\x2dwine@…`, process `env[18495]`: - Up to **07:30:39.065**, the main thread (`0024`) logs `UiaRaiseAutomationPropertyChangedEvent … 30047` about every 500 ms with an increasing value, which is the playhead advancing. Then it goes silent and playback stops. - **07:30:44.42 / .47**: `04f4` and `04dc` both log `err:sync:RtlpWaitForCriticalSection section 00000001420ADC10 "?" wait timed out …, blocked by 0024`. Wine prints that after a 5 s wait, so they have been blocked since about 07:30:39. They log it again at 07:31:44. - The section is in `iTunes.exe`'s own image (`140000000–142593000`). - `04a8` and `04e0` are the audio threads, the ones that call `AvSetMmThreadCharacteristicsW("Pro Audio")` for each track. `04dc` was created just before `04e0`. - **07:32:13**: `Assertion failed at address 0000000140B07004 (thread 0024)` (STATUS_ASSERTION_FAILURE, which is iTunes' own check failing, not Wine's). - `rbx`=`1420adc50`, `r14`=`1420adc00` and `r15`=`1420adcf0` all point into the structure holding that critical section. - Backtrace: `itunes+0xb07004 ← +0xb07190 ← +0xb077ae ← +0xb07fdd ← +0xaf5dd0 ← +0xcb2568 ← … ← corefoundation+0x8cee6` (a CFRunLoop callout) `← itunes+0x25e51d ← … ← kernel32`. - Afterwards `winedbg --auto` stayed attached to the dead process until `wineserver -k`. The library (`iTunes Library.itl`) was last saved at 07:28:41. - The prefix uses DXVK, since Fedora's `wine` pulls in `wine-dxvk`, and a D3D11 device was created at 07:30:11. UNKNOWN whether that matters. This is a different signature from the two crashes in `KNOWLEDGE.md` → *Update check in CI*, which were page faults. **Why it matters:** playback is a required feature, and this kills it with no error message. **Done when:** it is reproduced with `WINEDEBUG=+seh,+sync` (or `+relay` scoped to `ntdll`) and the lock holder's stack at the moment of the hang is known. Either a workaround is found (for example a Wine version, a DLL override, or DXVK off via `WINEDLLOVERRIDES=d3d11,dxgi=b`), or it is recorded as an UNKNOWN cause with the measured frequency.
The_Void added this to the Ready to work milestone 2026-10-29 07:36:26 +01:00
Author
Owner

Second hang, 2026-10-29 07:37:52 (same session pattern: playing music, then the authorize dialog). This time there was no crash and no critical-section timeout. iTunes stayed up with no CPU use. I ran winedbg with attach 0x20 / bt all / detach; the full dump is on the desktop, not committed.

  • Three threads are blocked in the same iTunes routine: 0024 (main), 03c8 and 03d0. Each is in itunes+0xb07264, the return address of a SignalObjectAndWait(signal=?, wait=<its own handle>, INFINITE, FALSE) call. The wait handles are 0xE70, 0x130 and 0x5B4. All three are reached through itunes+0xb077ae.
  • What the routine is (objdump of iTunes.exe 12.13.11.1, 0x140b070f0): a user-mode thread scheduler that passes a baton between threads.
    • Its state sits at 0x1420adc00: magic 'lock' (0x6c6f636b), owner tid, recursion count, then the CRITICAL_SECTION 0x1420adc10 from the first hang. 0x1420adc48 is the current thread, and the run queues hold time quanta stored as doubles.
    • To yield, it picks the next thread (0x140b06e40), sets it as current, and leaves the critical section. It then calls SignalObjectAndWait(next->[0xc8], self->[0xc8], INFINITE, FALSE): it wakes the next thread's event and sleeps on its own.
    • In this hang the baton is lost. Every thread in the scheduler is asleep on its own event, and none of them will signal another.
  • The crash address from the first hang, 0x140b07004, is in 0x140b06f00, the run-queue update called just before the pick. The instruction there is an ordinary incl into .data, not an int 0x2c. UNKNOWN why Wine reported STATUS_ASSERTION_FAILURE there. The first hang had the main thread holding 0x1420adc10 for about 90 s while inside this code, so that hang was the scheduler stuck inside its own lock.
  • Wine here uses ntsync: /dev/ntsync exists (crw-rw-rw-), and every waiting thread's wchan is ntsync_schedule. Fedora's ntdll implements NtSignalAndWaitForSingleObject as inproc_signal_and_wait, and the ntsync driver has no combined signal-and-wait operation. So on this setup the signal and the wait are two separate steps, whereas on Windows they happen as one. UNVERIFIED that this loses the baton, since an event that is signalled early should stay signalled.

Next test: the same use with ntsync unavailable (/dev/ntsync mode 000 until reboot), so wineserver does the waits itself. If hours of playback pass without a hang, while it hangs within minutes with ntsync, that points at ntsync.

**Second hang, 2026-10-29 07:37:52** (same session pattern: playing music, then the authorize dialog). This time there was no crash and no critical-section timeout. iTunes stayed up with no CPU use. I ran `winedbg` with `attach 0x20` / `bt all` / `detach`; the full dump is on the desktop, not committed. - **Three threads are blocked in the same iTunes routine**: `0024` (main), `03c8` and `03d0`. Each is in `itunes+0xb07264`, the return address of a `SignalObjectAndWait(signal=?, wait=<its own handle>, INFINITE, FALSE)` call. The wait handles are `0xE70`, `0x130` and `0x5B4`. All three are reached through `itunes+0xb077ae`. - **What the routine is** (objdump of `iTunes.exe` 12.13.11.1, `0x140b070f0`): a user-mode thread scheduler that passes a baton between threads. - Its state sits at `0x1420adc00`: magic `'lock'` (`0x6c6f636b`), owner tid, recursion count, then the CRITICAL_SECTION `0x1420adc10` from the first hang. `0x1420adc48` is the current thread, and the run queues hold time quanta stored as doubles. - To yield, it picks the next thread (`0x140b06e40`), sets it as current, and leaves the critical section. It then calls `SignalObjectAndWait(next->[0xc8], self->[0xc8], INFINITE, FALSE)`: it wakes the next thread's event and sleeps on its own. - **In this hang the baton is lost.** Every thread in the scheduler is asleep on its own event, and none of them will signal another. - The crash address from the first hang, `0x140b07004`, is in `0x140b06f00`, the run-queue update called just before the pick. The instruction there is an ordinary `incl` into `.data`, not an `int 0x2c`. UNKNOWN why Wine reported STATUS_ASSERTION_FAILURE there. The first hang had the main thread **holding** `0x1420adc10` for about 90 s while inside this code, so that hang was the scheduler stuck inside its own lock. - **Wine here uses ntsync**: `/dev/ntsync` exists (`crw-rw-rw-`), and every waiting thread's `wchan` is `ntsync_schedule`. Fedora's ntdll implements `NtSignalAndWaitForSingleObject` as `inproc_signal_and_wait`, and the ntsync driver has no combined signal-and-wait operation. So on this setup the signal and the wait are two separate steps, whereas on Windows they happen as one. UNVERIFIED that this loses the baton, since an event that is signalled early should stay signalled. **Next test:** the same use with ntsync unavailable (`/dev/ntsync` mode 000 until reboot), so wineserver does the waits itself. If hours of playback pass without a hang, while it hangs within minutes with ntsync, that points at ntsync.
Author
Owner

With ntsync off, authorization works (2026-10-29 07:46).

  • At 07:43 the operator ran chmod 000 /dev/ntsync (it comes back at reboot), and iTunes was restarted after wineserver -k. Its threads now wait in anon_pipe_read and futex_do_wait, never ntsync_schedule, and wineserver holds no /dev/ntsync fd.
  • Authorize This Computer, which had failed twice with ntsync on ("There was an error connecting to the Apple Account server"), now succeeds. SC Info.sidb went from 0 bytes to 3365, and SC Info.sidd (200356 bytes) appeared, both at 07:46:19.
  • UNKNOWN for now: whether playback still hangs without ntsync. That needs hours of normal use, since with ntsync it hung twice in about 15 minutes.

If it holds, the fix in install.sh is to keep this prefix off ntsync. It also needs a way to do that without root: Fedora's Wine has no switch for it (strings ntdll.so shows no sync env var), so this is still open.

**With ntsync off, authorization works** (2026-10-29 07:46). - At 07:43 the operator ran `chmod 000 /dev/ntsync` (it comes back at reboot), and iTunes was restarted after `wineserver -k`. Its threads now wait in `anon_pipe_read` and `futex_do_wait`, never `ntsync_schedule`, and wineserver holds no `/dev/ntsync` fd. - *Authorize This Computer*, which had failed twice with ntsync on ("There was an error connecting to the Apple Account server"), now succeeds. `SC Info.sidb` went from 0 bytes to 3365, and `SC Info.sidd` (200356 bytes) appeared, both at 07:46:19. - UNKNOWN for now: whether playback still hangs without ntsync. That needs hours of normal use, since with ntsync it hung twice in about 15 minutes. If it holds, the fix in `install.sh` is to keep this prefix off ntsync. It also needs a way to do that without root: Fedora's Wine has no switch for it (`strings ntdll.so` shows no sync env var), so this is still open.
Sign in to join this conversation.
No description provided.