A Weird Imagination

SDL screensaver hangs on exit

The problem

I was modifying a screensaver written using SDL and noticed that sometimes there were many instances of it left running, even after unlocking the screen. Another bug was causing the screensaver to use 100% CPU, resulting in it using up all of my processing power just for a simple screensaver.

The solution

Make the program exit immediately when it receives a SIGTERM signal by including the function

void exitImmediately(int sig) {
    abort();
}

and making the SIGTERM signal handler call it:

signal(SIGTERM, exitImmediately);

The details

What's a screensaver?

A screensaver for XScreenSaver is a program that draws to an X window. The input is managed by XScreenSaver. All the screensaver does is draw to the window it is given and stop when it is told to through a SIGTERM signal (the normal way to request that a program exits).

The actual drawing can be done through any API as long as it has a way to attach to an existing X window. In our case, the screensaver uses SDL.

Interrogating hung processes

gdb can attach to running processes:

$ gdb -p $(pidof noflipqlo)
...

Debugging symbols

The ... is skipping over a lot of initial loading messages from gdb. You may want to pay attention to the ones about reading symbols for seeing which packages are missing debugging symbols:

Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/x86_64-linux-gnu/libgcc_s.so.1
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.19.so...done.
done.
Loaded symbols for /lib/x86_64-linux-gnu/libc.so.6

In Debian/Ubuntu many packages have -dbg variants of the -dev packages with the debugging symbols. You'll probably want the ones for glibc and SDL, at least:

$ sudo apt-get install libc6-dbg libsdl1.2-dbg libsdl2-dbg

Showing the backtrace

Once done attaching to the process, you can ask for a backtrace (also bt or where) to find out what the program is currently doing:

(gdb) backtrace
#0  0x00007fb6322eb6fd in nanosleep () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007fb63340ec55 in SDL_Delay_REAL (ms=<optimized out>)
    at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/timer/unix/SDL_systimer.c:197
#2  0x0000000000402b57 in main ()

Interrogating hung screensavers

Given that the process we want to attach to is a screensaver and the screensaver naturally prevents normal interaction with the computer, I ran gdb in a terminal over SSH from another computer.

Where was it stuck?

The backtrace above was in a good state. Here's what the backtrace looks like when SDL_Quit() is called and the program hangs:1

(gdb) bt
#0  0x00007ff498b1a50d in poll () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007ff496416252 in ?? () from /usr/lib/x86_64-linux-gnu/libxcb.so.1
#2  0x00007ff496417ddf in xcb_wait_for_event ()
   from /usr/lib/x86_64-linux-gnu/libxcb.so.1
#3  0x00007ff499853008 in _XReadEvents ()
   from /usr/lib/x86_64-linux-gnu/libX11.so.6
#4  0x00007ff49983a9ea in XIfEvent ()
   from /usr/lib/x86_64-linux-gnu/libX11.so.6
#5  0x00007ff499c0ef61 in X11_HideWindow (_this=0xe33bd0, window=0xeb3640)
    at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/x11/SDL_x11window.c:943
#6  0x00007ff499bfe8d0 in SDL_HideWindow_REAL (window=0xeb3640)
    at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/SDL_video.c:1801
#7  0x00007ff499c00408 in SDL_DestroyWindow_REAL (window=0xeb3640)
    at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/SDL_video.c:2228
#8  0x00007ff499c006f5 in SDL_VideoQuit_REAL ()
    at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/video/SDL_video.c:2350
#9  0x00007ff499b6b715 in SDL_QuitSubSystem_REAL (flags=flags@entry=29233)
    at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/SDL.c:297
#10 0x00007ff499b6b848 in SDL_Quit_REAL ()
    at /tmp/buildd/libsdl2-2.0.2+dfsg1/src/SDL.c:357

It's getting stuck inside a call to SDL_DestroyWindow() and no windows were created by our program, just the one we attached to. This suggests that SDL_DestroyWindow() doesn't work properly in that case, so we should avoid calling it, which means avoiding calling SDL_Quit().

Why not exit()?

abort() does less when exiting than exit() (the newer quick_exit() would have also worked). Most importantly, the screensaver uses atexit() to call SDL's cleanup function SQL_Quit() whenever exit() is called:

atexit(SDL_Quit);

Why many processes?

This explains why there would be one hung screensaver process, but not why there would be a lot of them. The reason for that is that XScreenSaver regularly kills (with a SIGTERM) and restarts the screensaver process. The intention is to rotate to a different screensaver, but even if there's only one screensaver enabled, it does it anyway and just starts up the same one again.


  1. I actually didn't have a broken version of the code around, so as a workaround, I just attached to the screensaver and used the call command and then hit Ctrl+c when it hung in order to generate that backtrace:

    (gdb) call SDL_Quit()
    ^C
    Program received signal SIGINT, Interrupt.
    0x00007ff498b1a50d in poll () at ../sysdeps/unix/syscall-template.S:81
    81  ../sysdeps/unix/syscall-template.S: No such file or directory.
    The program being debugged was signaled while in a function called from GDB.
    GDB remains in the frame where the signal was received.
    To change this behavior use "set unwindonsignal on".
    Evaluation of the expression containing the function
    (SDL_Quit) will be abandoned.
    When the function is done executing, GDB will silently stop.
    (gdb) bt
    ...
    

Comments

Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.

There are no comments yet.