diff --git a/include/io/net-listener.h b/include/io/net-listener.h index 93608f7fe8..fdc5f1c81a 100644 --- a/include/io/net-listener.h +++ b/include/io/net-listener.h @@ -152,6 +152,27 @@ void qio_net_listener_set_client_func(QIONetListener *listener, gpointer data, GDestroyNotify notify); +/** + * qio_net_listener_set_client_aio_func: + * @listener: the network listener object + * @func: the callback function + * @data: opaque data to pass to @func + * @context: AioContext that @func will be bound to; if NULL, this will + * will use qemu_get_aio_context(). + * + * Similar to qio_net_listener_set_client_func_full(), except that the polling + * will be done by an AioContext rather than a GMainContext. + * + * Because AioContext does not (yet) support a clean way to deregister + * a callback from one thread while another thread might be in that + * callback, this function is only safe to call from the thread + * currently associated with @context. + */ +void qio_net_listener_set_client_aio_func(QIONetListener *listener, + QIONetListenerClientFunc func, + void *data, + AioContext *context); + /** * qio_net_listener_wait_client: * @listener: the network listener object diff --git a/io/net-listener.c b/io/net-listener.c index 49399ec926..9410d72da9 100644 --- a/io/net-listener.c +++ b/io/net-listener.c @@ -85,6 +85,17 @@ static gboolean qio_net_listener_channel_func(QIOChannel *ioc, } +static void qio_net_listener_aio_func(void *opaque) +{ + QIONetListenerSource *data = opaque; + + assert(data->io_source == NULL); + assert(data->listener->aio_context != NULL); + qio_net_listener_channel_func(QIO_CHANNEL(data->sioc), G_IO_IN, + data->listener); +} + + int qio_net_listener_open_sync(QIONetListener *listener, SocketAddress *addr, int num, @@ -157,8 +168,26 @@ qio_net_listener_watch(QIONetListener *listener, size_t i, const char *caller) qio_net_listener_channel_func, listener, (GDestroyNotify)object_unref, listener->context); } else { - /* The user passed an AioContext. Not supported yet. */ - g_assert_not_reached(); + /* + * The user passed an AioContext. At this point, + * AioContext lacks a clean way to call a notify function + * to release a final reference after any callback is + * complete. But we asserted earlier that the async + * callback is changed only from the thread associated + * with aio_context, which means no other thread is in the + * middle of running the callback when we are changing the + * refcount on listener here. Therefore, a single + * reference here is sufficient to ensure listener is not + * finalized during the callback. + */ + assert(listener->context == NULL); + if (i == 0) { + object_ref(OBJECT(listener)); + } + qio_channel_set_aio_fd_handler( + QIO_CHANNEL(listener->source[i]->sioc), + listener->aio_context, qio_net_listener_aio_func, + NULL, NULL, listener->source[i]); } } } @@ -184,7 +213,13 @@ qio_net_listener_unwatch(QIONetListener *listener, const char *caller) listener->source[i]->io_source = NULL; } } else { - g_assert_not_reached(); + assert(listener->context == NULL); + qio_channel_set_aio_fd_handler( + QIO_CHANNEL(listener->source[i]->sioc), + listener->aio_context, NULL, NULL, NULL, NULL); + if (i == listener->nsioc - 1) { + object_unref(OBJECT(listener)); + } } } } @@ -259,6 +294,31 @@ void qio_net_listener_set_client_func(QIONetListener *listener, notify, NULL, NULL); } +void qio_net_listener_set_client_aio_func(QIONetListener *listener, + QIONetListenerClientFunc func, + void *data, + AioContext *context) +{ + if (!context) { + assert(qemu_in_main_thread()); + context = qemu_get_aio_context(); + } else { + /* + * TODO: The API was intentionally designed to allow a caller + * to pass an alternative AioContext for future expansion; + * however, actually implementating that is not possible + * without notify callbacks wired into AioContext similar to + * how they work in GSource. So for now, this code hard-codes + * the knowledge that the only client needing AioContext is + * the NBD server, which uses the global context and does not + * suffer from cross-thread safety issues. + */ + g_assert_not_reached(); + } + qio_net_listener_set_client_func_internal(listener, func, data, + NULL, NULL, context); +} + struct QIONetListenerClientWaitData { QIOChannelSocket *sioc; GMainLoop *loop;