Search code examples
c++clinuxgtkxlib

Old xlib programs hang the Linux GUI on window resize. Why?


I have noticed, that with the older X programs, when the user start to resize window by dragging its edges, the whole GUI of the OS freezes.

I am testing with glxgears - the gears stop rotating. The same happens with the content update of all other programs - such as the task manager, terminal windows and so on.

After stopping moving the mouse, all activity starts again.

Resizing newer program windows (I mean using GTK or Qt) does not freeze anything.

In the same time, the GUI of the older programs is much more responsive than the new. Only the dragging resize is the problem.

The older programs all use the standard documented way of handling the message queue. Something like the following (more complex, of course):

while (1) {
      XNextEvent(d, &e);
      if (e.type == Expose) {
         XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
         XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));
      }
}

I have tried to eliminate the whole message processing by setting XSetWindowAttributes.event_mask = 0 on main window creation. The events stop flowing at all, but on resizing the empty window, all GUI still freezes.

So, the problem is not (only) on the client side. Although, it can be in the way the client and the server interact. For example it can be because the client does not do something.

So, what the newer toolkits do differently? What to change in the older programs in order to avoid such freezes.


Solution

  • Well, after some research I have found the answer.

    The problem is that the old programs does not use the _NEW_WM_SYNC_REQUEST protocol in order to synchronize their ability to redraw the window content with the rate of the resize events from the window manager.

    Because of this the window manager resizes the window in too high rate and the application can't draw so fast. This way, the window manager effectively loads the X server and provides DoS hanging to the other running applications.

    Of course in this case, the rate of the resize events depends on the window manager, but most of them simply resize the window on every mouse move.

    The _NET_WM_SYNC_REQUEST protocol is aimed to provide information to the window manager when the application finished drawing its window and to stop it from resizing the window before previous resize is processed.

    The implementation is pretty simple.

    At first, the application must include the _NET_WM_SYNC_REQUEST in the WM_PROTOCOLS property of the window.

    Also, the application should provide one (possibly two) synchronization counters (see SYNC X extension or xcb-sync library or the libX variant)

    Then the protocol looks the following way:

    1. Before to resize the window, WM sends to the application ClientMessage with data[0] set to the Atom of _NEW_WM_SYNC_REQUEST string. In the data[2] and data[3] of this event there is an 64 bit number. The application must store this number somewhere.

    2. After processing the following ConfigureNotify and Expose events and having the window surface fully redrawn, it must set the synchronization counter to this 64 bit value.

    3. The window manager checks the value of the counter and after see there its number, knows that it is safe to resize the window again.

    Of course, there are some timeout mechanisms and if the program responds too slow or does not responds at all, the window manager switches to fall-back mode and starts to resize the windows the old way.

    There is another variant of this protocol with two synchronization counters, but IMHO, it aims to solve another programs. With the window resizing, the first version of the protocol works great.