Better handle X server out-of-memory situations

* compositor.h (struct _RenderFuncs): Add function `set_client'.
(struct _ClientErrorData): New struct.
* icon_surface.c (XLGetIconSurface): Attach client to render
target.
* picture_renderer.c (struct _BackBuffer): Keep track of how
many pixels were allocated.
(struct _PictureTarget): Keep track of the client data.
(SetClient): New function.
(FreeBackBuffer, FreeBackBuffers, CreateBackBuffer)
(SetStandardEventMask, NoteTargetSize, DestroyRenderTarget)
(picture_render_funcs): Keep track of the number of pixels
allocated on behalf of each client.
* renderer.c (RenderSetClient): New function.
* run.c (RunStep): Disconnect clients pending disconnect.
* shm.c (InitRender): Export the render error base.
* test.c (GetTestSurface):
* xdg_surface.c (XLGetXdgSurface): Attach the client to the
render target.
* xerror.c (enum _ClientMemoryCategory): New enum.
(ReleaseClientData, HandleClientDestroy, ErrorDataForClient)
(CategorizeClients, SavePendingDisconnectClient)
(DisconnectOneClient, ProcessPendingDisconnectClients)
(HandleBadAlloc): New functions.
(ErrorHandler): Handle BadAlloc errors by disconnecting pixmap
hogs.
This commit is contained in:
hujianwei 2022-11-08 03:37:37 +00:00
parent 801eee5464
commit f4194133f9
9 changed files with 497 additions and 15 deletions

View file

@ -325,6 +325,10 @@ struct _RenderFuncs
/* Create a rendering target for the given pixmap. */
RenderTarget (*target_from_pixmap) (Pixmap);
/* Set the client associated with the target. This allows some
extra pixmap memory allocation tracking to be done. */
void (*set_client) (RenderTarget, struct wl_client *);
/* Set the standard event mask of the target. */
void (*set_standard_event_mask) (RenderTarget, unsigned long);
@ -537,6 +541,7 @@ extern void InitRenderers (void);
extern RenderTarget RenderTargetFromWindow (Window, unsigned long);
extern RenderTarget RenderTargetFromPixmap (Pixmap);
extern void RenderSetClient (RenderTarget, struct wl_client *);
extern void RenderSetStandardEventMask (RenderTarget, unsigned long);
extern void RenderNoteTargetSize (RenderTarget, int, int);
extern Picture RenderPictureFromTarget (RenderTarget);
@ -742,6 +747,8 @@ extern void ExtBufferDestroy (ExtBuffer *);
/* Defined in shm.c. */
extern int render_first_error;
extern void XLInitShm (void);
/* Defined in subcompositor.c. */
@ -1408,6 +1415,29 @@ extern void XLInitPopups (void);
/* Defined in xerror.c. */
typedef struct _ClientErrorData ClientErrorData;
struct _ClientErrorData
{
/* The wl_listener used to hang this data from. */
struct wl_listener listener;
/* How many pixels of pixmap data this client has allocated. This
data is guaranteed to be present as long as the client still
exists. */
uint64_t n_pixels;
/* The number of references to this error data. The problem with is
that resources are destroyed after the client error data, so
without reference counting the client data will become invalid by
the time the destroy listener is called. */
int refcount;
};
extern ClientErrorData *ErrorDataForClient (struct wl_client *);
extern void ReleaseClientData (ClientErrorData *);
extern void ProcessPendingDisconnectClients (void);
extern void InitXErrors (void);
extern void CatchXErrors (void);
extern Bool UncatchXErrors (XErrorEvent *);

View file

@ -416,6 +416,11 @@ XLGetIconSurface (Surface *surface)
/* Create a target associated with the window. */
role->target = RenderTargetFromWindow (role->window, None);
/* Set the client. */
if (surface->resource)
RenderSetClient (role->target,
wl_resource_get_client (surface->resource));
/* For simplicity reasons we do not handle idle notifications
asynchronously. */
RenderSetNeedWaitForIdle (role->target);

View file

@ -183,6 +183,9 @@ struct _PresentCompletionCallback
struct _BackBuffer
{
/* How many pixels were allocated. */
uint64_t n_pixels;
/* The picture of this back buffer. High bit means the back buffer
is busy. */
Picture picture;
@ -228,6 +231,10 @@ struct _PictureTarget
/* Two back buffers. */
BackBuffer *back_buffers[2];
/* Structure used to allocate the amount of pixmap allocated on
behalf of a client. */
ClientErrorData *client;
/* Presentation event context. */
XID presentation_event_context;
@ -699,7 +706,7 @@ HandleActivityEvent (uint64_t counter)
static void
FreeBackBuffer (BackBuffer *buffer)
FreeBackBuffer (PictureTarget *target, BackBuffer *buffer)
{
XRenderFreePicture (compositor.display, (buffer->picture
& ~BufferSync
@ -707,6 +714,15 @@ FreeBackBuffer (BackBuffer *buffer)
XFreePixmap (compositor.display, (buffer->pixmap
& ~BufferSync));
FenceRelease (buffer->idle_fence);
/* Subtract the amount of pixels allocated from the target. */
if (target->client
&& IntSubtractWrapv (target->client->n_pixels,
buffer->n_pixels,
&target->client->n_pixels))
/* Handle overflow by just setting n_pixels to 0. */
target->client->n_pixels = 0;
XLFree (buffer);
}
@ -718,7 +734,7 @@ FreeBackBuffers (PictureTarget *target)
for (i = 0; i < ArrayElements (target->back_buffers); ++i)
{
if (target->back_buffers[i])
FreeBackBuffer (target->back_buffers[i]);
FreeBackBuffer (target, target->back_buffers[i]);
}
/* Also clear target->picture if it is a window target. */
@ -753,6 +769,18 @@ CreateBackBuffer (PictureTarget *target)
/* The target is no longer freshly presented. */
target->flags &= ~JustPresented;
/* Calculate how many pixels would be allocated and add it to the
target data. */
if (IntMultiplyWrapv (target->width, target->height,
&buffer->n_pixels))
buffer->n_pixels = UINT64_MAX;
if (target->client
&& IntAddWrapv (target->client->n_pixels, buffer->n_pixels,
&target->client->n_pixels))
target->client->n_pixels = UINT64_MAX;
return buffer;
}
@ -1253,6 +1281,40 @@ TargetFromWindow (Window window, unsigned long event_mask)
return TargetFromDrawable (window, window, event_mask);
}
static void
SetClient (RenderTarget target, struct wl_client *client)
{
PictureTarget *picture_target;
ClientErrorData *data;
uint64_t pixels;
picture_target = target.pointer;
/* Release the client data if some is already attached. */
if (picture_target->client)
{
if (picture_target->client
&& IntMultiplyWrapv (picture_target->width,
picture_target->height,
&pixels)
&& IntSubtractWrapv (picture_target->client->n_pixels,
pixels,
&picture_target->client->n_pixels))
picture_target->client->n_pixels = 0;
ReleaseClientData (picture_target->client);
}
picture_target->client = NULL;
if (!client)
return;
/* Retain the client data. */
data = ErrorDataForClient (client);
picture_target->client = data;
data->refcount++;
}
static void
SetStandardEventMask (RenderTarget target, unsigned long standard_event_mask)
{
@ -1269,13 +1331,34 @@ static void
NoteTargetSize (RenderTarget target, int width, int height)
{
PictureTarget *pict_target;
uint64_t pixels;
pict_target = target.pointer;
if (width != pict_target->width
|| height != pict_target->height)
/* Recreate all the back buffers for the new target size. */
FreeBackBuffers (pict_target);
{
/* Recreate all the back buffers for the new target size. */
FreeBackBuffers (pict_target);
/* First, remove existing pixels from the client. */
if (pict_target->client
&& IntMultiplyWrapv (pict_target->width,
pict_target->height,
&pixels)
&& IntSubtractWrapv (pict_target->client->n_pixels,
pixels,
&pict_target->client->n_pixels))
pict_target->client->n_pixels = 0;
/* Next, add the new width and height to the client. */
if (pict_target->client
&& IntMultiplyWrapv (width, height, &pixels)
&& IntAddWrapv (pict_target->client->n_pixels,
pixels,
&pict_target->client->n_pixels))
pict_target->client->n_pixels = UINT64_MAX;
}
pict_target->width = width;
pict_target->height = height;
@ -1314,6 +1397,7 @@ DestroyRenderTarget (RenderTarget target)
PresentRecord *record, *last;
BufferActivityRecord *activity_record, *activity_last;
IdleCallback *idle, *idle_last;
uint64_t pixels;
pict_target = target.pointer;
@ -1370,6 +1454,22 @@ DestroyRenderTarget (RenderTarget target)
XRenderFreePicture (compositor.display,
pict_target->picture);
/* Dereference the client data if it is set. Also, remove the
pixels recorded. */
if (pict_target->client)
{
if (pict_target->client
&& IntMultiplyWrapv (pict_target->width,
pict_target->height,
&pixels)
&& IntSubtractWrapv (pict_target->client->n_pixels,
pixels,
&pict_target->client->n_pixels))
pict_target->client->n_pixels = 0;
ReleaseClientData (pict_target->client);
}
XFree (pict_target);
}
@ -1899,6 +1999,7 @@ static RenderFuncs picture_render_funcs =
.init_render_funcs = InitRenderFuncs,
.target_from_window = TargetFromWindow,
.target_from_pixmap = TargetFromPixmap,
.set_client = SetClient,
.set_standard_event_mask = SetStandardEventMask,
.note_target_size = NoteTargetSize,
.picture_from_target = PictureFromTarget,

View file

@ -80,6 +80,15 @@ RenderTargetFromPixmap (Pixmap pixmap)
return render_funcs.target_from_pixmap (pixmap);
}
void
RenderSetClient (RenderTarget target, struct wl_client *client)
{
if (!render_funcs.set_client)
return;
render_funcs.set_client (target, client);
}
void
RenderSetStandardEventMask (RenderTarget target,
unsigned long standard_event_mask)

12
run.c
View file

@ -215,6 +215,10 @@ RunStep (void)
/* Drain complete selection transfers. */
FinishTransfers ();
/* Disconnect clients that have experienced out-of-memory
errors. */
ProcessPendingDisconnectClients ();
/* FinishTransfers can potentially send events to Wayland clients
and make X requests. Flush after it is called. */
XFlush (compositor.display);
@ -273,6 +277,10 @@ RunStep (void)
wl_display_flush_clients (compositor.wl_display);
}
/* Disconnect clients that have experienced out-of-memory
errors. */
ProcessPendingDisconnectClients ();
rc = ProcessPoll (fds, 2 + i, &timeout);
if (rc > 0)
@ -295,6 +303,10 @@ RunStep (void)
pollfds[j]->data, pollfds[j]);
}
}
/* Disconnect clients that have experienced out-of-memory
errors. */
ProcessPendingDisconnectClients ();
}
void __attribute__ ((noreturn))

7
shm.c
View file

@ -77,6 +77,9 @@ typedef struct _Buffer
/* The shared memory global. */
static struct wl_global *global_shm;
/* The error base of the Render extension. */
int render_first_error;
static void
DereferencePool (Pool *pool)
{
@ -534,10 +537,10 @@ HandleBind (struct wl_client *client, void *data,
static void
InitRender (void)
{
int major, minor, base, dummy;
int major, minor, base;
if (!XRenderQueryExtension (compositor.display,
&base, &dummy))
&base, &render_first_error))
{
fprintf (stderr, "XRender is not supported by this X server\n");
exit (1);

3
test.c
View file

@ -457,6 +457,9 @@ GetTestSurface (struct wl_client *client, struct wl_resource *resource,
test->subcompositor = MakeSubcompositor ();
test->target = RenderTargetFromWindow (test->window, DefaultEventMask);
/* Set the client. */
RenderSetClient (test->target, client);
/* And a buffer release helper. */
test->release_helper = MakeBufferReleaseHelper (AllBuffersReleased,
test);

View file

@ -1475,6 +1475,9 @@ XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource,
role->release_helper = MakeBufferReleaseHelper (AllBuffersReleased,
role);
/* Set the client. */
RenderSetClient (role->target, client);
role->subcompositor = MakeSubcompositor ();
role->clock = XLMakeFrameClockForWindow (role->window);
XLFrameClockSetFreezeCallback (role->clock, HandleFreeze, role);

334
xerror.c
View file

@ -24,6 +24,19 @@ along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
#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
@ -34,18 +47,28 @@ along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
/* First request from which errors should be caught. -1 if we are not
currently catching errors. */
static unsigned long first_error_req;
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)
{
@ -78,10 +101,290 @@ UncatchXErrors (XErrorEvent *event)
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;
}
fprintf (stderr,
"%p %"PRIu64" %"PRIu64" %u\n", client, total, data->n_pixels,
categories[i - 1]);
}
}
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)
@ -92,11 +395,6 @@ ErrorHandler (Display *display, XErrorEvent *event)
return 0;
}
#if 0
if (XLHandleErrorForDmabuf (event))
return 0;
#endif
if (HandleErrorForPictureRenderer (event))
return 0;
@ -106,6 +404,24 @@ ErrorHandler (Display *display, XErrorEvent *event)
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);