12to11/xerror.c
hujianwei 30f22613b8 Fix some minor problems
* seat.c (DispatchEntryExit): Understand all kinds of entry and
exit events instead of just non-grab ones.
* subcompositor.c (SubcompositorComposite1): Remove redundant
assignment.
* xerror.c (CategorizeClients): Add new assertion.

* tests/select_test.c (verify_sample_text):
(verify_sample_text_multiple): Fix typos.
2022-11-13 13:45:11 +00:00

438 lines
12 KiB
C

/* Wayland compositor running on top of an X server.
Copyright (C) 2022 to various contributors.
This file is part of 12to11.
12to11 is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
12to11 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <stdlib.h>
#include "compositor.h"
#include <X11/extensions/XInput.h>
typedef enum _ClientMemoryCategory ClientMemoryCategory;
/* See the comment in HandleBadAlloc for the meaning of these
enumerators. */
enum _ClientMemoryCategory
{
MemoryCategoryI,
MemoryCategoryII,
MemoryCategoryIII,
MemoryCategoryIV,
MemoryCategoryV,
};
/* X error handling routines. The entry point into this code is
CatchXErrors, which starts catching X errors in the following code,
and UncatchXErrors, which syncs (if necessary) and saves any
received XErrorEvent into a provided buffer.
This code is not reentrant since it doesn't have to take care of
many complicated scenarios that the Emacs code needs. */
/* First request from which errors should be caught. -1 if we are not
currently catching errors. */
static long long first_error_req;
/* XErrorEvent any error that happens while errors are being caught
will be saved in. */
static XErrorEvent error;
/* True if any error was caught. */
static Bool error_caught;
/* True if any BadAlloc error was encountered. */
static Bool bad_alloc_experienced;
/* The serial of the last BadAlloc request. */
static unsigned long next_bad_alloc_serial;
/* Whether or not program execution is currently inside the bad alloc
handler. */
static Bool inside_bad_alloc_handler;
/* Clients that are pending disconnect. */
static XLList *pending_disconnect_clients;
void
CatchXErrors (void)
{
first_error_req = XNextRequest (compositor.display);
error_caught = False;
}
Bool
UncatchXErrors (XErrorEvent *event)
{
/* Try to avoid syncing to obtain errors if we know none could have
been generated, because either no request has been made, or all
requests have been processed. */
if ((LastKnownRequestProcessed (compositor.display)
!= XNextRequest (compositor.display) - 1)
&& (NextRequest (compositor.display)
> first_error_req))
/* If none of those conditions apply, catch errors now. */
XSync (compositor.display, False);
first_error_req = -1;
if (!event)
return error_caught;
if (!error_caught)
return False;
*event = error;
return True;
}
void
ReleaseClientData (ClientErrorData *data)
{
if (--data->refcount)
return;
XLFree (data);
}
static void
HandleClientDestroy (struct wl_listener *listener, void *data)
{
struct wl_client *client;
/* The client has been destroyed. Remove it from the list of
clients pending destruction. */
client = data;
pending_disconnect_clients
= XLListRemove (pending_disconnect_clients, client);
/* listener is actually the ClientErrorData. */
ReleaseClientData ((ClientErrorData *) listener);
}
ClientErrorData *
ErrorDataForClient (struct wl_client *client)
{
struct wl_listener *listener;
ClientErrorData *data;
listener = wl_client_get_destroy_listener (client,
HandleClientDestroy);
if (listener)
return (ClientErrorData *) listener;
/* Allocate the data and set it as the client's destroy
listener. */
data = XLMalloc (sizeof *data);
data->listener.notify = HandleClientDestroy;
data->n_pixels = 0;
/* Add a reference to the data. */
data->refcount = 1;
wl_client_add_destroy_listener (client, &data->listener);
return data;
}
static void
CategorizeClients (struct wl_list *client_list,
struct wl_client **clients,
ClientMemoryCategory *categories,
ClientMemoryCategory *max_category)
{
struct wl_list *list;
struct wl_client *client;
ClientErrorData *data;
uint64_t total;
int i;
total = 0;
i = 0;
list = client_list;
/* The heuristics here are not intended to handle every out of
memory situation. Rather, they are designed to handle BadAlloc
errors caused by clients recently allocating unreasonable chunks
of memory from the server. */
/* Compute the total number of pixels allocated. */
wl_client_for_each (client, list)
{
data = ErrorDataForClient (client);
if (IntAddWrapv (total, data->n_pixels, &total))
total = UINT64_MAX;
}
list = client_list;
wl_client_for_each (client, list)
{
data = ErrorDataForClient (client);
if (data->n_pixels <= total * 0.05)
{
if (*max_category < MemoryCategoryI)
*max_category = MemoryCategoryI;
categories[i++] = MemoryCategoryI;
clients[i - 1] = client;
}
else if (data->n_pixels <= total * 0.10)
{
if (*max_category < MemoryCategoryII)
*max_category = MemoryCategoryII;
categories[i++] = MemoryCategoryII;
clients[i - 1] = client;
}
else if (data->n_pixels <= total * 0.20)
{
if (*max_category < MemoryCategoryIII)
*max_category = MemoryCategoryIII;
categories[i++] = MemoryCategoryIII;
clients[i - 1] = client;
}
else if (data->n_pixels <= total / 2)
{
if (*max_category < MemoryCategoryIV)
*max_category = MemoryCategoryIV;
categories[i++] = MemoryCategoryIV;
clients[i - 1] = client;
}
else
{
if (*max_category < MemoryCategoryV)
*max_category = MemoryCategoryV;
categories[i++] = MemoryCategoryV;
clients[i - 1] = client;
}
}
XLAssert (i == wl_list_length (client_list));
}
static void
SavePendingDisconnectClient (struct wl_client *client)
{
XLList *list;
/* Never link the same client onto the list twice. */
list = pending_disconnect_clients;
for (; list; list = list->next)
{
if (list->data == client)
return;
}
pending_disconnect_clients
= XLListPrepend (pending_disconnect_clients, client);
}
static void
DisconnectOneClient (void *data)
{
wl_client_post_no_memory (data);
}
void
ProcessPendingDisconnectClients (void)
{
XLList *head;
/* Unlink the list onto head. */
head = pending_disconnect_clients;
pending_disconnect_clients = NULL;
if (head)
XLListFree (head, DisconnectOneClient);
/* Ignore future BadAlloc errors caused by requests generated before
client disconnects were processed. */
next_bad_alloc_serial
= XNextRequest (compositor.display);
}
static void
HandleBadAlloc (XErrorEvent *event)
{
struct wl_client **clients_by_category;
struct wl_list *client_list;
int num_clients, i;
char buf[256];
ClientMemoryCategory *categories, max_category;
if (event->serial < next_bad_alloc_serial)
return;
if (inside_bad_alloc_handler)
/* This function is not reentrant. */
return;
/* Handle a BadAlloc error. For efficiency reasons, errors are not
caught around events (CreatePixmap, CreateWindow, etc) that are
likely to raise BadAlloc errors upon ridiculous requests from
clients do not have errors caught around them.
Upon such an error occuring, clients are sorted by the amount of
pixmap they have allocated. Each client has its own "badness"
score for each pixel of pixmap data allocated on its behalf.
Each client is organized by the percentage of the total badness
it takes up. Those clients are then categorized as follows:
I. Clients occupying between 0 to 5 percent of the total
score.
II. Clients occupying between 6 to 10 percent of the total
score.
III. Clients occupying between 11 to 20 percent of the total
score.
IV. Clients occupying more than 20 percent of the total score.
V. Clients occupying more than 50 percent of the total score.
Finally, all clients falling in the bottom-most category that
exists are disconnected with out-of-memory errors. */
client_list = wl_display_get_client_list (compositor.wl_display);
if (wl_list_empty (client_list))
{
/* If there are no clients, just exit immediately. */
XGetErrorText (compositor.display, event->error_code,
buf, sizeof buf);
fprintf (stderr, "X protocol error: %s on protocol request %d\n",
buf, event->request_code);
exit (70);
}
/* Count the number of clients. */
num_clients = wl_list_length (client_list);
/* Allocate buffers to hold the client data. */
categories
= alloca (sizeof *categories * num_clients);
clients_by_category
= alloca (sizeof *clients_by_category * num_clients);
max_category
= MemoryCategoryI;
/* Organize the clients by category. */
CategorizeClients (client_list, clients_by_category,
categories, &max_category);
/* Ignore future BadAlloc errors caused by requests generated before
this BadAlloc error was processed. */
next_bad_alloc_serial = XNextRequest (compositor.display);
/* Prevent recursive invocations of this error handler when XSync is
called by resource destructors. */
inside_bad_alloc_handler = True;
/* Now disconnect each client fitting that category.
wl_client_post_no_memory can call Xlib functions through resource
destructors, so each client is actually put on a queue that is
drained in RunStep. */
for (i = 0; i < num_clients; ++i)
{
if (categories[i] == max_category)
SavePendingDisconnectClient (clients_by_category[i]);
}
/* At this point, start ignoring all BadDrawable, BadWindow,
BadPixmap and BadPicture errors. Whatever resource was supposed
to be created could not be created, so trying to destroy it as
part of client destruction will fail. */
bad_alloc_experienced = True;
inside_bad_alloc_handler = False;
}
static int
ErrorHandler (Display *display, XErrorEvent *event)
{
char buf[256];
unsigned long next_request;
/* Reset fields that overflowed. */
next_request = XNextRequest (compositor.display);
if (first_error_req != -1
&& next_request < first_error_req)
first_error_req = 0;
if (next_request < next_bad_alloc_serial)
next_bad_alloc_serial = 0;
if (first_error_req != -1
&& event->serial >= first_error_req)
{
error = *event;
error_caught = True;
return 0;
}
if (HandleErrorForPictureRenderer (event))
return 0;
if (event->error_code == (xi_first_error + XI_BadDevice))
/* Various XI requests can result in XI_BadDevice errors if the
device has been removed on the X server, but we have not yet
processed the corresponding hierarchy events. */
return 0;
if (bad_alloc_experienced
/* See the comment at the end of HandleBadAlloc for why this is
done. */
&& (event->error_code == BadDrawable
|| event->error_code == BadWindow
|| event->error_code == BadPixmap
|| event->error_code == (render_first_error + BadPicture)))
return 0;
if (event->error_code == BadAlloc)
{
/* A client may have asked for the protocol translator to
allocate an unreasonable amount of memory. */
HandleBadAlloc (event);
return 0;
}
XGetErrorText (display, event->error_code, buf, sizeof buf);
fprintf (stderr, "X protocol error: %s on protocol request %d\n",
buf, event->request_code);
exit (70);
}
void
InitXErrors (void)
{
first_error_req = -1;
XSetErrorHandler (ErrorHandler);
/* Allow debugging by setting an environment variable. */
if (getenv ("SYNCHRONIZE"))
XSynchronize (compositor.display, True);
}