Help gathering info for sdl2-compat deadlock regressions

OS: Fedora 42 x86_64
SDL version: sdl2-compat 2.32.56 and SDL3 3.2.12 installed from Fedora’s repos

I’m trying to build EmptyEpsilon, an open-source game that uses its own SeriousProton game engine built on SDL2. Since upgrading to Fedora 42, which replaced SDL2 with sdl2-compat in its packaged libraries, I’ve encountered two audio-related deadlocks that didn’t occur when building on Fedora 41’s packaged SDL2.

If I build and install legacy SDL2 2.32.8 from its source, and then rebuild EmptyEpsilon against that on the same system, these deadlocks don’t occur. So the issue seems to be related to either sdl2-compat or SDL3.

I’d like to file an issue on the sdl2-compat project, but don’t know what information would be useful for that project or whether these issues should be reported against upstream SDL.

Issue details

Both deadlocks appear to occur on SDL audio functions, either SDL_InitAudio or SDL_PauseAudioDevice, but I’m not confident of the root cause.

  • When quitting the game, it deadlocks indefinitely.

    gdb output:

[INFO    ]: Loaded: sfx/button.wav of 0.15 seconds   <-- moment after clicking "Quit" on Main Menu
^C
Thread 1 "EmptyEpsilon" received signal SIGINT, Interrupt.
futex_wait (futex_word=0xa9b420, expected=2, private=0) at ../sysdeps/nptl/futex-internal.h:146
146       int err = lll_futex_timed_wait (futex_word, expected, NULL, private);
(gdb) bt
#0  futex_wait (futex_word=0xa9b420, expected=2, private=0) at ../sysdeps/nptl/futex-internal.h:146
#1  __GI___lll_lock_wait (futex=futex@entry=0xa9b420, private=0) at lowlevellock.c:49
#2  0x00007ffff78827e8 in lll_mutex_lock_optimized (mutex=0xa9b420) at pthread_mutex_lock.c:48
#3  ___pthread_mutex_lock (mutex=0xa9b420) at pthread_mutex_lock.c:128
#4  0x00007ffff720cb4c in SDL_LockMutex_REAL (mutex=<optimized out>)
    at /usr/src/debug/SDL3-3.2.12-1.fc42.x86_64/src/thread/pthread/SDL_sysmutex.c:79
#5  ObtainLogicalAudioDevice (devid=<optimized out>, _device=_device@entry=0x7fffffffcfc0)
    at /usr/src/debug/SDL3-3.2.12-1.fc42.x86_64/src/audio/SDL_audio.c:423
#6  0x00007ffff720d36c in SetLogicalAudioDevicePauseState (devid=<optimized out>, value=1)
    at /usr/src/debug/SDL3-3.2.12-1.fc42.x86_64/src/audio/SDL_audio.c:1844
#7  SDL_PauseAudioDevice_REAL (devid=<optimized out>) at /usr/src/debug/SDL3-3.2.12-1.fc42.x86_64/src/audio/SDL_audio.c:1854
#8  0x0000000000443687 in main (argc=11121696, argv=0x80) at /home/username/git/emptyepsilon-compile/EmptyEpsilon/src/main.cpp:228

strace output while quitting ends with FUTEX_WAIT_PRIVATE before the deadlock:

ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_DESTROY, 0x7ffcb07c45e0) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_I915_GEM_MADVISE, 0x7ffcb07c45e8) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4680) = 0
ioctl(30, DRM_IOCTL_I915_GEM_MADVISE, 0x7ffcb07c45a8) = 0
getpid()                                = 244100
getpid()                                = 244100
ioctl(30, DRM_IOCTL_I915_GEM_EXECBUFFER2, 0x7ffcb07c47d0) = 0
ioctl(30, DRM_IOCTL_I915_GEM_MADVISE, 0x7ffcb07c4768) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_WAIT, 0x7ffcb07c4640) = 0
ioctl(30, DRM_IOCTL_I915_GEM_MADVISE, 0x7ffcb07c4698) = 0
ioctl(30, DRM_IOCTL_SYNCOBJ_CREATE, 0x7ffcb07c47e0) = 0
recvmsg(5, {msg_namelen=0}, 0)          = -1 EAGAIN (Resource temporarily unavailable)
recvmsg(5, {msg_namelen=0}, 0)          = -1 EAGAIN (Resource temporarily unavailable)
poll([{fd=5, events=POLLIN|POLLOUT}], 1, -1) = 1 ([{fd=5, revents=POLLOUT}])
writev(5, [{iov_base="\222\1\22\0004\0 \2C\0 \2Q\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=72}], 1) = 72
futex(0x389ab2d0, FUTEX_WAIT_PRIVATE, 2, NULL

The engine code being invoked calls only SDL_PauseAudioDevice.

  • When launching a second instance of the game on the same system, it deadlocks while initializing the game’s built-in voice chat.

GDB output:

[INFO    ]: Starting...
[New Thread 0x7fffe8ebd6c0 (LWP 98929)]
[New Thread 0x7fffe3fff6c0 (LWP 98930)]
^C
Thread 1 "EmptyEpsilon" received signal SIGINT, Interrupt.
__syscall_cancel_arch () at ../sysdeps/unix/sysv/linux/x86_64/syscall_cancel.S:56
56              ret
(gdb) bt
#0  __syscall_cancel_arch () at ../sysdeps/unix/sysv/linux/x86_64/syscall_cancel.S:56
#1  0x00007ffff787b9da in __internal_syscall_cancel (a1=<optimized out>, a2=<optimized out>, a3=<optimized out>, a4=<optimized out>, 
    a5=a5@entry=0, a6=a6@entry=4294967295, nr=202) at cancellation.c:49
#2  0x00007ffff787c04c in __futex_abstimed_wait_common64 (private=0, futex_word=0x9f8530, expected=<optimized out>, op=<optimized out>, 
    abstime=0x0, cancel=true) at futex-internal.c:57
#3  __futex_abstimed_wait_common (futex_word=futex_word@entry=0x9f8530, expected=<optimized out>, clockid=clockid@entry=0, 
    abstime=abstime@entry=0x0, private=private@entry=0, cancel=cancel@entry=true) at futex-internal.c:87
#4  0x00007ffff787c0af in __GI___futex_abstimed_wait_cancelable64 (futex_word=futex_word@entry=0x9f8530, expected=<optimized out>, 
    clockid=clockid@entry=0, abstime=abstime@entry=0x0, private=private@entry=0) at futex-internal.c:139
#5  0x00007ffff787e71e in __pthread_cond_wait_common (cond=0x9f8510, mutex=0x9f84e8, clockid=0, abstime=0x0) at pthread_cond_wait.c:426
#6  ___pthread_cond_wait (cond=cond@entry=0x9f8510, mutex=mutex@entry=0x9f84e8) at pthread_cond_wait.c:458
#7  0x00007fffe8f35ed8 in pw_thread_loop_wait (loop=0x9f84d0) at ../src/pipewire/thread-loop.c:428
#8  0x00007ffff7349c25 in PIPEWIRE_PREFERRED_Init (impl=<optimized out>)
    at /usr/src/debug/SDL3-3.2.12-1.fc42.x86_64/src/audio/pipewire/SDL_pipewire.c:1332
#9  0x00007ffff720a1a6 in SDL_InitAudio (driver_name=<optimized out>) at /usr/src/debug/SDL3-3.2.12-1.fc42.x86_64/src/audio/SDL_audio.c:995
#10 SDL_InitSubSystem_REAL (flags=<optimized out>) at /usr/src/debug/SDL3-3.2.12-1.fc42.x86_64/src/SDL.c:384
#11 0x00007ffff7f49b24 in InitSubsystemInternal (flags=62001) at /usr/src/debug/sdl2-compat-2.32.56-1.fc42.x86_64/src/sdl2_compat.c:6883
#12 0x0000000000441527 in Engine::Engine () at /home/username/git/emptyepsilon-compile/SeriousProton/src/engine.cpp:120
#13 main (argc=10454320, argv=0x189) at /home/username/git/emptyepsilon-compile/EmptyEpsilon/src/main.cpp:102

The engine code being invoked calls SDL_Init(SDL_INIT_EVERYTHING);

I narrowed this down to the game’s voice chat by commenting out the lines that initalize it. Doing so consistently avoids the hang.

strace output cuts off on another futex wait at the moment of the hang:

...
newfstatat(AT_FDCWD, "/usr/lib64/pipewire-0.3/libpipewire-module-session-manager.so", {st_mode=S_IFREG|0755, st_size=157176, ...}, 0) = 0
openat(AT_FDCWD, "/usr/lib64/pipewire-0.3/libpipewire-module-session-manager.so", O_RDONLY|O_CLOEXEC) = 18
read(18, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
fstat(18, {st_mode=S_IFREG|0755, st_size=157176, ...}) = 0
mmap(NULL, 151560, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 18, 0) = 0x7f6299296000
mmap(0x7f62992b3000, 24576, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 18, 0x1d000) = 0x7f62992b3000
mmap(0x7f62992b9000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 18, 0x23000) = 0x7f62992b9000
mmap(0x7f62992bb000, 8, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f62992bb000
close(18)                               = 0
mprotect(0x7f62992b9000, 8192, PROT_READ) = 0
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 18
connect(18, {sa_family=AF_UNIX, sun_path="/run/user/1000/pipewire-0"}, 28) = 0
epoll_ctl(7, EPOLL_CTL_ADD, 18, {events=EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP, data=0x3d178d30}) = 0
mmap(NULL, 8392704, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f628a33d000
mprotect(0x7f628a33e000, 8388608, PROT_READ|PROT_WRITE) = 0
rt_sigprocmask(SIG_BLOCK, ~[], [], 8)   = 0
clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7f628ab3d990, parent_tid=0x7f628ab3d990, exit_signal=0, stack=0x7f628a33d000, stack_size=0x7fff40, tls=0x7f628ab3d6c0} => {parent_tid=[98995]}, 88) = 98995
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
openat(AT_FDCWD, "/proc/self/task/98995/comm", O_RDWR) = 19
write(19, "SDLPwAudioPlug", 14)         = 14
close(19)                               = 0
futex(0x3d0cd070, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, NULL, FUTEX_BITSET_MATCH_ANY

What I’m looking for

My questions are:

  1. Is this a known issue? I’ve reviewed several sdl2-compat issues on audio and mutexes but I’m not experienced enough to associate these with any reported issue.
  2. Is the above information enough to report an issue? If not, what more can I provide?
  3. To which project would I report the issue: sdl2-compat or sdl?
  4. I don’t have any authority in the EmptyEpsilon project, I’m just a fan trying to build it. Is sdl2-compat considered stable for building SDL2 projects from source? Should I instead build SDL2 and use it to build this project if I want a stable outcome?

I’ve just tested my app using sdl2-compat and I can confirm that I’m seeing an incompatibility as regards audio. Specifically, SDL_BuildAudioCVT() is writing beyond the end of the SDL_AudioCVT structure I am passing to it, as if that structure is larger in sdl2-compat than it is in SDL2 (could be a padding issue I suppose).

1 Like