Lifetimes and Threads
Lifetimes
Proxies
A proxy lives until one of these happens:
| How | What it does |
|---|---|
destroy-proxy |
sends the interface's destructor request if the proxy's version has one, and otherwise destroys the proxy on the client side only |
a destructor request, such as wl-surface-destroy or wl-pointer-release |
tells the compositor, then destroys the proxy |
a destructor event, such as wl_callback's :done |
destroys the proxy after its handler has run, or unwound |
disconnect |
destroys every remaining proxy of the display |
destroy-proxy is the one to reach for. It sends wl_output.release
for a version-3 output and destroys a version-2 output on the client side,
which has no release request. It also destroys wl_registry on the
client side, since that interface has no destructor at all. It does not
send a destructor whose request takes arguments; in the protocols cl-wl
ships, none does.
After that the proxy is dead. proxy-live-p returns NIL, its printed
form says destroyed, and any request on it signals
destroyed-proxy-error. cl-wl clears the proxy's pointer when it
destroys it, so a dead proxy never reaches freed memory, even if it is
kept in a variable. Destroying a dead proxy does nothing.
Events already queued for a proxy that is destroyed before they are delivered are dropped.
Garbage collection does not destroy proxies. A display keeps every
live proxy, so an object you drop without destroying stays alive on both
sides until disconnect. Destroy objects explicitly. Frame callbacks are
the exception that takes care of itself: their :done destroys them.
Destruction order
The protocol defines the order for some objects. For example, an
xdg_toplevel must be destroyed before its xdg_surface, and that
before its wl_surface. Breaking the order is a protocol error, and
destroy-proxy does not reorder anything. As a rule of thumb, destroy
children first:
(dolist (object (list toplevel xdg-surface surface))
(wl:destroy-proxy object))
Displays
disconnect, or leaving with-display, destroys every proxy, closes the
connection and frees the foreign interface tables the display built.
Disconnecting twice is harmless. Afterwards the display object is dead
like a proxy.
A display made by connect-to-fd owns its fd and closes it on
disconnect.
Shared memory
A shared-memory lives until destroy-shared-memory, or the end of
with-shared-memory. Garbage collection does not free it. The compositor
maps the memory itself when a pool is created from it, so pools and
buffers outlive the client's shared-memory. Only writing needs it. Do
not write to the part of the memory a buffer uses between committing the
buffer and its :release event.
File descriptors
An fd received in an event is yours, so close it with close-fd;
read-keymap closes a keymap's fd for you, and receive-text and
send-text the pipe ends they are given. An fd passed in a request is
duplicated by libwayland, so it is still yours to close as well: close the
write end of a pipe once you have passed it. A shared-memory's fd
belongs to it, and destroy-shared-memory closes it.
Borrowed proxies and cursor themes
A proxy from borrow-proxy wraps a wl_proxy that other C code owns. The
owner destroys it: destroy-proxy and disconnect only forget the proxy.
Forget it before the owner destroys the object.
A cursor theme of cl-wl/cursor owns its images' buffers, which are such
borrowed proxies. Destroy the theme with destroy-cursor-theme before
disconnecting its display; never destroy its buffers yourself. See
Cursors.
Raw pointers
The pointer proxy-pointer returns is valid until its proxy is
destroyed or its display disconnected. Anything built on it, such as a
Vulkan surface, must be destroyed first. shared-memory-pointer is valid
until the memory is resized or destroyed, and interface-pointer until
its tables are freed.
Images
cl-wl keeps no foreign memory globally. The interface tables that
libwayland needs are built per display on first use, and freed on
disconnect. Loading cl-wl into an image and saving it with
save-lisp-and-die therefore needs no special care. Do not save an image
with a display open or shared memory mapped.
Threads
A display, and every proxy created on it, may be used by one thread at a
time. That covers requests, handlers, dispatching and destruction. The
one exception is read-events, which may wait on the display while
another thread uses it. display-error and flush may run while
another thread waits in read-events.
Different displays are independent and may be used on different threads.
| Object | May be shared between threads? |
|---|---|
display |
Yes, if every use except read-events holds a lock you provide. |
proxy |
Follows its display. |
global-follower |
Follows its display. |
shared-memory |
One thread at a time. It belongs to no display. |
interface-tables |
One thread at a time. |
| Event handlers | Run in the thread that dispatches. |
Interface and enum descriptions (message, argument, enum-value, …) |
Yes. They are read-only once bindings load. |
| Bitfield keyword lists delivered in events | Read-only and shared; do not modify them. |
The pattern for an event thread is in Event Loops.
Handlers never run inside libwayland's callback. They run in the thread
that called dispatch, dispatch-pending or roundtrip, after
libwayland has returned. They may therefore take locks, signal and
unwind.
Allocation
Events, requests and pixels happen every frame, so in steady state cl-wl allocates nothing to:
- decode and queue an event, since the queue's records are reused once it has grown to its working size;
- call a handler with up to ten arguments;
- send a request that creates no object;
- decode a
fixed, delivered as an immediatesingle-float; - copy pixels with
write-shared-memory; - wait for events, in
dispatchorread-events, even with a timeout of 0 each frame; - find a cursor of a theme, its frame, or its image's buffer again.
The exceptions are values that must be new Lisp objects:
- strings and arrays in events;
- new proxies, from requests that create objects and events that
announce them, such as the
wl_callbackof each frame; - the first occurrence of each bitfield value per display. Its keyword list is cached and reused afterwards, which is why you must not modify it.
The test suite checks this by counting the bytes allocated over many events, requests and writes.