All of lore.kernel.org
 help / color / mirror / Atom feed
From: Denis Kenzior <denkenz@gmail.com>
To: ell@lists.01.org
Subject: Re: [RFC PATCH 2/8] dbus: Separate interface registration from instantiation
Date: Tue, 26 Jan 2016 15:13:58 -0600	[thread overview]
Message-ID: <56A7E196.70501@gmail.com> (raw)
In-Reply-To: <1453517972-22833-2-git-send-email-andrew.zaborowski@intel.com>

[-- Attachment #1: Type: text/plain, Size: 18615 bytes --]

Hi Andrew,

On 01/22/2016 08:59 PM, Andrew Zaborowski wrote:
> Currently l_dbus_register_interface performs three related actions:
>   * if needed, creates a new object node in the dbus object tree
>   * if needed, sets up internal structs for the new interface
>   * adds the interface to the object
>
> With this patch these are three spearate calls, although the first
> is still performed automatically by l_dbus_add_interface if
> l_dbus_register_object wasn't called first.  This is in preparation for
> ObjectManager support.  With this the setup_func parameter and new
> interface parameters don't need to be passed every time an interface is
> instiated, only when it's being registered/created.
>
> One thing that may be unclear is that as a side effect
> _dbus_object_tree_prune_node takes not only the reference to the node
> being pruned but also to the object tree and a path to the object.  The
> latter two are needed because when the function is walking the tree
> upwards to free any intermediate nodes that no longer have any
> dbus-visible objects in their subtree, the function needs to be able to
> check if these nodes are visible on dbus (i.e. are in tree->objects).  I
> found that currently if you've got a tree with objects /a and /a/b in
> it, and remove all interfaces from /a first, then from /a/b, the
> _dbus_object_tree_prune_node call will free /a without removing it from
> tree->objects.  The node reference parameter is no longer needed except
> for the unit/test-dbus-service to be able to test adding and removing
> intermediate nodes from the tree.

If we register object on '/a', then register object on '/a/b'.  We have 
two objects in the tree->objects hashmap.  If we unregister object '/a' 
first, then tree->objects will no longer contain '/a'.  Then 
unregistering '/a/b' will prune the tree upwards.

Tree pruning is intended to be fully separate from the object hash, so 
I'm really not following.

Perhaps you're conflating unregistering all interfaces vs unregistering 
the object?  But then you mention that your semantics of not removing 
the object from the tree if all interfaces are removed is intentional in 
the next paragraph.

>
> Note that while the client doesn't need to call l_dbus_register_object,
> they still need to call l_dbus_unregister_object to free the object
> because it's not freed automatically when the last interface gets
> removed.  But they can skip the l_dbus_remove_interface calls
> because the interfaces will be removed either way.
> ---
>   ell/dbus-private.h       |  20 ++++-
>   ell/dbus-service.c       | 224 +++++++++++++++++++++++++++++++++++------------
>   ell/dbus.c               |  91 +++++++++++++++++--
>   ell/dbus.h               |  19 ++--
>   examples/dbus-service.c  |  13 ++-
>   unit/test-dbus-service.c |  17 ++--
>   unit/test-kdbus.c        |   9 +-
>   7 files changed, 311 insertions(+), 82 deletions(-)
>
> diff --git a/ell/dbus-private.h b/ell/dbus-private.h
> index e60d09e..fc26aa2 100644
> --- a/ell/dbus-private.h
> +++ b/ell/dbus-private.h
> @@ -167,13 +167,27 @@ struct object_node *_dbus_object_tree_makepath(struct _dbus_object_tree *tree,
>   						const char *path);
>   struct object_node *_dbus_object_tree_lookup(struct _dbus_object_tree *tree,
>   						const char *path);
> -void _dbus_object_tree_prune_node(struct object_node *node);
> +
> +struct object_node *_dbus_object_tree_new_object(struct _dbus_object_tree *tree,
> +						const char *path,
> +						void *user_data,
> +						void (*destroy) (void *));
> +bool _dbus_object_tree_prune_node(struct _dbus_object_tree *tree,
> +					struct object_node *node,
> +					const char *path);
>
>   bool _dbus_object_tree_register(struct _dbus_object_tree *tree,
> -				const char *path, const char *interface,
> +				const char *interface,
>   				void (*setup_func)(struct l_dbus_interface *),
> -				void *user_data, void (*destroy) (void *));
> +				void (*destroy) (void *),
> +				bool old_style_properties);

Should this be called _dbus_object_tree_register_interface now?  Since 
it only deals with interfaces and not object paths?

>   bool _dbus_object_tree_unregister(struct _dbus_object_tree *tree,
> +				const char *interface);
> +

Similarly, should this be _dbus_object_tree_unregister_interface?

> +bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree,
> +					const char *path, const char *interface,
> +					void *user_data);
> +bool _dbus_object_tree_remove_interface(struct _dbus_object_tree *tree,
>   					const char *path,
>   					const char *interface);
>
> diff --git a/ell/dbus-service.c b/ell/dbus-service.c
> index 77310c6..d37989e 100644
> --- a/ell/dbus-service.c
> +++ b/ell/dbus-service.c
> @@ -72,6 +72,9 @@ struct l_dbus_interface {
>   	struct l_queue *methods;
>   	struct l_queue *signals;
>   	struct l_queue *properties;
> +	bool handle_old_style_properties;
> +	void (*setup_func)(struct l_dbus_interface *);
> +	void (*instance_destroy)(void *);
>   	char name[];
>   };
>
> @@ -84,13 +87,14 @@ struct child_node {
>   struct interface_instance {
>   	struct l_dbus_interface *interface;
>   	void *user_data;
> -	void (*user_destroy) (void *);
>   };
>
>   struct object_node {
>   	struct object_node *parent;
>   	struct l_queue *instances;
>   	struct child_node *children;
> +	void *user_data;
> +	void (*destroy) (void *);
>   };
>
>   struct _dbus_object_tree {
> @@ -489,8 +493,8 @@ struct _dbus_property *_dbus_interface_find_property(struct l_dbus_interface *i,
>
>   static void interface_instance_free(struct interface_instance *instance)
>   {
> -	if (instance->user_destroy)
> -		instance->user_destroy(instance->user_data);
> +	if (instance->interface->instance_destroy)
> +		instance->interface->instance_destroy(instance->user_data);
>
>   	l_free(instance);
>   }
> @@ -539,6 +543,9 @@ static void subtree_free(struct object_node *node)
>   	l_queue_destroy(node->instances,
>   			(l_queue_destroy_func_t) interface_instance_free);
>
> +	if (node->destroy)
> +		node->destroy(node->user_data);
> +
>   	l_free(node);
>   }
>
> @@ -626,81 +633,197 @@ struct object_node *_dbus_object_tree_lookup(struct _dbus_object_tree *tree,
>   	return lookup_recurse(tree->root, path);
>   }
>
> -void _dbus_object_tree_prune_node(struct object_node *node)
> +struct object_node *_dbus_object_tree_new_object(struct _dbus_object_tree *tree,
> +						const char *path,
> +						void *user_data,
> +						void (*destroy) (void *))
> +{
> +	struct object_node *node;
> +
> +	if (!_dbus_valid_object_path(path))
> +		return NULL;
> +
> +	if (l_hashmap_lookup(tree->objects, path))
> +		return NULL;
> +
> +	node = _dbus_object_tree_makepath(tree, path);
> +	node->user_data = user_data;
> +	node->destroy = destroy;
> +
> +	l_hashmap_insert(tree->objects, path, node);
> +
> +	return node;
> +}
> +
> +bool _dbus_object_tree_prune_node(struct _dbus_object_tree *tree,
> +					struct object_node *node,
> +					const char *path)

This part sounds like it really belongs in a separate patch...

>   {
> -	struct object_node *parent = node->parent;
> -	struct child_node *p = NULL, *c;
> +	struct object_node *parent;
> +	struct child_node *p, *c;
> +	struct interface_instance *instance;
> +	char parentpath[strlen(path) + 1];
> +
> +	if (!node) {
> +		node = l_hashmap_remove(tree->objects, path);
> +		if (!node)
> +			return false;
> +	}
> +
> +	while ((instance = l_queue_pop_head(node->instances)))
> +		interface_instance_free(instance);
> +
> +	if (node->destroy) {
> +		node->destroy(node->user_data);
>
> -	while (parent) {
> -		for (c = parent->children, p = NULL; c; p = c, c = c->next) {
> -			if (c->node != node)
> -				continue;
> +		node->destroy = NULL;
> +	}
>
> -			if (p)
> -				p->next = c->next;
> -			else
> -				parent->children = c->next;
> +	if (node->children || !node->parent)
> +		return true;
>
> -			subtree_free(c->node);
> -			l_free(c);
> +	/*
> +	 * Walk up the tree until a node that either has more than one
> +	 * child, is the root or is in the objects hashmap.
> +	 */
> +	strcpy(parentpath, path);
>
> +	while (true) {
> +		parent = node->parent;
> +
> +		if (parent == tree->root)
> +			break;
> +
> +		if (parent->children->next)
>   			break;
> -		}
>
> -		if (parent->children != NULL)
> -			return;
> +		/* Parent's path */
> +		parentpath[strlen(parentpath) -
> +			strlen(parent->children->subpath) - 1] = '\0';
> +
> +		if (l_hashmap_lookup(tree->objects, parentpath))
> +			break;
>
>   		node = parent;
> -		parent = node->parent;
>   	}
> +
> +	for (c = parent->children, p = NULL; c; p = c, c = c->next)
> +		if (c->node == node)
> +			break;
> +
> +	if (p)
> +		p->next = c->next;
> +	else
> +		parent->children = c->next;
> +
> +	l_free(c);
> +	subtree_free(node);
> +
> +	return true;
>   }
>
>   bool _dbus_object_tree_register(struct _dbus_object_tree *tree,
> -				const char *path, const char *interface,
> +				const char *interface,
>   				void (*setup_func)(struct l_dbus_interface *),
> -				void *user_data, void (*destroy) (void *))
> +				void (*destroy) (void *),
> +				bool old_style_properties)
>   {
> -	struct object_node *object;
>   	struct l_dbus_interface *dbi;
> -	const struct l_queue_entry *entry;
> -	struct interface_instance *instance;
>
>   	if (!_dbus_valid_interface(interface))
>   		return false;
>
> -	if (!_dbus_valid_object_path(path))
> +	/*
> +	 * Check to make sure we do not have this interface already
> +	 * registered
> +	 */
> +	dbi = l_hashmap_lookup(tree->interfaces, interface);
> +	if (dbi)
> +		return false;
> +
> +	dbi = _dbus_interface_new(interface);
> +	dbi->instance_destroy = destroy;
> +	dbi->setup_func = setup_func;

Can't we just call this right away and not wait for first use?  Let the 
programmer decide when to instantiate the interface.

> +	dbi->handle_old_style_properties = old_style_properties;
> +
> +	l_hashmap_insert(tree->interfaces, dbi->name, dbi);
> +
> +	return true;
> +}
> +
> +struct interface_check {
> +	const char *interface;
> +	bool result;
> +};
> +
> +static void check_interface_used(const void *key, void *value, void *user_data)
> +{
> +	struct object_node *node = value;
> +	struct interface_check *state = user_data;
> +
> +	if (l_queue_find(node->instances, match_interface_instance,
> +				(char *) state->interface))
> +		state->result = true;
> +}
> +
> +bool _dbus_object_tree_unregister(struct _dbus_object_tree *tree,
> +					const char *interface_name)
> +{
> +	struct l_dbus_interface *interface;
> +	struct interface_check state = { interface_name, false };
> +
> +	interface = l_hashmap_lookup(tree->interfaces, interface_name);
> +	if (!interface)
> +		return false;
> +
> +	/* Check that the interface is not in use */
> +	l_hashmap_foreach(tree->objects, check_interface_used, &state);
> +	if (state.result)
> +		return false;

I'm not sure this is useful in its current form.  If we want to have 
this, then we probably need to distinguish between telling the 
programmer that "you're trying to unregister an unknown interface" vs 
"you're trying to unregister a interface that is still being used".  As 
an alternative, you can keep a reference count on the interface itself 
and keep the object around until all instances have been destroyed, and 
this function would always succeed...

However, I would keep it simple and just let the programmer control 
this.  If they try to remove an interface that is still being used, 
things will crash spectacularly and valgrind/gdb will find it very fast. 
  Remember our motto, "crash early, crash often" :)

> +
> +	l_hashmap_remove(tree->interfaces, interface_name);
> +
> +	_dbus_interface_free(interface);
> +
> +	return true;
> +}
> +
> +bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree,
> +					const char *path, const char *interface,
> +					void *user_data)
> +{
> +	struct object_node *object;
> +	struct l_dbus_interface *dbi;
> +	struct interface_instance *instance;
> +
> +	dbi = l_hashmap_lookup(tree->interfaces, interface);
> +	if (!dbi)
>   		return false;
>
>   	object = l_hashmap_lookup(tree->objects, path);
>   	if (!object) {
> -		object = _dbus_object_tree_makepath(tree, path);
> -		l_hashmap_insert(tree->objects, path, object);
> +		object = _dbus_object_tree_new_object(tree, path, NULL, NULL);
> +
> +		if (!object)
> +			return false;
>   	}
>
>   	/*
>   	 * Check to make sure we do not have this interface already
>   	 * registered for this object
>   	 */
> -	entry = l_queue_get_entries(object->instances);
> -	while (entry) {
> -		instance = entry->data;
> -
> -		if (!strcmp(instance->interface->name, interface))
> -			return false;
> +	if (l_queue_find(object->instances, match_interface_instance,
> +				(char *) interface))
> +		return false;
>
> -		entry = entry->next;
> -	}
> +	if (dbi->setup_func) {
> +		dbi->setup_func(dbi);
>

See my comments above...

> -	dbi = l_hashmap_lookup(tree->interfaces, interface);
> -	if (!dbi) {
> -		dbi = _dbus_interface_new(interface);
> -		setup_func(dbi);
> -		l_hashmap_insert(tree->interfaces, dbi->name, dbi);
> +		dbi->setup_func = NULL;
>   	}
>
>   	instance = l_new(struct interface_instance, 1);
>   	instance->interface = dbi;
> -	instance->user_destroy = destroy;
>   	instance->user_data = user_data;
>
>   	if (!object->instances)
> @@ -711,13 +834,12 @@ bool _dbus_object_tree_register(struct _dbus_object_tree *tree,
>   	return true;
>   }
>
> -bool _dbus_object_tree_unregister(struct _dbus_object_tree *tree,
> +bool _dbus_object_tree_remove_interface(struct _dbus_object_tree *tree,
>   					const char *path,
>   					const char *interface)
>   {
>   	struct object_node *node;
>   	struct interface_instance *instance;
> -	bool r;
>
>   	node = l_hashmap_lookup(tree->objects, path);
>   	if (!node)
> @@ -725,18 +847,12 @@ bool _dbus_object_tree_unregister(struct _dbus_object_tree *tree,
>
>   	instance = l_queue_remove_if(node->instances,
>   			match_interface_instance, (char *) interface);
> +	if (!instance)
> +		return false;
>
> -	r = instance ? true : false;
> -
> -	if (instance)
> -		interface_instance_free(instance);
> -
> -	if (l_queue_isempty(node->instances) && !node->children) {
> -		l_hashmap_remove(tree->objects, path);
> -		_dbus_object_tree_prune_node(node);
> -	}
> +	interface_instance_free(instance);
>
> -	return r;
> +	return true;
>   }
>
>   static void generate_interface_instance(void *data, void *user)
> diff --git a/ell/dbus.c b/ell/dbus.c
> index 1d9aed5..a9bb314 100644
> --- a/ell/dbus.c
> +++ b/ell/dbus.c

<snip>

> +
> +LIB_EXPORT bool l_dbus_register_object(struct l_dbus *dbus, const char *path,
> +					void *user_data,
> +					l_dbus_destroy_func_t destroy, ...)
> +{

We've talked about this before, but this one really needs a 
documentation blurb or an additional example of the expected syntax.

e.g. l_dbus_register_object(dbus, "/foo", object, destroy_object, 
"FooInterface", foo_data, "BarInterface", bar_data, NULL);

> +	va_list args;
> +	const char *interface;
> +	void *if_user_data;
> +	bool r = true;;
> +
> +	if (unlikely(!dbus))
> +		return false;
> +
> +	if (unlikely(!dbus->tree))
> +		return false;
> +
> +	if (!_dbus_object_tree_new_object(dbus->tree, path, user_data, destroy))
> +		return false;
> +
> +	va_start(args, destroy);
> +	while ((interface = va_arg(args, const char *))) {
> +		if_user_data = va_arg(args, void *);
> +
> +		if (!_dbus_object_tree_add_interface(dbus->tree, path,
> +							interface,
> +							if_user_data)) {
> +			_dbus_object_tree_prune_node(dbus->tree, NULL, path);
> +			r = false;
> +
> +			break;
> +		}
> +	}
> +	va_end(args);
> +
> +	return r;
> +}
> +

<snip>

> diff --git a/unit/test-dbus-service.c b/unit/test-dbus-service.c
> index d0bf174..16fc00e 100644
> --- a/unit/test-dbus-service.c
> +++ b/unit/test-dbus-service.c
> @@ -205,16 +205,16 @@ static void test_dbus_object_tree(const void *test_data)
>
>   	tmp = _dbus_object_tree_lookup(tree, "/foo/bee");
>   	assert(tmp);
> -	_dbus_object_tree_prune_node(leaf3);
> +	_dbus_object_tree_prune_node(tree, leaf3, "/foo/bee/boo");
>   	tmp = _dbus_object_tree_lookup(tree, "/foo/bee");
>   	assert(!tmp);
>
>   	tmp = _dbus_object_tree_lookup(tree, "/foo/bar");
>   	assert(tmp);
> -	_dbus_object_tree_prune_node(leaf2);
> +	_dbus_object_tree_prune_node(tree, leaf2, "/foo/bar/ble");
>   	tmp = _dbus_object_tree_lookup(tree, "/foo/bar");
>   	assert(tmp);
> -	_dbus_object_tree_prune_node(leaf1);
> +	_dbus_object_tree_prune_node(tree, leaf1, "/foo/bar/baz");
>   	tmp = _dbus_object_tree_lookup(tree, "/foo/bar");
>   	assert(!tmp);
>   	tmp = _dbus_object_tree_lookup(tree, "/foo");
> @@ -276,9 +276,10 @@ static void test_dbus_object_tree_introspection(const void *test_data)
>
>   	tree = _dbus_object_tree_new();
>
> -	_dbus_object_tree_register(tree, "/", "org.ofono.Manager",
> +	_dbus_object_tree_register(tree, "org.ofono.Manager",
>   					build_manager_interface,
> -					NULL, NULL);
> +					NULL, false);
> +	_dbus_object_tree_add_interface(tree, "/", "org.ofono.Manager", NULL);
>
>   	_dbus_object_tree_makepath(tree, "/phonesim");
>
> @@ -298,9 +299,11 @@ static void test_dbus_object_tree_dispatch(const void *test_data)
>
>   	tree = _dbus_object_tree_new();
>
> -	_dbus_object_tree_register(tree, "/", "org.ofono.Manager",
> +	_dbus_object_tree_register(tree, "org.ofono.Manager",
>   					build_manager_interface,
> -					dummy_data, NULL);
> +					NULL, false);
> +	_dbus_object_tree_add_interface(tree, "/", "org.ofono.Manager",
> +					dummy_data);
>
>   	message = _dbus_message_new_method_call(1, "org.ofono", "/",
>   						"org.ofono.Manager",

<snip>

By the way, applying this patch makes unit tests fail:
denkenz(a)new-host-2 ~/ell $ unit/test-dbus-service
TEST: Test Frobate Introspection
TEST: Test Bazify Introspection
TEST: Test Mogrify Introspection
TEST: Test Changed Introspection
TEST: Test Bar Property Introspection
test-dbus-service: unit/test-dbus-service.c:157: 
test_introspect_property: Assertion `!strcmp(test->expected_xml, xml)' 
failed.
Aborted

Regards,
-Denis

  reply	other threads:[~2016-01-26 21:13 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-01-23  2:59 [RFC PATCH 1/8] dbus: setters and getters API for properties Andrew Zaborowski
2016-01-23  2:59 ` [RFC PATCH 2/8] dbus: Separate interface registration from instantiation Andrew Zaborowski
2016-01-26 21:13   ` Denis Kenzior [this message]
2016-01-26 23:40     ` Andrzej Zaborowski
2016-01-27  0:16       ` Denis Kenzior
2016-01-23  2:59 ` [RFC PATCH 3/8] dbus: Don't show Introspectable on intermediate nodes Andrew Zaborowski
2016-01-25 15:48   ` Denis Kenzior
2016-01-23  2:59 ` [RFC PATCH 4/8] dbus: Message builder function to copy from an iter Andrew Zaborowski
2016-01-23  2:59 ` [RFC PATCH 5/8] dbus: Private function to retrieve the tree for a connection Andrew Zaborowski
2016-01-23  2:59 ` [RFC PATCH 6/8] dbus: Handle legacy GetProperties and SetProperty automatically Andrew Zaborowski
2016-01-29  0:16   ` Denis Kenzior
2016-01-23  2:59 ` [RFC PATCH 7/8] dbus: Implement org.freedesktop.DBus.Properties Andrew Zaborowski
2016-01-23  2:59 ` [RFC PATCH 8/8] dbus: Implement org.freedesktop.DBus.ObjectManager Andrew Zaborowski
2016-01-29  3:35   ` Denis Kenzior
2016-01-29 16:47     ` Andrzej Zaborowski
2016-01-29 17:44       ` Denis Kenzior
2016-01-29 18:53         ` Andrzej Zaborowski
2016-01-29 19:19           ` Denis Kenzior

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=56A7E196.70501@gmail.com \
    --to=denkenz@gmail.com \
    --cc=ell@lists.01.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.