Fix reference count of a2dp stream setup
Each callback should have a reference to the setup and once the callback
is removed the setup should be unreferenced which wasn't always the case.
diff --git a/audio/a2dp.c b/audio/a2dp.c
index 18a199c..b1e94d9 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -77,6 +77,7 @@
};
struct a2dp_setup_cb {
+ struct a2dp_setup *setup;
a2dp_select_cb_t select_cb;
a2dp_config_cb_t config_cb;
a2dp_stream_cb_t resume_cb;
@@ -155,6 +156,7 @@
static void setup_free(struct a2dp_setup *s)
{
DBG("%p", s);
+
setups = g_slist_remove(setups, s);
if (s->session)
avdtp_unref(s->session);
@@ -169,8 +171,31 @@
DBG("%p: ref=%d", setup, setup->ref);
- if (setup->ref <= 0)
- setup_free(setup);
+ if (setup->ref > 0)
+ return;
+
+ setup_free(setup);
+}
+
+static struct a2dp_setup_cb *setup_cb_new(struct a2dp_setup *setup)
+{
+ struct a2dp_setup_cb *cb;
+
+ cb = g_new0(struct a2dp_setup_cb, 1);
+ cb->setup = setup;
+ cb->id = ++cb_id;
+
+ setup->cb = g_slist_append(setup->cb, cb);
+ return cb;
+}
+
+static void setup_cb_free(struct a2dp_setup_cb *cb)
+{
+ struct a2dp_setup *setup = cb->setup;
+
+ setup->cb = g_slist_remove(setup->cb, cb);
+ setup_unref(cb->setup);
+ g_free(cb);
}
static gboolean finalize_config(struct a2dp_setup *s)
@@ -178,7 +203,6 @@
GSList *l;
struct avdtp_stream *stream = s->err ? NULL : s->stream;
- setup_ref(s);
for (l = s->cb; l != NULL; ) {
struct a2dp_setup_cb *cb = l->data;
@@ -189,13 +213,9 @@
cb->config_cb(s->session, s->sep, stream, s->err,
cb->user_data);
- s->cb = g_slist_remove(s->cb, cb);
- g_free(cb);
- setup_unref(s);
+ setup_cb_free(cb);
}
- setup_unref(s);
-
return FALSE;
}
@@ -213,8 +233,6 @@
{
GSList *l;
- setup_ref(s);
-
for (l = s->cb; l != NULL; ) {
struct a2dp_setup_cb *cb = l->data;
@@ -224,13 +242,9 @@
continue;
cb->resume_cb(s->session, s->err, cb->user_data);
- s->cb = g_slist_remove(s->cb, cb);
- g_free(cb);
- setup_unref(s);
+ setup_cb_free(cb);
}
- setup_unref(s);
-
return FALSE;
}
@@ -238,7 +252,6 @@
{
GSList *l;
- setup_ref(s);
for (l = s->cb; l != NULL; ) {
struct a2dp_setup_cb *cb = l->data;
@@ -248,12 +261,9 @@
continue;
cb->suspend_cb(s->session, s->err, cb->user_data);
- s->cb = g_slist_remove(s->cb, cb);
- g_free(cb);
- setup_unref(s);
+ setup_cb_free(cb);
}
- setup_unref(s);
return FALSE;
}
@@ -271,7 +281,6 @@
{
GSList *l;
- setup_ref(s);
for (l = s->cb; l != NULL; ) {
struct a2dp_setup_cb *cb = l->data;
@@ -281,12 +290,9 @@
continue;
cb->select_cb(s->session, s->sep, caps, cb->user_data);
- s->cb = g_slist_remove(s->cb, cb);
- g_free(cb);
- setup_unref(s);
+ setup_cb_free(cb);
}
- setup_unref(s);
return FALSE;
}
@@ -391,6 +397,8 @@
if (err)
g_free(err);
+ setup_unref(setup);
+
return FALSE;
}
@@ -1909,12 +1917,10 @@
if (!setup)
return 0;
- cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data = setup_cb_new(setup);
cb_data->select_cb = cb;
cb_data->user_data = user_data;
- cb_data->id = ++cb_id;
- setup->cb = g_slist_append(setup->cb, cb_data);
setup->sep = sep;
setup->rsep = avdtp_find_remote_sep(session, sep->lsep);
@@ -1947,8 +1953,7 @@
return cb_data->id;
fail:
- setup_unref(setup);
- cb_id--;
+ setup_cb_free(cb_data);
return 0;
}
@@ -1995,12 +2000,10 @@
if (!setup)
return 0;
- cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data = setup_cb_new(setup);
cb_data->config_cb = cb;
cb_data->user_data = user_data;
- cb_data->id = ++cb_id;
- setup->cb = g_slist_append(setup->cb, cb_data);
setup->sep = sep;
setup->stream = sep->stream;
setup->client_caps = caps;
@@ -2068,8 +2071,7 @@
return cb_data->id;
failed:
- setup_unref(setup);
- cb_id--;
+ setup_cb_free(cb_data);
return 0;
}
@@ -2083,12 +2085,10 @@
if (!setup)
return 0;
- cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data = setup_cb_new(setup);
cb_data->resume_cb = cb;
cb_data->user_data = user_data;
- cb_data->id = ++cb_id;
- setup->cb = g_slist_append(setup->cb, cb_data);
setup->sep = sep;
setup->stream = sep->stream;
@@ -2122,8 +2122,7 @@
return cb_data->id;
failed:
- setup_unref(setup);
- cb_id--;
+ setup_cb_free(cb_data);
return 0;
}
@@ -2137,12 +2136,10 @@
if (!setup)
return 0;
- cb_data = g_new0(struct a2dp_setup_cb, 1);
+ cb_data = setup_cb_new(setup);
cb_data->suspend_cb = cb;
cb_data->user_data = user_data;
- cb_data->id = ++cb_id;
- setup->cb = g_slist_append(setup->cb, cb_data);
setup->sep = sep;
setup->stream = sep->stream;
@@ -2169,46 +2166,39 @@
return cb_data->id;
failed:
- setup_unref(setup);
- cb_id--;
+ setup_cb_free(cb_data);
return 0;
}
gboolean a2dp_cancel(struct audio_device *dev, unsigned int id)
{
- struct a2dp_setup_cb *cb_data;
struct a2dp_setup *setup;
GSList *l;
- DBG("a2dp_cancel()");
-
setup = find_setup_by_dev(dev);
if (!setup)
return FALSE;
- for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) {
+ for (l = setup->cb; l != NULL; l = g_slist_next(l)) {
struct a2dp_setup_cb *cb = l->data;
- if (cb->id == id) {
- cb_data = cb;
- break;
+ if (cb->id != id)
+ continue;
+
+ setup_ref(setup);
+ setup_cb_free(cb);
+
+ if (!setup->cb) {
+ DBG("aborting setup %p", setup);
+ avdtp_abort(setup->session, setup->stream);
+ return TRUE;
}
- }
- if (!cb_data) {
- error("a2dp_cancel: no matching callback with id %u", id);
- return FALSE;
- }
-
- setup->cb = g_slist_remove(setup->cb, cb_data);
- g_free(cb_data);
-
- if (setup->cb)
+ setup_unref(setup);
return TRUE;
+ }
- avdtp_abort(setup->session, setup->stream);
-
- return TRUE;
+ return FALSE;
}
gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)