]> code.delx.au - pulseaudio/blobdiff - src/modules/module-zeroconf-publish.c
Use pa_hashmap_remove_and_free() where appropriate
[pulseaudio] / src / modules / module-zeroconf-publish.c
index 34565395fb080948b39322d294a6a9995dba9d41..aa9e89165ccc2adbb4e35ec4873a66f8f52adf5b 100644 (file)
@@ -1,5 +1,3 @@
-/* $Id$ */
-
 /***
   This file is part of PulseAudio.
 
 /***
   This file is part of PulseAudio.
 
@@ -7,7 +5,7 @@
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
-  published by the Free Software Foundation; either version 2 of the
+  published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
   License, or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
 #endif
 
 #include <stdio.h>
 #endif
 
 #include <stdio.h>
-#include <assert.h>
 #include <stdlib.h>
 #include <stdlib.h>
-#include <string.h>
 #include <unistd.h>
 
 #include <avahi-client/client.h>
 #include <avahi-client/publish.h>
 #include <avahi-common/alternative.h>
 #include <avahi-common/error.h>
 #include <unistd.h>
 
 #include <avahi-client/client.h>
 #include <avahi-client/publish.h>
 #include <avahi-common/alternative.h>
 #include <avahi-common/error.h>
+#include <avahi-common/domain.h>
 
 #include <pulse/xmalloc.h>
 #include <pulse/util.h>
 
 #include <pulse/xmalloc.h>
 #include <pulse/util.h>
+#include <pulse/thread-mainloop.h>
 
 
-#include <pulsecore/autoload.h>
+#include <pulsecore/parseaddr.h>
 #include <pulsecore/sink.h>
 #include <pulsecore/source.h>
 #include <pulsecore/native-common.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/log.h>
 #include <pulsecore/sink.h>
 #include <pulsecore/source.h>
 #include <pulsecore/native-common.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/log.h>
-#include <pulsecore/core-subscribe.h>
 #include <pulsecore/dynarray.h>
 #include <pulsecore/modargs.h>
 #include <pulsecore/avahi-wrap.h>
 #include <pulsecore/dynarray.h>
 #include <pulsecore/modargs.h>
 #include <pulsecore/avahi-wrap.h>
-#include <pulsecore/endianmacros.h>
+#include <pulsecore/protocol-native.h>
 
 #include "module-zeroconf-publish-symdef.h"
 
 
 #include "module-zeroconf-publish-symdef.h"
 
-PA_MODULE_AUTHOR("Lennart Poettering")
-PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
-PA_MODULE_VERSION(PACKAGE_VERSION)
-PA_MODULE_USAGE("port=<IP port number>")
+PA_MODULE_AUTHOR("Lennart Poettering");
+PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
 
 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
 
 #define SERVICE_TYPE_SINK "_pulse-sink._tcp"
 #define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
 #define SERVICE_TYPE_SERVER "_pulse-server._tcp"
+#define SERVICE_SUBTYPE_SINK_HARDWARE "_hardware._sub."SERVICE_TYPE_SINK
+#define SERVICE_SUBTYPE_SINK_VIRTUAL "_virtual._sub."SERVICE_TYPE_SINK
+#define SERVICE_SUBTYPE_SOURCE_HARDWARE "_hardware._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_VIRTUAL "_virtual._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_MONITOR "_monitor._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE
+
+/*
+ * Note: Because the core avahi-client calls result in synchronous D-Bus
+ * communication, calling any of those functions in the PA mainloop context
+ * could lead to the mainloop being blocked for long periods.
+ *
+ * To avoid this, we create a threaded-mainloop for Avahi calls, and push all
+ * D-Bus communication into that thread. The thumb-rule for the split is:
+ *
+ * 1. If access to PA data structures is needed, use the PA mainloop context
+ *
+ * 2. If a (blocking) avahi-client call is needed, use the Avahi mainloop
+ *
+ * We do have message queue to pass messages from the Avahi mainloop to the PA
+ * mainloop.
+ */
 
 static const char* const valid_modargs[] = {
 
 static const char* const valid_modargs[] = {
-    "port",
     NULL
 };
 
     NULL
 };
 
+struct avahi_msg {
+    pa_msgobject parent;
+};
+
+typedef struct avahi_msg avahi_msg;
+PA_DEFINE_PRIVATE_CLASS(avahi_msg, pa_msgobject);
+
+enum {
+    AVAHI_MESSAGE_PUBLISH_ALL,
+    AVAHI_MESSAGE_SHUTDOWN_START,
+    AVAHI_MESSAGE_SHUTDOWN_COMPLETE,
+};
+
+enum service_subtype {
+    SUBTYPE_HARDWARE,
+    SUBTYPE_VIRTUAL,
+    SUBTYPE_MONITOR
+};
+
 struct service {
 struct service {
+    void *key;
+
     struct userdata *userdata;
     AvahiEntryGroup *entry_group;
     char *service_name;
     struct userdata *userdata;
     AvahiEntryGroup *entry_group;
     char *service_name;
+    const char *service_type;
+    enum service_subtype subtype;
+
     char *name;
     char *name;
-    enum  { UNPUBLISHED, PUBLISHED_REAL, PUBLISHED_AUTOLOAD } published ;
-
-    struct {
-        int valid;
-        pa_namereg_type_t type;
-        uint32_t index;
-    } loaded;
-
-    struct {
-        int valid;
-        pa_namereg_type_t type;
-        uint32_t index;
-    } autoload;
+    bool is_sink;
+    pa_sample_spec ss;
+    pa_channel_map cm;
+    pa_proplist *proplist;
 };
 
 struct userdata {
 };
 
 struct userdata {
+    pa_thread_mq thread_mq;
+    pa_rtpoll *rtpoll;
+    avahi_msg *msg;
+
     pa_core *core;
     pa_core *core;
+    pa_module *module;
+    pa_mainloop_api *api;
+    pa_threaded_mainloop *mainloop;
+
     AvahiPoll *avahi_poll;
     AvahiClient *client;
     AvahiPoll *avahi_poll;
     AvahiClient *client;
-    pa_hashmap *services;
-    pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
-    pa_subscription *subscription;
+
+    pa_hashmap *services; /* protect with mainloop lock */
     char *service_name;
 
     AvahiEntryGroup *main_entry_group;
 
     char *service_name;
 
     AvahiEntryGroup *main_entry_group;
 
-    uint16_t port;
+    pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
+
+    pa_native_protocol *native;
+
+    bool shutting_down; /* Used in the main thread. */
+    bool client_freed; /* Used in the Avahi thread. */
 };
 
 };
 
-static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description) {
-    pa_assert(u && s && s->loaded.valid && ret_ss && ret_description);
-
-    if (s->loaded.type == PA_NAMEREG_SINK) {
-        pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index);
-        pa_assert(sink);
-        *ret_ss = sink->sample_spec;
-        *ret_description = sink->description;
-    } else if (s->loaded.type == PA_NAMEREG_SOURCE) {
-        pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index);
-        pa_assert(source);
-        *ret_ss = source->sample_spec;
-        *ret_description = source->description;
+/* Runs in PA mainloop context */
+static void get_service_data(struct service *s, pa_object *device) {
+    pa_assert(s);
+
+    if (pa_sink_isinstance(device)) {
+        pa_sink *sink = PA_SINK(device);
+
+        s->is_sink = true;
+        s->service_type = SERVICE_TYPE_SINK;
+        s->ss = sink->sample_spec;
+        s->cm = sink->channel_map;
+        s->name = pa_xstrdup(sink->name);
+        s->proplist = pa_proplist_copy(sink->proplist);
+        s->subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+
+    } else if (pa_source_isinstance(device)) {
+        pa_source *source = PA_SOURCE(device);
+
+        s->is_sink = false;
+        s->service_type = SERVICE_TYPE_SOURCE;
+        s->ss = source->sample_spec;
+        s->cm = source->channel_map;
+        s->name = pa_xstrdup(source->name);
+        s->proplist = pa_proplist_copy(source->proplist);
+        s->subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL);
+
     } else
     } else
-        pa_assert(0);
+        pa_assert_not_reached();
 }
 
 }
 
+/* Can be used in either PA or Avahi mainloop context since the bits of u->core
+ * that we access don't change after startup. */
 static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
     char s[128];
 static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
     char s[128];
+    char *t;
+
     pa_assert(c);
 
     l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
     pa_assert(c);
 
     l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
-    l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s)));
+
+    t = pa_get_user_name_malloc();
+    l = avahi_string_list_add_pair(l, "user-name", t);
+    pa_xfree(t);
+
+    t = pa_machine_id();
+    l = avahi_string_list_add_pair(l, "machine-id", t);
+    pa_xfree(t);
+
+    t = pa_uname_string();
+    l = avahi_string_list_add_pair(l, "uname", t);
+    pa_xfree(t);
+
     l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s)));
     l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie);
 
     return l;
 }
 
     l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s)));
     l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie);
 
     return l;
 }
 
-static int publish_service(struct userdata *u, struct service *s);
+static void publish_service(pa_mainloop_api *api, void *service);
 
 
+/* Runs in Avahi mainloop context */
 static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
     struct service *s = userdata;
 
 static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
     struct service *s = userdata;
 
-    if (state == AVAHI_ENTRY_GROUP_COLLISION) {
-        char *t;
+    pa_assert(s);
+
+    switch (state) {
+
+        case AVAHI_ENTRY_GROUP_ESTABLISHED:
+            pa_log_info("Successfully established service %s.", s->service_name);
+            break;
+
+        case AVAHI_ENTRY_GROUP_COLLISION: {
+            char *t;
+
+            t = avahi_alternative_service_name(s->service_name);
+            pa_log_info("Name collision, renaming %s to %s.", s->service_name, t);
+            pa_xfree(s->service_name);
+            s->service_name = t;
+
+            publish_service(NULL, s);
+            break;
+        }
+
+        case AVAHI_ENTRY_GROUP_FAILURE: {
+            pa_log("Failed to register service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
 
 
-        t = avahi_alternative_service_name(s->service_name);
-        pa_xfree(s->service_name);
-        s->service_name = t;
+            avahi_entry_group_free(g);
+            s->entry_group = NULL;
 
 
-        publish_service(s->userdata, s);
+            break;
+        }
+
+        case AVAHI_ENTRY_GROUP_UNCOMMITED:
+        case AVAHI_ENTRY_GROUP_REGISTERING:
+            ;
     }
 }
 
     }
 }
 
-static int publish_service(struct userdata *u, struct service *s) {
-    int r = -1;
-    AvahiStringList *txt = NULL;
+static void service_free(struct service *s);
+
+/* Can run in either context */
+static uint16_t compute_port(struct userdata *u) {
+    pa_strlist *i;
 
     pa_assert(u);
 
     pa_assert(u);
-    pa_assert(s);
 
 
-    if (!u->client || avahi_client_get_state(u->client) != AVAHI_CLIENT_S_RUNNING)
-        return 0;
+    for (i = pa_native_protocol_servers(u->native); i; i = pa_strlist_next(i)) {
+        pa_parsed_address a;
 
 
-    if ((s->published == PUBLISHED_REAL && s->loaded.valid) ||
-        (s->published == PUBLISHED_AUTOLOAD && s->autoload.valid && !s->loaded.valid))
-        return 0;
+        if (pa_parse_address(pa_strlist_data(i), &a) >= 0 &&
+            (a.type == PA_PARSED_ADDRESS_TCP4 ||
+             a.type == PA_PARSED_ADDRESS_TCP6 ||
+             a.type == PA_PARSED_ADDRESS_TCP_AUTO) &&
+            a.port > 0) {
 
 
-    if (s->published != UNPUBLISHED) {
-        avahi_entry_group_reset(s->entry_group);
-        s->published = UNPUBLISHED;
+            pa_xfree(a.path_or_host);
+            return a.port;
+        }
+
+        pa_xfree(a.path_or_host);
     }
 
     }
 
-    if (s->loaded.valid || s->autoload.valid) {
-        pa_namereg_type_t type;
+    return PA_NATIVE_DEFAULT_PORT;
+}
 
 
-        if (!s->entry_group) {
-            if (!(s->entry_group = avahi_entry_group_new(u->client, service_entry_group_callback, s))) {
-                pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(u->client)));
-                goto finish;
-            }
+/* Runs in Avahi mainloop context */
+static void publish_service(pa_mainloop_api *api PA_GCC_UNUSED, void *service) {
+    struct service *s = (struct service *) service;
+    int r = -1;
+    AvahiStringList *txt = NULL;
+    char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    const char *t;
+
+    const char * const subtype_text[] = {
+        [SUBTYPE_HARDWARE] = "hardware",
+        [SUBTYPE_VIRTUAL] = "virtual",
+        [SUBTYPE_MONITOR] = "monitor"
+    };
+
+    pa_assert(s);
+
+    if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING)
+        return;
+
+    if (!s->entry_group) {
+        if (!(s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) {
+            pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+            goto finish;
         }
         }
+    } else
+        avahi_entry_group_reset(s->entry_group);
 
 
-        txt = avahi_string_list_add_pair(txt, "device", s->name);
-        txt = txt_record_server_data(u->core, txt);
+    txt = txt_record_server_data(s->userdata->core, txt);
+
+    txt = avahi_string_list_add_pair(txt, "device", s->name);
+    txt = avahi_string_list_add_printf(txt, "rate=%u", s->ss.rate);
+    txt = avahi_string_list_add_printf(txt, "channels=%u", s->ss.channels);
+    txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(s->ss.format));
+    txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &s->cm));
+    txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[s->subtype]);
+
+    if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+        txt = avahi_string_list_add_pair(txt, "description", t);
+    if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_ICON_NAME)))
+        txt = avahi_string_list_add_pair(txt, "icon-name", t);
+    if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_VENDOR_NAME)))
+        txt = avahi_string_list_add_pair(txt, "vendor-name", t);
+    if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_PRODUCT_NAME)))
+        txt = avahi_string_list_add_pair(txt, "product-name", t);
+    if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS)))
+        txt = avahi_string_list_add_pair(txt, "class", t);
+    if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_FORM_FACTOR)))
+        txt = avahi_string_list_add_pair(txt, "form-factor", t);
 
 
-        if (s->loaded.valid) {
-            char *description;
-            pa_sample_spec ss;
+    if (avahi_entry_group_add_service_strlst(
+                s->entry_group,
+                AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+                0,
+                s->service_name,
+                s->service_type,
+                NULL,
+                NULL,
+                compute_port(s->userdata),
+                txt) < 0) {
 
 
-            get_service_data(u, s, &ss, &description);
+        pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+        goto finish;
+    }
 
 
-            txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);
-            txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels);
-            txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format));
-            if (description)
-                txt = avahi_string_list_add_pair(txt, "description", description);
+    if (avahi_entry_group_add_service_subtype(
+                s->entry_group,
+                AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+                0,
+                s->service_name,
+                s->service_type,
+                NULL,
+                s->is_sink ? (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) :
+                (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (s->subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) {
 
 
-            type = s->loaded.type;
-        } else if (s->autoload.valid)
-            type = s->autoload.type;
+        pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+        goto finish;
+    }
 
 
-        if (avahi_entry_group_add_service_strlst(
+    if (!s->is_sink && s->subtype != SUBTYPE_MONITOR) {
+        if (avahi_entry_group_add_service_subtype(
                     s->entry_group,
                     AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
                     0,
                     s->service_name,
                     s->entry_group,
                     AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
                     0,
                     s->service_name,
-                    type == PA_NAMEREG_SINK ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
+                    SERVICE_TYPE_SOURCE,
                     NULL,
                     NULL,
-                    NULL,
-                    u->port,
-                    txt) < 0) {
-
-            pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(u->client)));
-            goto finish;
-        }
+                    SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) {
 
 
-        if (avahi_entry_group_commit(s->entry_group) < 0) {
-            pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(u->client)));
+            pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
             goto finish;
         }
             goto finish;
         }
+    }
 
 
-        if (s->loaded.valid)
-            s->published = PUBLISHED_REAL;
-        else if (s->autoload.valid)
-            s->published = PUBLISHED_AUTOLOAD;
+    if (avahi_entry_group_commit(s->entry_group) < 0) {
+        pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
+        goto finish;
     }
 
     r = 0;
     }
 
     r = 0;
+    pa_log_debug("Successfully created entry group for %s.", s->service_name);
 
 finish:
 
 
 finish:
 
-    if (s->published == UNPUBLISHED) {
-        /* Remove this service */
-
-        if (s->entry_group)
-            avahi_entry_group_free(s->entry_group);
-
-        pa_hashmap_remove(u->services, s->name);
-        pa_xfree(s->name);
-        pa_xfree(s->service_name);
-        pa_xfree(s);
-    }
-
-    if (txt)
-        avahi_string_list_free(txt);
+    /* Remove this service */
+    if (r < 0)
+        pa_hashmap_remove_and_free(s->userdata->services, s->key);
 
 
-    return r;
+    avahi_string_list_free(txt);
 }
 
 }
 
-static struct service *get_service(struct userdata *u, const char *name, const char *description) {
+/* Runs in PA mainloop context */
+static struct service *get_service(struct userdata *u, pa_object *device) {
     struct service *s;
     struct service *s;
-    char hn[64];
+    char *hn, *un;
+    const char *n;
+
+    pa_assert(u);
+    pa_object_assert_ref(device);
+
+    pa_threaded_mainloop_lock(u->mainloop);
 
 
-    if ((s = pa_hashmap_get(u->services, name)))
-        return s;
+    if ((s = pa_hashmap_get(u->services, device)))
+        goto out;
 
     s = pa_xnew(struct service, 1);
 
     s = pa_xnew(struct service, 1);
+    s->key = device;
     s->userdata = u;
     s->entry_group = NULL;
     s->userdata = u;
     s->entry_group = NULL;
-    s->published = UNPUBLISHED;
-    s->name = pa_xstrdup(name);
-    s->loaded.valid = s->autoload.valid = 0;
-    s->service_name = pa_sprintf_malloc("%s on %s", description ? description : s->name, pa_get_host_name(hn, sizeof(hn)));
 
 
-    pa_hashmap_put(u->services, s->name, s);
+    get_service_data(s, device);
 
 
-    return s;
-}
-
-static int publish_sink(struct userdata *u, pa_sink *s) {
-    struct service *svc;
-    int ret;
-    pa_assert(u && s);
-
-    svc = get_service(u, s->name, s->description);
-    if (svc->loaded.valid)
-        return publish_service(u, svc);
-
-    svc->loaded.valid = 1;
-    svc->loaded.type = PA_NAMEREG_SINK;
-    svc->loaded.index = s->index;
-
-    if ((ret = publish_service(u, svc)) < 0)
-        return ret;
-
-    pa_dynarray_put(u->sink_dynarray, s->index, svc);
-    return ret;
-}
+    if (!(n = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+        n = s->name;
 
 
-static int publish_source(struct userdata *u, pa_source *s) {
-    struct service *svc;
-    int ret;
+    hn = pa_get_host_name_malloc();
+    un = pa_get_user_name_malloc();
 
 
-    pa_assert(u && s);
+    s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", un, hn, n), AVAHI_LABEL_MAX-1);
 
 
-    svc = get_service(u, s->name, s->description);
-    if (svc->loaded.valid)
-        return publish_service(u, svc);
+    pa_xfree(un);
+    pa_xfree(hn);
 
 
-    svc->loaded.valid = 1;
-    svc->loaded.type = PA_NAMEREG_SOURCE;
-    svc->loaded.index = s->index;
+    pa_hashmap_put(u->services, device, s);
 
 
-    pa_dynarray_put(u->source_dynarray, s->index, svc);
+out:
+    pa_threaded_mainloop_unlock(u->mainloop);
 
 
-    if ((ret = publish_service(u, svc)) < 0)
-        return ret;
-
-    pa_dynarray_put(u->sink_dynarray, s->index, svc);
-    return ret;
+    return s;
 }
 
 }
 
-static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
-    struct service *svc;
-    int ret;
-
-    pa_assert(u && s);
+/* Run from Avahi mainloop context */
+static void service_free(struct service *s) {
+    pa_assert(s);
 
 
-    svc = get_service(u, s->name, NULL);
-    if (svc->autoload.valid)
-        return publish_service(u, svc);
+    if (s->entry_group) {
+        pa_log_debug("Removing entry group for %s.", s->service_name);
+        avahi_entry_group_free(s->entry_group);
+    }
 
 
-    svc->autoload.valid = 1;
-    svc->autoload.type = s->type;
-    svc->autoload.index = s->index;
+    pa_xfree(s->service_name);
 
 
-    if ((ret = publish_service(u, svc)) < 0)
-        return ret;
+    pa_xfree(s->name);
+    pa_proplist_free(s->proplist);
 
 
-    pa_dynarray_put(u->autoload_dynarray, s->index, svc);
-    return ret;
+    pa_xfree(s);
 }
 
 }
 
-static int remove_sink(struct userdata *u, uint32_t idx) {
-    struct service *svc;
-    pa_assert(u && idx != PA_INVALID_INDEX);
+/* Runs in PA mainloop context */
+static bool shall_ignore(pa_object *o) {
+    pa_object_assert_ref(o);
 
 
-    if (!(svc = pa_dynarray_get(u->sink_dynarray, idx)))
-        return 0;
+    if (pa_sink_isinstance(o))
+        return !!(PA_SINK(o)->flags & PA_SINK_NETWORK);
 
 
-    if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
-        return 0;
+    if (pa_source_isinstance(o))
+        return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK);
 
 
-    svc->loaded.valid = 0;
-    pa_dynarray_put(u->sink_dynarray, idx, NULL);
-
-    return publish_service(u, svc);
+    pa_assert_not_reached();
 }
 
 }
 
-static int remove_source(struct userdata *u, uint32_t idx) {
-    struct service *svc;
-    pa_assert(u && idx != PA_INVALID_INDEX);
-
-    if (!(svc = pa_dynarray_get(u->source_dynarray, idx)))
-        return 0;
-
-    if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
-        return 0;
+/* Runs in PA mainloop context */
+static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
+    pa_assert(c);
+    pa_object_assert_ref(o);
 
 
-    svc->loaded.valid = 0;
-    pa_dynarray_put(u->source_dynarray, idx, NULL);
+    if (!shall_ignore(o)) {
+        pa_threaded_mainloop_lock(u->mainloop);
+        pa_mainloop_api_once(u->api, publish_service, get_service(u, o));
+        pa_threaded_mainloop_unlock(u->mainloop);
+    }
 
 
-    return publish_service(u, svc);
+    return PA_HOOK_OK;
 }
 
 }
 
-static int remove_autoload(struct userdata *u, uint32_t idx) {
-    struct service *svc;
-    pa_assert(u && idx != PA_INVALID_INDEX);
-
-    if (!(svc = pa_dynarray_get(u->autoload_dynarray, idx)))
-        return 0;
-
-    if (!svc->autoload.valid)
-        return 0;
+/* Runs in PA mainloop context */
+static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
+    pa_assert(c);
+    pa_object_assert_ref(o);
 
 
-    svc->autoload.valid = 0;
-    pa_dynarray_put(u->autoload_dynarray, idx, NULL);
+    pa_threaded_mainloop_lock(u->mainloop);
+    pa_hashmap_remove_and_free(u->services, o);
+    pa_threaded_mainloop_unlock(u->mainloop);
 
 
-    return publish_service(u, svc);
+    return PA_HOOK_OK;
 }
 
 }
 
-static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
-    struct userdata *u = userdata;
-    pa_assert(u && c);
+static int publish_main_service(struct userdata *u);
 
 
-    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
-        case PA_SUBSCRIPTION_EVENT_SINK: {
-            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
-                pa_sink *sink;
+/* Runs in Avahi mainloop context */
+static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+    struct userdata *u = userdata;
+    pa_assert(u);
 
 
-                if ((sink = pa_idxset_get_by_index(c->sinks, idx))) {
-                    if (publish_sink(u, sink) < 0)
-                        goto fail;
-                }
-            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
-                if (remove_sink(u, idx) < 0)
-                    goto fail;
-            }
+    switch (state) {
 
 
+        case AVAHI_ENTRY_GROUP_ESTABLISHED:
+            pa_log_info("Successfully established main service.");
             break;
 
             break;
 
-        case PA_SUBSCRIPTION_EVENT_SOURCE:
+        case AVAHI_ENTRY_GROUP_COLLISION: {
+            char *t;
 
 
-            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
-                pa_source *source;
-
-                if ((source = pa_idxset_get_by_index(c->sources, idx))) {
-                    if (publish_source(u, source) < 0)
-                        goto fail;
-                }
-            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
-                if (remove_source(u, idx) < 0)
-                    goto fail;
-            }
+            t = avahi_alternative_service_name(u->service_name);
+            pa_log_info("Name collision: renaming main service %s to %s.", u->service_name, t);
+            pa_xfree(u->service_name);
+            u->service_name = t;
 
 
+            publish_main_service(u);
             break;
             break;
+        }
 
 
-        case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
-            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
-                pa_autoload_entry *autoload;
-
-                if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, idx))) {
-                    if (publish_autoload(u, autoload) < 0)
-                        goto fail;
-                }
-            } else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
-                if (remove_autoload(u, idx) < 0)
-                        goto fail;
-            }
+        case AVAHI_ENTRY_GROUP_FAILURE: {
+            pa_log("Failed to register main service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
 
 
+            avahi_entry_group_free(g);
+            u->main_entry_group = NULL;
             break;
             break;
-    }
-
-    return;
-
-fail:
-    if (u->subscription) {
-        pa_subscription_free(u->subscription);
-        u->subscription = NULL;
-    }
-}
-
-static int publish_main_service(struct userdata *u);
-
-static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
-    struct userdata *u = userdata;
-    pa_assert(u);
-
-    if (state == AVAHI_ENTRY_GROUP_COLLISION) {
-        char *t;
-
-        t = avahi_alternative_service_name(u->service_name);
-        pa_xfree(u->service_name);
-        u->service_name = t;
+        }
 
 
-        publish_main_service(u);
+        case AVAHI_ENTRY_GROUP_UNCOMMITED:
+        case AVAHI_ENTRY_GROUP_REGISTERING:
+            break;
     }
 }
 
     }
 }
 
+/* Runs in Avahi mainloop context */
 static int publish_main_service(struct userdata *u) {
     AvahiStringList *txt = NULL;
     int r = -1;
 
 static int publish_main_service(struct userdata *u) {
     AvahiStringList *txt = NULL;
     int r = -1;
 
+    pa_assert(u);
+
     if (!u->main_entry_group) {
         if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
             pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
     if (!u->main_entry_group) {
         if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
             pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
@@ -464,7 +532,7 @@ static int publish_main_service(struct userdata *u) {
     } else
         avahi_entry_group_reset(u->main_entry_group);
 
     } else
         avahi_entry_group_reset(u->main_entry_group);
 
-    txt = txt_record_server_data(u->core, NULL);
+    txt = txt_record_server_data(u->core, txt);
 
     if (avahi_entry_group_add_service_strlst(
                 u->main_entry_group,
 
     if (avahi_entry_group_add_service_strlst(
                 u->main_entry_group,
@@ -474,7 +542,7 @@ static int publish_main_service(struct userdata *u) {
                 SERVICE_TYPE_SERVER,
                 NULL,
                 NULL,
                 SERVICE_TYPE_SERVER,
                 NULL,
                 NULL,
-                u->port,
+                compute_port(u),
                 txt) < 0) {
 
         pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
                 txt) < 0) {
 
         pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
@@ -494,10 +562,10 @@ fail:
     return r;
 }
 
     return r;
 }
 
+/* Runs in PA mainloop context */
 static int publish_all_services(struct userdata *u) {
     pa_sink *sink;
     pa_source *source;
 static int publish_all_services(struct userdata *u) {
     pa_sink *sink;
     pa_source *source;
-    pa_autoload_entry *autoload;
     int r = -1;
     uint32_t idx;
 
     int r = -1;
     uint32_t idx;
 
@@ -505,18 +573,19 @@ static int publish_all_services(struct userdata *u) {
 
     pa_log_debug("Publishing services in Zeroconf");
 
 
     pa_log_debug("Publishing services in Zeroconf");
 
-    for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx))
-        if (publish_sink(u, sink) < 0)
-            goto fail;
-
-    for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx))
-        if (publish_source(u, source) < 0)
-            goto fail;
+    for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
+        if (!shall_ignore(PA_OBJECT(sink))) {
+            pa_threaded_mainloop_lock(u->mainloop);
+            pa_mainloop_api_once(u->api, publish_service, get_service(u, PA_OBJECT(sink)));
+            pa_threaded_mainloop_unlock(u->mainloop);
+        }
 
 
-    if (u->core->autoload_idxset)
-        for (autoload = pa_idxset_first(u->core->autoload_idxset, &idx); autoload; autoload = pa_idxset_next(u->core->autoload_idxset, &idx))
-            if (publish_autoload(u, autoload) < 0)
-                goto fail;
+    for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
+        if (!shall_ignore(PA_OBJECT(source))) {
+            pa_threaded_mainloop_lock(u->mainloop);
+            pa_mainloop_api_once(u->api, publish_service, get_service(u, PA_OBJECT(source)));
+            pa_threaded_mainloop_unlock(u->mainloop);
+        }
 
     if (publish_main_service(u) < 0)
         goto fail;
 
     if (publish_main_service(u) < 0)
         goto fail;
@@ -527,7 +596,8 @@ fail:
     return r;
 }
 
     return r;
 }
 
-static void unpublish_all_services(struct userdata *u, int rem) {
+/* Runs in Avahi mainloop context */
+static void unpublish_all_services(struct userdata *u, bool rem) {
     void *state = NULL;
     struct service *s;
 
     void *state = NULL;
     struct service *s;
 
@@ -538,47 +608,86 @@ static void unpublish_all_services(struct userdata *u, int rem) {
     while ((s = pa_hashmap_iterate(u->services, &state, NULL))) {
         if (s->entry_group) {
             if (rem) {
     while ((s = pa_hashmap_iterate(u->services, &state, NULL))) {
         if (s->entry_group) {
             if (rem) {
+                pa_log_debug("Removing entry group for %s.", s->service_name);
                 avahi_entry_group_free(s->entry_group);
                 s->entry_group = NULL;
                 avahi_entry_group_free(s->entry_group);
                 s->entry_group = NULL;
-            } else
+            } else {
                 avahi_entry_group_reset(s->entry_group);
                 avahi_entry_group_reset(s->entry_group);
+                pa_log_debug("Resetting entry group for %s.", s->service_name);
+            }
         }
         }
-
-        s->published = UNPUBLISHED;
     }
 
     if (u->main_entry_group) {
         if (rem) {
     }
 
     if (u->main_entry_group) {
         if (rem) {
+            pa_log_debug("Removing main entry group.");
             avahi_entry_group_free(u->main_entry_group);
             u->main_entry_group = NULL;
             avahi_entry_group_free(u->main_entry_group);
             u->main_entry_group = NULL;
-        } else
+        } else {
             avahi_entry_group_reset(u->main_entry_group);
             avahi_entry_group_reset(u->main_entry_group);
+            pa_log_debug("Resetting main entry group.");
+        }
     }
 }
 
     }
 }
 
+/* Runs in PA mainloop context */
+static int avahi_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+    struct userdata *u = (struct userdata *) data;
+
+    pa_assert(u);
+
+    if (u->shutting_down)
+        return 0;
+
+    switch (code) {
+        case AVAHI_MESSAGE_PUBLISH_ALL:
+            publish_all_services(u);
+            break;
+
+        case AVAHI_MESSAGE_SHUTDOWN_START:
+            pa_module_unload(u->core, u->module, true);
+            break;
+
+        default:
+            pa_assert_not_reached();
+    }
+
+    return 0;
+}
+
+/* Runs in Avahi mainloop context */
 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
     struct userdata *u = userdata;
 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
     struct userdata *u = userdata;
+
     pa_assert(c);
     pa_assert(c);
+    pa_assert(u);
 
     u->client = c;
 
     switch (state) {
         case AVAHI_CLIENT_S_RUNNING:
 
     u->client = c;
 
     switch (state) {
         case AVAHI_CLIENT_S_RUNNING:
-            publish_all_services(u);
+            /* Collect all sinks/sources, and publish them */
+            pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_PUBLISH_ALL, u, 0, NULL, NULL);
             break;
 
         case AVAHI_CLIENT_S_COLLISION:
             break;
 
         case AVAHI_CLIENT_S_COLLISION:
-            unpublish_all_services(u, 0);
+            pa_log_debug("Host name collision");
+            unpublish_all_services(u, false);
             break;
 
         case AVAHI_CLIENT_FAILURE:
             if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
                 int error;
             break;
 
         case AVAHI_CLIENT_FAILURE:
             if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
                 int error;
-                unpublish_all_services(u, 1);
+
+                pa_log_debug("Avahi daemon disconnected.");
+
+                unpublish_all_services(u, true);
                 avahi_client_free(u->client);
 
                 avahi_client_free(u->client);
 
-                if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error)))
-                    pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
+                if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
+                    pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
+                    pa_module_unload_request(u->module, true);
+                }
             }
 
             break;
             }
 
             break;
@@ -587,48 +696,83 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
     }
 }
 
     }
 }
 
-int pa__init(pa_module*m) {
-    
-    struct userdata *u;
-    uint32_t port = PA_NATIVE_DEFAULT_PORT;
-    pa_modargs *ma = NULL;
-    char hn[256];
+/* Runs in Avahi mainloop context */
+static void create_client(pa_mainloop_api *api PA_GCC_UNUSED, void *userdata) {
+    struct userdata *u = (struct userdata *) userdata;
     int error;
 
     int error;
 
-    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
-        pa_log("failed to parse module arguments.");
+    /* create_client() and client_free() are called via defer events. If the
+     * two defer events are created very quickly one after another, we can't
+     * assume that the defer event that runs create_client() will be dispatched
+     * before the defer event that runs client_free() (at the time of writing,
+     * pa_mainloop actually always dispatches queued defer events in reverse
+     * creation order). For that reason we must be prepared for the case where
+     * client_free() has already been called. */
+    if (u->client_freed)
+        return;
+
+    pa_thread_mq_install(&u->thread_mq);
+
+    if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
+        pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
         goto fail;
     }
 
         goto fail;
     }
 
-    if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) {
-        pa_log("invalid port specified.");
+    pa_log_debug("Started Avahi threaded mainloop");
+
+    return;
+
+fail:
+    pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_SHUTDOWN_START, u, 0, NULL, NULL);
+}
+
+int pa__init(pa_module*m) {
+
+    struct userdata *u;
+    pa_modargs *ma = NULL;
+    char *hn, *un;
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments.");
         goto fail;
     }
 
         goto fail;
     }
 
-    m->userdata = u = pa_xnew(struct userdata, 1);
+    m->userdata = u = pa_xnew0(struct userdata, 1);
     u->core = m->core;
     u->core = m->core;
-    u->port = (uint16_t) port;
+    u->module = m;
+    u->native = pa_native_protocol_get(u->core);
 
 
-    u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
+    u->rtpoll = pa_rtpoll_new();
+    u->mainloop = pa_threaded_mainloop_new();
+    u->api = pa_threaded_mainloop_get_api(u->mainloop);
 
 
-    u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-    u->sink_dynarray = pa_dynarray_new();
-    u->source_dynarray = pa_dynarray_new();
-    u->autoload_dynarray = pa_dynarray_new();
+    pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll);
+    u->msg = pa_msgobject_new(avahi_msg);
+    u->msg->parent.process_msg = avahi_process_msg;
 
 
-    u->subscription = pa_subscription_new(m->core,
-                                          PA_SUBSCRIPTION_MASK_SINK|
-                                          PA_SUBSCRIPTION_MASK_SOURCE|
-                                          PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
+    u->avahi_poll = pa_avahi_poll_new(u->api);
 
 
-    u->main_entry_group = NULL;
+    u->services = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL, (pa_free_cb_t) service_free);
 
 
-    u->service_name = pa_xstrdup(pa_get_host_name(hn, sizeof(hn)));
+    u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
+    u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u);
+    u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u);
 
 
-    if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
-        pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
-        goto fail;
-    }
+    un = pa_get_user_name_malloc();
+    hn = pa_get_host_name_malloc();
+    u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", un, hn), AVAHI_LABEL_MAX-1);
+    pa_xfree(un);
+    pa_xfree(hn);
+
+    pa_threaded_mainloop_set_name(u->mainloop, "avahi-ml");
+    pa_threaded_mainloop_start(u->mainloop);
+
+    pa_threaded_mainloop_lock(u->mainloop);
+    pa_mainloop_api_once(u->api, create_client, u);
+    pa_threaded_mainloop_unlock(u->mainloop);
 
     pa_modargs_free(ma);
 
 
     pa_modargs_free(ma);
 
@@ -643,19 +787,24 @@ fail:
     return -1;
 }
 
     return -1;
 }
 
-static void service_free(void *p, void *userdata) {
-    struct service *s = p;
-    struct userdata *u = userdata;
+/* Runs in Avahi mainloop context */
+static void client_free(pa_mainloop_api *api PA_GCC_UNUSED, void *userdata) {
+    struct userdata *u = (struct userdata *) userdata;
 
 
-    pa_assert(s);
-    pa_assert(u);
+    pa_hashmap_free(u->services);
 
 
-    if (s->entry_group)
-        avahi_entry_group_free(s->entry_group);
+    if (u->main_entry_group)
+        avahi_entry_group_free(u->main_entry_group);
 
 
-    pa_xfree(s->service_name);
-    pa_xfree(s->name);
-    pa_xfree(s);
+    if (u->client)
+        avahi_client_free(u->client);
+
+    if (u->avahi_poll)
+        pa_avahi_poll_free(u->avahi_poll);
+
+    pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->msg), AVAHI_MESSAGE_SHUTDOWN_COMPLETE, u, 0, NULL, NULL);
+
+    u->client_freed = true;
 }
 
 void pa__done(pa_module*m) {
 }
 
 void pa__done(pa_module*m) {
@@ -665,30 +814,36 @@ void pa__done(pa_module*m) {
     if (!(u = m->userdata))
         return;
 
     if (!(u = m->userdata))
         return;
 
-    if (u->services)
-        pa_hashmap_free(u->services, service_free, u);
-
-    if (u->subscription)
-        pa_subscription_free(u->subscription);
+    u->shutting_down = true;
 
 
-    if (u->sink_dynarray)
-        pa_dynarray_free(u->sink_dynarray, NULL, NULL);
-    if (u->source_dynarray)
-        pa_dynarray_free(u->source_dynarray, NULL, NULL);
-    if (u->autoload_dynarray)
-        pa_dynarray_free(u->autoload_dynarray, NULL, NULL);
+    pa_threaded_mainloop_lock(u->mainloop);
+    pa_mainloop_api_once(u->api, client_free, u);
+    pa_threaded_mainloop_unlock(u->mainloop);
+    pa_asyncmsgq_wait_for(u->thread_mq.outq, AVAHI_MESSAGE_SHUTDOWN_COMPLETE);
 
 
+    pa_threaded_mainloop_stop(u->mainloop);
+    pa_threaded_mainloop_free(u->mainloop);
 
 
-    if (u->main_entry_group)
-        avahi_entry_group_free(u->main_entry_group);
+    pa_thread_mq_done(&u->thread_mq);
+    pa_rtpoll_free(u->rtpoll);
 
 
-    if (u->client)
-        avahi_client_free(u->client);
+    if (u->sink_new_slot)
+        pa_hook_slot_free(u->sink_new_slot);
+    if (u->source_new_slot)
+        pa_hook_slot_free(u->source_new_slot);
+    if (u->sink_changed_slot)
+        pa_hook_slot_free(u->sink_changed_slot);
+    if (u->source_changed_slot)
+        pa_hook_slot_free(u->source_changed_slot);
+    if (u->sink_unlink_slot)
+        pa_hook_slot_free(u->sink_unlink_slot);
+    if (u->source_unlink_slot)
+        pa_hook_slot_free(u->source_unlink_slot);
 
 
-    if (u->avahi_poll)
-        pa_avahi_poll_free(u->avahi_poll);
+    if (u->native)
+        pa_native_protocol_unref(u->native);
 
 
+    pa_xfree(u->msg);
     pa_xfree(u->service_name);
     pa_xfree(u);
 }
     pa_xfree(u->service_name);
     pa_xfree(u);
 }
-