[PATCH wayland v3] client: Introduce proxy wrappers
Jonas Ådahl
jadahl at gmail.com
Thu Apr 28 07:31:31 UTC 2016
Using the libwayland-client client API with multiple threads with
thread local queues are prone to race conditions.
The problem is that one thread can read and queue events after another
thread creates a proxy but before it sets the queue.
This may result in the event to the proxy being silently dropped, or
potentially dispatched on the wrong thread had the creating thread set
the implementation before setting the queue.
This patch introduces API to solve this case by introducing "proxy
wrappers". In short, a proxy wrapper is a wl_proxy struct that will
never itself proxy any events, but may be used by the client to set a
queue, and use it instead of the original proxy when sending requests
that creates new proxies. When sending requests, the wrapper will
work in the same way as the normal proxy object, but the proxy created
by sending a request (for example wl_display.sync) will inherit to the
same proxy queue as the wrapper.
https://bugs.freedesktop.org/show_bug.cgi?id=91273
Signed-off-by: Jonas Ådahl <jadahl at gmail.com>
---
Changes since v2:
- Reworded the commit message and added bugzilla link
- Changed some wl_log("warning: ..."); return; to wl_abort(...);
- Made wl_proxy_create_wrapper() set errno on error (including EINVAL)
- Made it allowed to create a wrapper from a wrapper - this was an arbitrary
restriction, and I believe it makes sense to allow it
- Changed flags == ... to flags & ...
- Documentation fixes
Jonas
src/wayland-client-core.h | 6 +++
src/wayland-client.c | 127 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 132 insertions(+), 1 deletion(-)
diff --git a/src/wayland-client-core.h b/src/wayland-client-core.h
index 91f7e7b..b1d6515 100644
--- a/src/wayland-client-core.h
+++ b/src/wayland-client-core.h
@@ -132,6 +132,12 @@ struct wl_proxy *
wl_proxy_create(struct wl_proxy *factory,
const struct wl_interface *interface);
+void *
+wl_proxy_create_wrapper(void *proxy);
+
+void
+wl_proxy_wrapper_destroy(void *proxy_wrapper);
+
struct wl_proxy *
wl_proxy_marshal_constructor(struct wl_proxy *proxy,
uint32_t opcode,
diff --git a/src/wayland-client.c b/src/wayland-client.c
index 0c2ab32..3aad9d0 100644
--- a/src/wayland-client.c
+++ b/src/wayland-client.c
@@ -51,7 +51,8 @@
enum wl_proxy_flag {
WL_PROXY_FLAG_ID_DELETED = (1 << 0),
- WL_PROXY_FLAG_DESTROYED = (1 << 1)
+ WL_PROXY_FLAG_DESTROYED = (1 << 1),
+ WL_PROXY_FLAG_WRAPPER = (1 << 2),
};
struct wl_proxy {
@@ -425,6 +426,8 @@ proxy_destroy(struct wl_proxy *proxy)
*
* \param proxy The proxy to be destroyed
*
+ * \c proxy must not be a proxy wrapper.
+ *
* \memberof wl_proxy
*/
WL_EXPORT void
@@ -432,6 +435,9 @@ wl_proxy_destroy(struct wl_proxy *proxy)
{
struct wl_display *display = proxy->display;
+ if (proxy->flags & WL_PROXY_FLAG_WRAPPER)
+ wl_abort("Tried to destroy wrapper with wl_proxy_destroy()\n");
+
pthread_mutex_lock(&display->mutex);
proxy_destroy(proxy);
pthread_mutex_unlock(&display->mutex);
@@ -452,12 +458,19 @@ wl_proxy_destroy(struct wl_proxy *proxy)
* \c n, \c implementation[n] should point to the handler of \c n for
* the given object.
*
+ * \c proxy must not be a proxy wrapper.
+ *
* \memberof wl_proxy
*/
WL_EXPORT int
wl_proxy_add_listener(struct wl_proxy *proxy,
void (**implementation)(void), void *data)
{
+ if (proxy->flags & WL_PROXY_FLAG_WRAPPER) {
+ wl_log("proxy %p is a wrapper\n", proxy);
+ return -1;
+ }
+
if (proxy->object.implementation || proxy->dispatcher) {
wl_log("proxy %p already has listener\n", proxy);
return -1;
@@ -504,6 +517,8 @@ wl_proxy_get_listener(struct wl_proxy *proxy)
* The exact details of dispatcher_data depend on the dispatcher used. This
* function is intended to be used by language bindings, not user code.
*
+ * \c proxy must not be a proxy wrapper.
+ *
* \memberof wl_proxy
*/
WL_EXPORT int
@@ -511,6 +526,11 @@ wl_proxy_add_dispatcher(struct wl_proxy *proxy,
wl_dispatcher_func_t dispatcher,
const void *implementation, void *data)
{
+ if (proxy->flags & WL_PROXY_FLAG_WRAPPER) {
+ wl_log("proxy %p is a wrapper\n", proxy);
+ return -1;
+ }
+
if (proxy->object.implementation || proxy->dispatcher) {
wl_log("proxy %p already has listener\n", proxy);
return -1;
@@ -1952,6 +1972,111 @@ wl_proxy_set_queue(struct wl_proxy *proxy, struct wl_event_queue *queue)
proxy->queue = &proxy->display->default_queue;
}
+/** Create a proxy wrapper for making queue assignments thread-safe
+ *
+ * \param proxy The proxy object to be wrapped
+ * \return A proxy wrapper for the given proxy or NULL on failure
+ *
+ * A proxy wrapper is type of 'struct wl_proxy' instance that can be used when
+ * sending requests instead of using the original proxy. A proxy wrapper does
+ * not have an implementation or dispatcher, and events received on the
+ * object is still emitted on the original proxy. Trying to set an
+ * implementation or dispatcher will have no effect but result in a warning
+ * being logged.
+ *
+ * Setting the proxy queue of the proxy wrapper will make new objects created
+ * using the proxy wrapper use the set proxy queue.
+ * Even though there is no implementation nor dispatcher, the proxy queue can
+ * be changed. This will affect the default queue of new objects created by
+ * requests sent via the proxy wrapper.
+ *
+ * A proxy wrapper can only be destroyed using wl_proxy_wrapper_destroy().
+ *
+ * A proxy wrapper must be destroyed before the proxy it was created from.
+ *
+ * Creating a proxy wrapper from a proxy that has either been destroyed or
+ * removed will fail, errno will be set to EINVAL and NULL will be returned.
+ *
+ * If a user reads and dispatches events on more than one thread, it is
+ * necessary to use a proxy wrapper when sending requests on objects when the
+ * intention is that a newly created proxy is to use a proxy queue different
+ * from the proxy the request was sent on, as creating the new proxy and then
+ * setting the queue is not thread safe.
+ *
+ * For example, a module that runs using its own proxy queue that needs to
+ * do display roundtrip must wrap the wl_display proxy object before sending
+ * the wl_display.sync request. For example:
+ *
+ * \code
+ *
+ * struct wl_event_queue *queue = ...;
+ * struct wl_display *wrapped_display;
+ * struct wl_callback *callback;
+ *
+ * wrapped_display = wl_proxy_create_wrapper(display);
+ * wl_proxy_set_queue((struct wl_proxy *) wrapped_display, queue);
+ * callback = wl_display_sync(wrapped_display);
+ * wl_proxy_wrapper_destroy(wrapped_display);
+ * wl_callback_add_listener(callback, ...);
+ *
+ * \endcode
+ *
+ * \memberof wl_proxy
+ */
+WL_EXPORT void *
+wl_proxy_create_wrapper(void *proxy)
+{
+ struct wl_proxy *wrapped_proxy = proxy;
+ struct wl_proxy *wrapper;
+
+ wrapper = zalloc(sizeof *wrapper);
+ if (!wrapper)
+ return NULL;
+
+ pthread_mutex_lock(&wrapped_proxy->display->mutex);
+
+ if ((wrapped_proxy->flags & WL_PROXY_FLAG_ID_DELETED) ||
+ (wrapped_proxy->flags & WL_PROXY_FLAG_DESTROYED)) {
+ pthread_mutex_unlock(&wrapped_proxy->display->mutex);
+ wl_log("proxy %p already deleted or destroyed flag: 0x%x\n",
+ wrapped_proxy, wrapped_proxy->flags);
+ free(wrapper);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ wrapper->object.interface = wrapped_proxy->object.interface;
+ wrapper->object.id = wrapped_proxy->object.id;
+ wrapper->version = wrapped_proxy->version;
+ wrapper->display = wrapped_proxy->display;
+ wrapper->queue = wrapped_proxy->queue;
+ wrapper->flags = WL_PROXY_FLAG_WRAPPER;
+ wrapper->refcount = 1;
+
+ pthread_mutex_unlock(&wrapped_proxy->display->mutex);
+
+ return wrapper;
+}
+
+/** Destroy a proxy wrapper
+ * \param proxy_wrapper The proxy wrapper to be destroyed
+ *
+ * \memberof wl_proxy
+ */
+WL_EXPORT void
+wl_proxy_wrapper_destroy(void *proxy_wrapper)
+{
+ struct wl_proxy *wrapper = proxy_wrapper;
+
+ if (!(wrapper->flags & WL_PROXY_FLAG_WRAPPER))
+ wl_abort("Tried to destroy non-wrapper proxy with "
+ "wl_proxy_wrapper_destroy\n");
+
+ assert(wrapper->refcount == 1);
+
+ free(wrapper);
+}
+
WL_EXPORT void
wl_log_set_handler_client(wl_log_func_t handler)
{
--
2.5.5
More information about the wayland-devel
mailing list