diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c index 3d6d0a93ead7305a96f06a9a83ba361c13a38902..df00742e1e49d35a9b5cac298e43d9383985a89c 100644 --- a/lib/wp/endpoint.c +++ b/lib/wp/endpoint.c @@ -682,6 +682,19 @@ on_stream_flags_changed (WpSessionItem * stream, WpSiFlags flags, { /* stream was deactivated; destroy the associated link */ if (!(flags & WP_SI_FLAG_ACTIVE)) { + wp_trace_object (link, "destroying because stream " WP_OBJECT_FORMAT + " was deactivated", WP_OBJECT_ARGS (stream)); + wp_session_item_reset (link); + g_object_unref (link); + } +} + +static void +on_link_flags_changed (WpSessionItem * link, WpSiFlags flags, gpointer data) +{ + const guint mask = (WP_SI_FLAG_EXPORTED | WP_SI_FLAG_EXPORT_ERROR); + if ((flags & mask) == mask) { + wp_trace_object (link, "destroying because impl proxy was destroyed"); wp_session_item_reset (link); g_object_unref (link); } @@ -855,6 +868,8 @@ impl_create_link (void *object, const struct spa_dict *props) G_CALLBACK (on_stream_flags_changed), link, 0); g_signal_connect_object (peer_si_stream, "flags-changed", G_CALLBACK (on_stream_flags_changed), link, 0); + g_signal_connect (link, "flags-changed", + G_CALLBACK (on_link_flags_changed), NULL); wp_session_item_export (link, session, (GAsyncReadyCallback) on_si_link_exported, self); diff --git a/lib/wp/session-item.c b/lib/wp/session-item.c index 906ba69e85dbb594baa08cf56a737336ccd118aa..a5b21800fd212960442b53f5b264f8086b359a8b 100644 --- a/lib/wp/session-item.c +++ b/lib/wp/session-item.c @@ -190,6 +190,7 @@ enum { EXPORT_STEP_ENDPOINT = WP_TRANSITION_STEP_CUSTOM_START, EXPORT_STEP_STREAMS, EXPORT_STEP_LINK, + EXPORT_STEP_CONNECT_DESTROYED, }; static guint @@ -229,6 +230,9 @@ wp_session_item_default_export_get_next_step (WpSessionItem * self, case EXPORT_STEP_LINK: g_return_val_if_fail (WP_IS_SI_LINK (self), WP_TRANSITION_STEP_ERROR); + return EXPORT_STEP_CONNECT_DESTROYED; + + case EXPORT_STEP_CONNECT_DESTROYED: return WP_TRANSITION_STEP_NONE; default: @@ -261,6 +265,36 @@ on_export_proxy_augmented (WpProxy * proxy, GAsyncResult * res, gpointer data) wp_transition_advance (transition); } +static gboolean +on_export_proxy_destroyed_deferred (WpSessionItem * self) +{ + WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); + + g_return_val_if_fail (priv->impl_proxy, G_SOURCE_REMOVE); + g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->export_rollback, + G_SOURCE_REMOVE); + + wp_info_object (self, "destroying " WP_OBJECT_FORMAT + " upon request by the server", WP_OBJECT_ARGS (priv->impl_proxy)); + + WP_SESSION_ITEM_GET_CLASS (self)->export_rollback (self); + + priv->flags |= WP_SI_FLAG_EXPORT_ERROR; + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); + + return G_SOURCE_REMOVE; +} + +static void +on_export_proxy_destroyed (WpProxy * proxy, gpointer data) +{ + WpSessionItem *self = WP_SESSION_ITEM (data); + g_autoptr (WpCore) core = wp_proxy_get_core (proxy); + + wp_core_idle_add_closure (core, NULL, g_cclosure_new_object ( + G_CALLBACK (on_export_proxy_destroyed_deferred), G_OBJECT (self))); +} + static void wp_session_item_default_export_execute_step (WpSessionItem * self, WpTransition * transition, guint step) @@ -311,6 +345,12 @@ wp_session_item_default_export_execute_step (WpSessionItem * self, transition); break; + case EXPORT_STEP_CONNECT_DESTROYED: + g_signal_connect_object (priv->impl_proxy, "pw-proxy-destroyed", + G_CALLBACK (on_export_proxy_destroyed), self, 0); + wp_transition_advance (transition); + break; + default: g_return_if_reached (); } @@ -320,6 +360,8 @@ static void wp_session_item_default_export_rollback (WpSessionItem * self) { WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self); + if (priv->impl_proxy) + g_signal_handlers_disconnect_by_data (priv->impl_proxy, self); g_clear_pointer (&priv->impl_streams, g_hash_table_unref); g_clear_object (&priv->impl_proxy); g_weak_ref_set (&priv->session, NULL); diff --git a/tests/modules/si-standard-link.c b/tests/modules/si-standard-link.c index 5933f26f09f1015f7e7defcb55b3aa34937bea11..08903bd5c872a271e97ea44e8e23e5a8bf94d0eb 100644 --- a/tests/modules/si-standard-link.c +++ b/tests/modules/si-standard-link.c @@ -235,6 +235,7 @@ test_si_standard_link_main (TestFixture * f, gconstpointer user_data) g_assert_cmpuint (wp_endpoint_link_get_state (ep_link, &error), ==, WP_ENDPOINT_LINK_STATE_ACTIVE); g_assert_null (error); + g_assert_cmpint (f->activation_state, ==, 2); } /* verify the graph state */ @@ -290,6 +291,7 @@ test_si_standard_link_main (TestFixture * f, gconstpointer user_data) g_assert_cmpuint (wp_endpoint_link_get_state (ep_link, &error), ==, WP_ENDPOINT_LINK_STATE_INACTIVE); g_assert_null (error); + g_assert_cmpint (f->activation_state, ==, 3); } /* verify the graph state */ @@ -328,6 +330,110 @@ test_si_standard_link_main (TestFixture * f, gconstpointer user_data) } } +static void +on_link_destroyed (WpEndpointLink * link, TestFixture * f) +{ + f->activation_state = 10; +} + +static void +test_si_standard_link_destroy (TestFixture * f, gconstpointer user_data) +{ + g_autoptr (WpSession) session_proxy = NULL; + g_autoptr (WpEndpoint) src_ep = NULL; + g_autoptr (WpEndpoint) sink_ep = NULL; + g_autoptr (WpEndpointLink) ep_link = NULL; + + /* find the "audio" session from the client */ + { + g_autoptr (WpObjectManager) om = wp_object_manager_new (); + wp_object_manager_add_interest_1 (om, WP_TYPE_SESSION, NULL); + wp_object_manager_request_proxy_features (om, WP_TYPE_SESSION, + WP_SESSION_FEATURES_STANDARD); + g_signal_connect_swapped (om, "installed", + G_CALLBACK (g_main_loop_quit), f->base.loop); + wp_core_install_object_manager (f->base.client_core, om); + g_main_loop_run (f->base.loop); + + g_assert_nonnull (session_proxy = + wp_object_manager_lookup (om, WP_TYPE_SESSION, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "session.name", "=s", "audio", NULL)); + g_assert_cmpint (wp_proxy_get_bound_id (WP_PROXY (session_proxy)), ==, + wp_proxy_get_bound_id (WP_PROXY (f->session))); + } + + /* find the endpoints */ + + g_assert_nonnull (src_ep = wp_session_lookup_endpoint (session_proxy, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "endpoint.name", "=s", "audiotestsrc", + NULL)); + g_assert_nonnull (sink_ep = wp_session_lookup_endpoint (session_proxy, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "endpoint.name", "=s", "fakesink", + NULL)); + g_assert_cmpuint (wp_endpoint_get_n_streams (src_ep), ==, 1); + g_assert_cmpuint (wp_endpoint_get_n_streams (sink_ep), ==, 1); + + /* create the link */ + { + g_autoptr (WpProperties) props = NULL; + g_autofree gchar * id = + g_strdup_printf ("%u", wp_proxy_get_bound_id (WP_PROXY (sink_ep))); + + /* only the peer endpoint id is required, + everything else will be discovered */ + props = wp_properties_new ("endpoint-link.input.endpoint", id, NULL); + wp_endpoint_create_link (src_ep, props); + } + + g_signal_connect_swapped (session_proxy, "links-changed", + G_CALLBACK (g_main_loop_quit), f->base.loop); + g_main_loop_run (f->base.loop); + + /* verify */ + + g_assert_cmpuint (wp_session_get_n_links (session_proxy), ==, 1); + g_assert_nonnull (ep_link = wp_session_lookup_link (session_proxy, NULL)); + g_assert_cmpuint (wp_endpoint_link_get_state (ep_link, NULL), ==, + WP_ENDPOINT_LINK_STATE_INACTIVE); + + /* activate */ + + g_signal_connect (ep_link, "state-changed", + G_CALLBACK (on_link_state_changed), f); + wp_endpoint_link_request_state (ep_link, WP_ENDPOINT_LINK_STATE_ACTIVE); + g_main_loop_run (f->base.loop); + g_assert_cmpuint (wp_endpoint_link_get_state (ep_link, NULL), ==, + WP_ENDPOINT_LINK_STATE_ACTIVE); + + /* destroy */ + + g_signal_connect (ep_link, "pw-proxy-destroyed", + G_CALLBACK (on_link_destroyed), f); + wp_proxy_request_destroy (WP_PROXY (ep_link)); + + /* loop will quit because the "links-changed" signal from the session + is still connected to quit() from earlier */ + g_main_loop_run (f->base.loop); + + g_assert_cmpint (f->activation_state, ==, 10); + g_assert_cmpuint (wp_session_get_n_links (session_proxy), ==, 0); + g_assert_cmpuint (wp_proxy_get_bound_id (WP_PROXY (ep_link)), ==, (guint) -1); + + /* verify the link was also destroyed on the session manager core */ + { + g_autoptr (WpObjectManager) om = wp_object_manager_new (); + + wp_object_manager_add_interest_1 (om, WP_TYPE_ENDPOINT_LINK, NULL); + g_signal_connect_swapped (om, "installed", + G_CALLBACK (g_main_loop_quit), f->base.loop); + wp_core_install_object_manager (f->base.core, om); + if (!wp_object_manager_is_installed (om)) + g_main_loop_run (f->base.loop); + + g_assert_cmpuint (wp_object_manager_get_n_objects (om), ==, 0); + } +} + gint main (gint argc, gchar *argv[]) { @@ -341,5 +447,11 @@ main (gint argc, gchar *argv[]) test_si_standard_link_main, test_si_standard_link_teardown); + g_test_add ("/modules/si-standard-link/destroy", + TestFixture, NULL, + test_si_standard_link_setup, + test_si_standard_link_destroy, + test_si_standard_link_teardown); + return g_test_run (); }