pulse-server: module-zeroconf-publish: handle disconnection

Keep track of the created services in two lists: published, pending.
Move services between the lists as the avahi client's state changes:
keep services in the pending list until the avahi daemon appears on dbus,
move them to the pending list if connection is lost,
and re-publish them after reconnection.
This commit is contained in:
Barnabás Pőcze 2021-11-06 20:07:22 +01:00
parent 987c7fc1e4
commit 422d69b471

View file

@ -66,6 +66,8 @@ enum service_subtype {
}; };
struct service { struct service {
struct spa_list link;
struct module_zeroconf_publish_data *userdata; struct module_zeroconf_publish_data *userdata;
AvahiEntryGroup *entry_group; AvahiEntryGroup *entry_group;
const char *service_type; const char *service_type;
@ -94,7 +96,10 @@ struct module_zeroconf_publish_data {
AvahiPoll *avahi_poll; AvahiPoll *avahi_poll;
AvahiClient *client; AvahiClient *client;
bool entry_group_free;
/* lists of services */
struct spa_list pending;
struct spa_list published;
}; };
static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
@ -125,19 +130,8 @@ static void get_service_name(struct pw_manager_object *o, char *buf, size_t leng
snprintf(buf, length, "%s@%s: %s", un, hn, n); snprintf(buf, length, "%s@%s: %s", un, hn, n);
} }
static int service_free(void *d, struct pw_manager_object *o) static void service_free(struct service *s)
{ {
struct service *s;
if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source(o))
return 0;
s = pw_manager_object_get_data(o, SERVICE_DATA_ID);
if (s == NULL) {
pw_log_error("Could not find service to remove");
return 0;
}
if (s->entry_group) { if (s->entry_group) {
pw_log_debug("Removing entry group for %s.", s->service_name); pw_log_debug("Removing entry group for %s.", s->service_name);
avahi_entry_group_free(s->entry_group); avahi_entry_group_free(s->entry_group);
@ -149,42 +143,21 @@ static int service_free(void *d, struct pw_manager_object *o)
} }
pw_properties_free(s->props); pw_properties_free(s->props);
spa_list_remove(&s->link);
return 0;
} }
static int unpublish_service(void *data, struct pw_manager_object *o) static void unpublish_service(struct service *s)
{
spa_list_remove(&s->link);
spa_list_append(&s->userdata->pending, &s->link);
}
static void unpublish_all_services(struct module_zeroconf_publish_data *d)
{ {
struct module_zeroconf_publish_data *d = data;
struct service *s; struct service *s;
if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source(o)) spa_list_consume(s, &d->published, link)
return 0; unpublish_service(s);
s = pw_manager_object_get_data(o, SERVICE_DATA_ID);
if (s == NULL) {
pw_log_error("Could not find service to remove");
return 0;
}
if (s->entry_group) {
if (d->entry_group_free) {
pw_log_debug("Removing entry group for %s.", s->service_name);
avahi_entry_group_free(s->entry_group);
s->entry_group = NULL;
} else {
avahi_entry_group_reset(s->entry_group);
pw_log_debug("Resetting entry group for %s.", s->service_name);
}
}
return 0;
}
static void unpublish_all_services(struct module_zeroconf_publish_data *d, bool entry_group_free)
{
d->entry_group_free = entry_group_free;
pw_manager_for_each_object(d->manager, unpublish_service, d);
} }
static char* channel_map_snprint(char *s, size_t l, const struct channel_map *map) static char* channel_map_snprint(char *s, size_t l, const struct channel_map *map)
@ -289,6 +262,7 @@ static struct service *create_service(struct module_zeroconf_publish_data *d, st
s->userdata = d; s->userdata = d;
s->entry_group = NULL; s->entry_group = NULL;
get_service_name(o, s->service_name, sizeof(s->service_name)); get_service_name(o, s->service_name, sizeof(s->service_name));
spa_list_append(&d->pending, &s->link);
fill_service_data(d, s, o); fill_service_data(d, s, o);
@ -321,6 +295,15 @@ static AvahiStringList* txt_record_server_data(struct pw_core_info *info, AvahiS
return l; return l;
} }
static void clear_entry_group(struct service *s)
{
if (s->entry_group == NULL)
return;
avahi_entry_group_free(s->entry_group);
s->entry_group = NULL;
}
static void publish_service(struct service *s); static void publish_service(struct service *s);
static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata)
@ -343,14 +326,16 @@ static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupStat
snprintf(s->service_name, sizeof(s->service_name), "%s", t); snprintf(s->service_name, sizeof(s->service_name), "%s", t);
avahi_free(t); avahi_free(t);
unpublish_service(s);
publish_service(s); publish_service(s);
break; break;
} }
case AVAHI_ENTRY_GROUP_FAILURE: case AVAHI_ENTRY_GROUP_FAILURE:
pw_log_error("Failed to register service: %s", pw_log_error("Failed to register service: %s",
avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
avahi_entry_group_free(g);
s->entry_group = NULL; unpublish_service(s);
clear_entry_group(s);
break; break;
case AVAHI_ENTRY_GROUP_UNCOMMITED: case AVAHI_ENTRY_GROUP_UNCOMMITED:
@ -379,14 +364,15 @@ static void publish_service(struct service *s)
return; return;
if (!s->entry_group) { if (!s->entry_group) {
if (!(s->entry_group = s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s);
avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) { if (s->entry_group == NULL) {
pw_log_error("avahi_entry_group_new(): %s", pw_log_error("avahi_entry_group_new(): %s",
avahi_strerror(avahi_client_errno(s->userdata->client))); avahi_strerror(avahi_client_errno(s->userdata->client)));
goto finish; goto finish;
} }
} else } else {
avahi_entry_group_reset(s->entry_group); avahi_entry_group_reset(s->entry_group);
}
txt = txt_record_server_data(s->userdata->manager->info, txt); txt = txt_record_server_data(s->userdata->manager->info, txt);
@ -461,12 +447,31 @@ static void publish_service(struct service *s)
goto finish; goto finish;
} }
spa_list_remove(&s->link);
spa_list_append(&s->userdata->published, &s->link);
pw_log_info("Successfully created entry group for %s.", s->service_name); pw_log_info("Successfully created entry group for %s.", s->service_name);
finish: finish:
avahi_string_list_free(txt); avahi_string_list_free(txt);
} }
static void publish_pending(struct module_zeroconf_publish_data *data)
{
struct service *s, *next;
spa_list_for_each_safe(s, next, &data->pending, link)
publish_service(s);
}
static void clear_pending_entry_groups(struct module_zeroconf_publish_data *data)
{
struct service *s;
spa_list_for_each(s, &data->pending, link)
clear_entry_group(s);
}
static void client_callback(AvahiClient *c, AvahiClientState state, void *d) static void client_callback(AvahiClient *c, AvahiClientState state, void *d)
{ {
struct module_zeroconf_publish_data *data = d; struct module_zeroconf_publish_data *data = d;
@ -478,27 +483,37 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *d)
switch (state) { switch (state) {
case AVAHI_CLIENT_S_RUNNING: case AVAHI_CLIENT_S_RUNNING:
pw_log_info("the avahi daemon is up and running");
publish_pending(data);
break; break;
case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_COLLISION:
pw_log_error("Host name collision"); pw_log_error("Host name collision");
unpublish_all_services(d, false); unpublish_all_services(d);
break; break;
case AVAHI_CLIENT_FAILURE: case AVAHI_CLIENT_FAILURE:
if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { {
int error; int err = avahi_client_errno(data->client);
pw_log_debug("Avahi daemon disconnected."); pw_log_error("avahi client failure: %s", avahi_strerror(err));
unpublish_all_services(d, true); unpublish_all_services(data);
avahi_client_free(data->client); clear_pending_entry_groups(data);
avahi_client_free(data->client);
data->client = NULL;
if (!(data->client = avahi_client_new(data->avahi_poll, if (err == AVAHI_ERR_DISCONNECTED) {
AVAHI_CLIENT_NO_FAIL, client_callback, data, &error))) { data->client = avahi_client_new(data->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, data, &err);
pw_log_error("avahi_client_new() failed: %s", if (data->client == NULL)
avahi_strerror(error)); pw_log_error("avahi_client_new(): %s", avahi_strerror(err));
module_schedule_unload(data->module);
}
} }
if (data->client == NULL)
module_schedule_unload(data->module);
break;
}
case AVAHI_CLIENT_CONNECTING:
pw_log_info("connecting to the avahi daemon...");
break; break;
default: default:
break; break;
@ -510,7 +525,11 @@ static void manager_removed(void *d, struct pw_manager_object *o)
if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source(o)) if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source(o))
return; return;
service_free(d, o); struct service *s = pw_manager_object_get_data(o, SERVICE_DATA_ID);
if (s == NULL)
return;
service_free(s);
} }
static void manager_added(void *d, struct pw_manager_object *o) static void manager_added(void *d, struct pw_manager_object *o)
@ -573,8 +592,12 @@ static int module_zeroconf_publish_load(struct client *client, struct module *mo
static int module_zeroconf_publish_unload(struct client *client, struct module *module) static int module_zeroconf_publish_unload(struct client *client, struct module *module)
{ {
struct module_zeroconf_publish_data *d = module->user_data; struct module_zeroconf_publish_data *d = module->user_data;
struct service *s;
pw_manager_for_each_object(d->manager, service_free, d); unpublish_all_services(d);
spa_list_consume(s, &d->pending, link)
service_free(s);
if (d->client) if (d->client)
avahi_client_free(d->client); avahi_client_free(d->client);
@ -634,8 +657,9 @@ struct module *create_module_zeroconf_publish(struct impl *impl, const char *arg
module->props = props; module->props = props;
d = module->user_data; d = module->user_data;
d->module = module; d->module = module;
d->port = pw_properties_get_uint32(props, "port", PW_PROTOCOL_PULSE_DEFAULT_PORT); d->port = pw_properties_get_uint32(props, "port", PW_PROTOCOL_PULSE_DEFAULT_PORT);
spa_list_init(&d->pending);
spa_list_init(&d->published);
return module; return module;
out: out: