diff mbox series

[1/4] qom: TYPE_SINGLETON interface

Message ID 20241024165627.1372621-2-peterx@redhat.com (mailing list archive)
State New
Headers show
Series QOM: Singleton interface | expand

Commit Message

Peter Xu Oct. 24, 2024, 4:56 p.m. UTC
Signed-off-by: Peter Xu <peterx@redhat.com>
---
 include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
 qom/object.c                    |  3 +++
 qom/object_interfaces.c         | 24 +++++++++++++++++
 qom/qom-qmp-cmds.c              | 22 ++++++++++++---
 system/qdev-monitor.c           |  7 +++++
 5 files changed, 100 insertions(+), 3 deletions(-)

Comments

Philippe Mathieu-Daudé Oct. 24, 2024, 8:02 p.m. UTC | #1
Hi Peter,

(Cc'ing Mark)

On 24/10/24 13:56, Peter Xu wrote:
> Signed-off-by: Peter Xu <peterx@redhat.com>
> ---
>   include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
>   qom/object.c                    |  3 +++
>   qom/object_interfaces.c         | 24 +++++++++++++++++
>   qom/qom-qmp-cmds.c              | 22 ++++++++++++---
>   system/qdev-monitor.c           |  7 +++++
>   5 files changed, 100 insertions(+), 3 deletions(-)


> +/**
> + * SingletonClass:
> + *
> + * @parent_class: the base class
> + * @get_instance: fetch the singleton instance if it is created,
> + *                NULL otherwise.
> + *
> + * Singleton class describes the type of object classes that can only
> + * provide one instance for the whole lifecycle of QEMU.  It will fail the
> + * operation if one attemps to create more than one instance.
> + *
> + * One can fetch the single object using class's get_instance() callback if
> + * it was created before.  This can be useful for operations like QMP
> + * qom-list-properties, where dynamically creating an object might not be
> + * feasible.
> + */
> +struct SingletonClass {
> +    /* <private> */
> +    InterfaceClass parent_class;
> +    /* <public> */
> +    Object *(*get_instance)(Error **errp);

IMHO asking get_instance() is overkill ...

> +};
> +
> +/**
> + * object_class_is_singleton:
> + *
> + * @class: the class to detect singleton
> + *
> + * Returns: true if it's a singleton class, false otherwise.
> + */
> +bool object_class_is_singleton(ObjectClass *class);
> +
> +/**
> + * singleton_get_instance:
> + *
> + * @class: the class to fetch singleton instance
> + *
> + * Returns: the object* if the class is a singleton class and the singleton
> + *          object is created, NULL otherwise.
> + */
> +Object *singleton_get_instance(ObjectClass *class);
> +
>   #endif

> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index e0833c8bfe..6766060d0a 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -354,6 +354,23 @@ void user_creatable_cleanup(void)
>       object_unparent(object_get_objects_root());
>   }
>   
> +bool object_class_is_singleton(ObjectClass *class)
> +{
> +    return !!object_class_dynamic_cast(class, TYPE_SINGLETON);
> +}
> +
> +Object *singleton_get_instance(ObjectClass *class)
> +{

... when we can use object_resolve_type_unambiguous:

       return 
object_resolve_type_unambiguous(object_class_get_name(class, NULL));

BTW should we pass Error** argument to singleton_get_instance()?

> +    SingletonClass *singleton =
> +        (SingletonClass *)object_class_dynamic_cast(class, TYPE_SINGLETON);
> +
> +    if (!singleton) {
> +        return NULL;
> +    }
> +
> +    return singleton->get_instance(&error_abort);
> +}

Alternatively call object_resolve_type_unambiguous() in instance_init()?

Regards,

Phil.
Peter Xu Oct. 24, 2024, 8:53 p.m. UTC | #2
On Thu, Oct 24, 2024 at 05:02:19PM -0300, Philippe Mathieu-Daudé wrote:
> Hi Peter,

Hi, Phil,

Thanks for the quick reviews!

> 
> (Cc'ing Mark)
> 
> On 24/10/24 13:56, Peter Xu wrote:
> > Signed-off-by: Peter Xu <peterx@redhat.com>
> > ---
> >   include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
> >   qom/object.c                    |  3 +++
> >   qom/object_interfaces.c         | 24 +++++++++++++++++
> >   qom/qom-qmp-cmds.c              | 22 ++++++++++++---
> >   system/qdev-monitor.c           |  7 +++++
> >   5 files changed, 100 insertions(+), 3 deletions(-)
> 
> 
> > +/**
> > + * SingletonClass:
> > + *
> > + * @parent_class: the base class
> > + * @get_instance: fetch the singleton instance if it is created,
> > + *                NULL otherwise.
> > + *
> > + * Singleton class describes the type of object classes that can only
> > + * provide one instance for the whole lifecycle of QEMU.  It will fail the
> > + * operation if one attemps to create more than one instance.
> > + *
> > + * One can fetch the single object using class's get_instance() callback if
> > + * it was created before.  This can be useful for operations like QMP
> > + * qom-list-properties, where dynamically creating an object might not be
> > + * feasible.
> > + */
> > +struct SingletonClass {
> > +    /* <private> */
> > +    InterfaceClass parent_class;
> > +    /* <public> */
> > +    Object *(*get_instance)(Error **errp);
> 
> IMHO asking get_instance() is overkill ...
> 
> > +};
> > +
> > +/**
> > + * object_class_is_singleton:
> > + *
> > + * @class: the class to detect singleton
> > + *
> > + * Returns: true if it's a singleton class, false otherwise.
> > + */
> > +bool object_class_is_singleton(ObjectClass *class);
> > +
> > +/**
> > + * singleton_get_instance:
> > + *
> > + * @class: the class to fetch singleton instance
> > + *
> > + * Returns: the object* if the class is a singleton class and the singleton
> > + *          object is created, NULL otherwise.
> > + */
> > +Object *singleton_get_instance(ObjectClass *class);
> > +
> >   #endif
> 
> > diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> > index e0833c8bfe..6766060d0a 100644
> > --- a/qom/object_interfaces.c
> > +++ b/qom/object_interfaces.c
> > @@ -354,6 +354,23 @@ void user_creatable_cleanup(void)
> >       object_unparent(object_get_objects_root());
> >   }
> > +bool object_class_is_singleton(ObjectClass *class)
> > +{
> > +    return !!object_class_dynamic_cast(class, TYPE_SINGLETON);
> > +}
> > +
> > +Object *singleton_get_instance(ObjectClass *class)
> > +{
> 
> ... when we can use object_resolve_type_unambiguous:
> 
>       return object_resolve_type_unambiguous(object_class_get_name(class,
> NULL));

I think an issue is migration object is nowhere to be find under
object_get_root(), so it won't work there.  A side benefit is, it's also
faster..

How about I use object_resolve_type_unambiguous() as a fallback?  Then it's
used only if get_instance() is not provided.

> 
> BTW should we pass Error** argument to singleton_get_instance()?

I didn't expect it to fail, hence I didn't even add it to make it more like
"this will always success or it asserts" kind of things.  I left Error** in
the hook just to be slightly flexible, but I always pass in error_abort()
in this patch.

I can either add Error** if anyone thinks it useful, or even drop Error**
in ->get_instance() since it's mostly not used at least for now.

Let me know!

> 
> > +    SingletonClass *singleton =
> > +        (SingletonClass *)object_class_dynamic_cast(class, TYPE_SINGLETON);
> > +
> > +    if (!singleton) {
> > +        return NULL;
> > +    }
> > +
> > +    return singleton->get_instance(&error_abort);
> > +}
> 
> Alternatively call object_resolve_type_unambiguous() in instance_init()?

Thanks,
Markus Armbruster Oct. 25, 2024, 8:07 a.m. UTC | #3
Peter Xu <peterx@redhat.com> writes:

> Signed-off-by: Peter Xu <peterx@redhat.com>
> ---
>  include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
>  qom/object.c                    |  3 +++
>  qom/object_interfaces.c         | 24 +++++++++++++++++
>  qom/qom-qmp-cmds.c              | 22 ++++++++++++---
>  system/qdev-monitor.c           |  7 +++++
>  5 files changed, 100 insertions(+), 3 deletions(-)
>
> diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
> index 02b11a7ef0..9b2cc0e554 100644
> --- a/include/qom/object_interfaces.h
> +++ b/include/qom/object_interfaces.h
> @@ -177,4 +177,51 @@ bool user_creatable_del(const char *id, Error **errp);
>   */
>  void user_creatable_cleanup(void);
>  
> +#define TYPE_SINGLETON "singleton"
> +
> +typedef struct SingletonClass SingletonClass;
> +DECLARE_CLASS_CHECKERS(SingletonClass, SINGLETON, TYPE_SINGLETON)
> +
> +/**
> + * SingletonClass:
> + *
> + * @parent_class: the base class
> + * @get_instance: fetch the singleton instance if it is created,
> + *                NULL otherwise.
> + *
> + * Singleton class describes the type of object classes that can only
> + * provide one instance for the whole lifecycle of QEMU.  It will fail the
> + * operation if one attemps to create more than one instance.
> + *
> + * One can fetch the single object using class's get_instance() callback if
> + * it was created before.  This can be useful for operations like QMP
> + * qom-list-properties, where dynamically creating an object might not be
> + * feasible.
> + */
> +struct SingletonClass {
> +    /* <private> */
> +    InterfaceClass parent_class;
> +    /* <public> */
> +    Object *(*get_instance)(Error **errp);
> +};
> +
> +/**
> + * object_class_is_singleton:
> + *
> + * @class: the class to detect singleton
> + *
> + * Returns: true if it's a singleton class, false otherwise.
> + */
> +bool object_class_is_singleton(ObjectClass *class);
> +
> +/**
> + * singleton_get_instance:
> + *
> + * @class: the class to fetch singleton instance
> + *
> + * Returns: the object* if the class is a singleton class and the singleton
> + *          object is created, NULL otherwise.
> + */
> +Object *singleton_get_instance(ObjectClass *class);

A non-null return value can become dangling when the object gets
destroyed.  This could conceivably happen in another thread.

The obviously safe interface would take a reference the caller must
unref when done.

> +
>  #endif
> diff --git a/qom/object.c b/qom/object.c
> index 11424cf471..ded299ae1a 100644
> --- a/qom/object.c
> +++ b/qom/object.c
> @@ -553,6 +553,9 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type
>      g_assert(type->abstract == false);
>      g_assert(size >= type->instance_size);
>  
> +    /* Singleton class can only create one object */
> +    g_assert(!singleton_get_instance(type->class));
> +
>      memset(obj, 0, type->instance_size);
>      obj->class = type->class;
>      object_ref(obj);
> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index e0833c8bfe..6766060d0a 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -354,6 +354,23 @@ void user_creatable_cleanup(void)
>      object_unparent(object_get_objects_root());
>  }
>  
> +bool object_class_is_singleton(ObjectClass *class)
> +{
> +    return !!object_class_dynamic_cast(class, TYPE_SINGLETON);
> +}
> +
> +Object *singleton_get_instance(ObjectClass *class)
> +{
> +    SingletonClass *singleton =
> +        (SingletonClass *)object_class_dynamic_cast(class, TYPE_SINGLETON);
> +
> +    if (!singleton) {
> +        return NULL;
> +    }
> +
> +    return singleton->get_instance(&error_abort);
> +}
> +
>  static void register_types(void)
>  {
>      static const TypeInfo uc_interface_info = {
> @@ -362,7 +379,14 @@ static void register_types(void)
>          .class_size = sizeof(UserCreatableClass),
>      };
>  
> +    static const TypeInfo singleton_interface_info = {
> +        .name          = TYPE_SINGLETON,
> +        .parent        = TYPE_INTERFACE,
> +        .class_size = sizeof(SingletonClass),
> +    };
> +
>      type_register_static(&uc_interface_info);
> +    type_register_static(&singleton_interface_info);
>  }
>  
>  type_init(register_types)
> diff --git a/qom/qom-qmp-cmds.c b/qom/qom-qmp-cmds.c
> index e91a235347..ecc1cf781c 100644
> --- a/qom/qom-qmp-cmds.c
> +++ b/qom/qom-qmp-cmds.c
> @@ -126,6 +126,7 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
>      ObjectProperty *prop;
>      ObjectPropertyIterator iter;
>      ObjectPropertyInfoList *prop_list = NULL;
> +    bool create;
>  
>      klass = module_object_class_by_name(typename);
>      if (klass == NULL) {
> @@ -141,7 +142,15 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
>          return NULL;
>      }
>  
> -    obj = object_new(typename);
> +    /* Avoid creating multiple instances if the class is a singleton */
> +    create = !object_class_is_singleton(klass) ||
> +        !singleton_get_instance(klass);
> +
> +    if (create) {
> +        obj = object_new(typename);

If the class is not a singleton or else if no instance exists, we create
a temporary instance.

> +    } else {
> +        obj = singleton_get_instance(klass);

If the class is a singleton and the instance exists, we use that
instead.

Any properties the instance has created dynamically after object_new()
are visible to introspection.  This is not the case when we create a
temporary instance.  Such subtle differences are problematic.  If we
decide we're okay with this one, we need to document it.

> +    }
>  
>      object_property_iter_init(&iter, obj);
>      while ((prop = object_property_iter_next(&iter))) {
> @@ -172,7 +181,9 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
>          QAPI_LIST_PREPEND(prop_list, info);
>      }
>  
> -    object_unref(obj);
> +    if (create) {
> +        object_unref(obj);
> +    }
>  
>      return prop_list;
>  }
> @@ -199,7 +210,12 @@ ObjectPropertyInfoList *qmp_qom_list_properties(const char *typename,
>          return NULL;
>      }
>  
> -    if (object_class_is_abstract(klass)) {
> +    /*
> +     * Abstract classes are not for instantiations, meanwhile avoid
> +     * creating temporary singleton objects because it can cause conflicts
> +     * if there's already one created.
> +     */
> +    if (object_class_is_abstract(klass) || object_class_is_singleton(klass)) {
>          object_class_property_iter_init(&iter, klass);
>      } else {
>          obj = object_new(typename);

If the class is a singleton, we treat it as if it was abstract.  Its
instance properties, if any, are not visible in qom-list-properties.
This is a defect.  If you make an existing class with instance
properties a singleton, the defect is a regression.

> diff --git a/system/qdev-monitor.c b/system/qdev-monitor.c
> index 44994ea0e1..1310f35c9f 100644
> --- a/system/qdev-monitor.c
> +++ b/system/qdev-monitor.c
> @@ -36,6 +36,7 @@
>  #include "qemu/option.h"
>  #include "qemu/qemu-print.h"
>  #include "qemu/option_int.h"
> +#include "qom/object_interfaces.h"
>  #include "sysemu/block-backend.h"
>  #include "migration/misc.h"
>  #include "qemu/cutils.h"
> @@ -643,6 +644,12 @@ DeviceState *qdev_device_add_from_qdict(const QDict *opts,
>          return NULL;
>      }
>  
> +    if (singleton_get_instance(OBJECT_CLASS(dc))) {
> +        error_setg(errp, "Class '%s' only supports one instance",
> +                   driver);
> +        return NULL;
> +    }
> +
>      /* find bus */
>      path = qdict_get_try_str(opts, "bus");
>      if (path != NULL) {
Daniel P. Berrangé Oct. 25, 2024, 9:51 a.m. UTC | #4
On Thu, Oct 24, 2024 at 12:56:24PM -0400, Peter Xu wrote:

Adding significant new functionality to QOM should really come
with a commit message explaining the rationale and the design
choices

> Signed-off-by: Peter Xu <peterx@redhat.com>
> ---
>  include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
>  qom/object.c                    |  3 +++
>  qom/object_interfaces.c         | 24 +++++++++++++++++
>  qom/qom-qmp-cmds.c              | 22 ++++++++++++---
>  system/qdev-monitor.c           |  7 +++++
>  5 files changed, 100 insertions(+), 3 deletions(-)

snip

> + * Singleton class describes the type of object classes that can only
> + * provide one instance for the whole lifecycle of QEMU.  It will fail the
> + * operation if one attemps to create more than one instance.
> + *
> + * One can fetch the single object using class's get_instance() callback if
> + * it was created before.  This can be useful for operations like QMP
> + * qom-list-properties, where dynamically creating an object might not be
> + * feasible.

snip

> +/**
> + * singleton_get_instance:
> + *
> + * @class: the class to fetch singleton instance
> + *
> + * Returns: the object* if the class is a singleton class and the singleton
> + *          object is created, NULL otherwise.
> + */
> +Object *singleton_get_instance(ObjectClass *class);

With this design, all code that uses a given type needs to know
whether or not it is intended to be a singleton. If some code
somewhere mistakenly calls 'object_new' instead of 'singleton_get_instance',
the singleton type  is no longer a singleton, except you handle this by
adding an assert in object_initialize_with_type.

This is still a bit of a loaded foot-gun IMHO, as we don't want random
code asserting.

> diff --git a/qom/object.c b/qom/object.c
> index 11424cf471..ded299ae1a 100644
> --- a/qom/object.c
> +++ b/qom/object.c
> @@ -553,6 +553,9 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type
>      g_assert(type->abstract == false);
>      g_assert(size >= type->instance_size);
>  
> +    /* Singleton class can only create one object */
> +    g_assert(!singleton_get_instance(type->class));
> +
>      memset(obj, 0, type->instance_size);
>      obj->class = type->class;
>      object_ref(obj);

> diff --git a/qom/qom-qmp-cmds.c b/qom/qom-qmp-cmds.c
> index e91a235347..ecc1cf781c 100644
> --- a/qom/qom-qmp-cmds.c
> +++ b/qom/qom-qmp-cmds.c
> @@ -126,6 +126,7 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
>      ObjectProperty *prop;
>      ObjectPropertyIterator iter;
>      ObjectPropertyInfoList *prop_list = NULL;
> +    bool create;
>  
>      klass = module_object_class_by_name(typename);
>      if (klass == NULL) {
> @@ -141,7 +142,15 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
>          return NULL;
>      }
>  
> -    obj = object_new(typename);
> +    /* Avoid creating multiple instances if the class is a singleton */
> +    create = !object_class_is_singleton(klass) ||
> +        !singleton_get_instance(klass);
> +
> +    if (create) {
> +        obj = object_new(typename);
> +    } else {
> +        obj = singleton_get_instance(klass);
> +    }

This is the first foot-gun example.

I really strongly dislike that the design pattern forces us to
create code like this, as we can never be confident we've
correctly identified all the places which may call object_new
on a singleton...

> @@ -172,7 +181,9 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
>          QAPI_LIST_PREPEND(prop_list, info);
>      }
>  
> -    object_unref(obj);
> +    if (create) {
> +        object_unref(obj);
> +    }

...and this just compounds the ugliness.

> @@ -199,7 +210,12 @@ ObjectPropertyInfoList *qmp_qom_list_properties(const char *typename,
>          return NULL;
>      }
>  
> -    if (object_class_is_abstract(klass)) {
> +    /*
> +     * Abstract classes are not for instantiations, meanwhile avoid
> +     * creating temporary singleton objects because it can cause conflicts
> +     * if there's already one created.
> +     */

Another example of the foot-gun firing at random code

> +    if (object_class_is_abstract(klass) || object_class_is_singleton(klass)) {
>          object_class_property_iter_init(&iter, klass);
>      } else {
>          obj = object_new(typename);


With changes to QOM, I think it is generally informative to look at how
GLib has handled the problem, since the QOM design has heavily borrowed
from its GObject design.

In GObject, singletons are handled in a very differnt way. It has a
concept of a "constructor" function against the class, which is what is
responsible for allocating the object. By default the 'constructor' will
call g_new0, but a class which wishes to become a singleton will override
the 'constructor' function to allocate on first call, and return the
cached object on subsequent calls. This is illustrated here:

  https://gitlab.gnome.org/GNOME/glib/-/blob/main/gobject/gobject.h#L297

The key benefit of this is that everything can carry on calling
g_object_new() as before, as it will just "do the right thing"
in terms of allocation.

In QOM, we don't have a 'constructor' class function, we just directly
call g_malloc from object_new_with_type. This is because at the time,
we didn't see an immediate need for it. We could easily change that
though to introduce the concept of a 'constructor', which could
probably make singletons work without needing updates to existing code.



With regards,
Daniel
Philippe Mathieu-Daudé Oct. 25, 2024, 3:11 p.m. UTC | #5
On 24/10/24 17:53, Peter Xu wrote:
> On Thu, Oct 24, 2024 at 05:02:19PM -0300, Philippe Mathieu-Daudé wrote:
>> Hi Peter,
> 
> Hi, Phil,
> 
> Thanks for the quick reviews!
> 
>>
>> (Cc'ing Mark)
>>
>> On 24/10/24 13:56, Peter Xu wrote:
>>> Signed-off-by: Peter Xu <peterx@redhat.com>
>>> ---
>>>    include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
>>>    qom/object.c                    |  3 +++
>>>    qom/object_interfaces.c         | 24 +++++++++++++++++
>>>    qom/qom-qmp-cmds.c              | 22 ++++++++++++---
>>>    system/qdev-monitor.c           |  7 +++++
>>>    5 files changed, 100 insertions(+), 3 deletions(-)
>>
>>
>>> +/**
>>> + * SingletonClass:
>>> + *
>>> + * @parent_class: the base class
>>> + * @get_instance: fetch the singleton instance if it is created,
>>> + *                NULL otherwise.
>>> + *
>>> + * Singleton class describes the type of object classes that can only
>>> + * provide one instance for the whole lifecycle of QEMU.  It will fail the
>>> + * operation if one attemps to create more than one instance.
>>> + *
>>> + * One can fetch the single object using class's get_instance() callback if
>>> + * it was created before.  This can be useful for operations like QMP
>>> + * qom-list-properties, where dynamically creating an object might not be
>>> + * feasible.
>>> + */
>>> +struct SingletonClass {
>>> +    /* <private> */
>>> +    InterfaceClass parent_class;
>>> +    /* <public> */
>>> +    Object *(*get_instance)(Error **errp);
>>
>> IMHO asking get_instance() is overkill ...
>>
>>> +};
>>> +
>>> +/**
>>> + * object_class_is_singleton:
>>> + *
>>> + * @class: the class to detect singleton
>>> + *
>>> + * Returns: true if it's a singleton class, false otherwise.
>>> + */
>>> +bool object_class_is_singleton(ObjectClass *class);
>>> +
>>> +/**
>>> + * singleton_get_instance:
>>> + *
>>> + * @class: the class to fetch singleton instance
>>> + *
>>> + * Returns: the object* if the class is a singleton class and the singleton
>>> + *          object is created, NULL otherwise.
>>> + */
>>> +Object *singleton_get_instance(ObjectClass *class);
>>> +
>>>    #endif
>>
>>> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
>>> index e0833c8bfe..6766060d0a 100644
>>> --- a/qom/object_interfaces.c
>>> +++ b/qom/object_interfaces.c
>>> @@ -354,6 +354,23 @@ void user_creatable_cleanup(void)
>>>        object_unparent(object_get_objects_root());
>>>    }
>>> +bool object_class_is_singleton(ObjectClass *class)
>>> +{
>>> +    return !!object_class_dynamic_cast(class, TYPE_SINGLETON);
>>> +}
>>> +
>>> +Object *singleton_get_instance(ObjectClass *class)
>>> +{
>>
>> ... when we can use object_resolve_type_unambiguous:
>>
>>        return object_resolve_type_unambiguous(object_class_get_name(class,
>> NULL));
> 
> I think an issue is migration object is nowhere to be find under
> object_get_root(), so it won't work there.  A side benefit is, it's also
> faster..

Maybe a simpler alternative is to add a field in ObjectClass, maintain
a single GHashTable to store TypeName -> Instance and retrieve as:

Object *singleton_get_instance(ObjectClass *class)
{
     return g_hash_table_lookup(&singletons,
                                object_class_get_name(class));
}

TBH the TYPE_SINGLETON interface seems a bit over-engineered and its
implementation error prone.

> How about I use object_resolve_type_unambiguous() as a fallback?  Then it's
> used only if get_instance() is not provided.
> 
>>
>> BTW should we pass Error** argument to singleton_get_instance()?
> 
> I didn't expect it to fail, hence I didn't even add it to make it more like
> "this will always success or it asserts" kind of things.  I left Error** in
> the hook just to be slightly flexible, but I always pass in error_abort()
> in this patch.
> 
> I can either add Error** if anyone thinks it useful, or even drop Error**
> in ->get_instance() since it's mostly not used at least for now.
> 
> Let me know!
> 
>>
>>> +    SingletonClass *singleton =
>>> +        (SingletonClass *)object_class_dynamic_cast(class, TYPE_SINGLETON);
>>> +
>>> +    if (!singleton) {
>>> +        return NULL;
>>> +    }
>>> +
>>> +    return singleton->get_instance(&error_abort);
>>> +}
>>
>> Alternatively call object_resolve_type_unambiguous() in instance_init()?
> 
> Thanks,
>
Peter Xu Oct. 25, 2024, 3:17 p.m. UTC | #6
On Fri, Oct 25, 2024 at 10:07:59AM +0200, Markus Armbruster wrote:
> Peter Xu <peterx@redhat.com> writes:
> 
> > Signed-off-by: Peter Xu <peterx@redhat.com>
> > ---
> >  include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
> >  qom/object.c                    |  3 +++
> >  qom/object_interfaces.c         | 24 +++++++++++++++++
> >  qom/qom-qmp-cmds.c              | 22 ++++++++++++---
> >  system/qdev-monitor.c           |  7 +++++
> >  5 files changed, 100 insertions(+), 3 deletions(-)
> >
> > diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
> > index 02b11a7ef0..9b2cc0e554 100644
> > --- a/include/qom/object_interfaces.h
> > +++ b/include/qom/object_interfaces.h
> > @@ -177,4 +177,51 @@ bool user_creatable_del(const char *id, Error **errp);
> >   */
> >  void user_creatable_cleanup(void);
> >  
> > +#define TYPE_SINGLETON "singleton"
> > +
> > +typedef struct SingletonClass SingletonClass;
> > +DECLARE_CLASS_CHECKERS(SingletonClass, SINGLETON, TYPE_SINGLETON)
> > +
> > +/**
> > + * SingletonClass:
> > + *
> > + * @parent_class: the base class
> > + * @get_instance: fetch the singleton instance if it is created,
> > + *                NULL otherwise.
> > + *
> > + * Singleton class describes the type of object classes that can only
> > + * provide one instance for the whole lifecycle of QEMU.  It will fail the
> > + * operation if one attemps to create more than one instance.
> > + *
> > + * One can fetch the single object using class's get_instance() callback if
> > + * it was created before.  This can be useful for operations like QMP
> > + * qom-list-properties, where dynamically creating an object might not be
> > + * feasible.
> > + */
> > +struct SingletonClass {
> > +    /* <private> */
> > +    InterfaceClass parent_class;
> > +    /* <public> */
> > +    Object *(*get_instance)(Error **errp);
> > +};
> > +
> > +/**
> > + * object_class_is_singleton:
> > + *
> > + * @class: the class to detect singleton
> > + *
> > + * Returns: true if it's a singleton class, false otherwise.
> > + */
> > +bool object_class_is_singleton(ObjectClass *class);
> > +
> > +/**
> > + * singleton_get_instance:
> > + *
> > + * @class: the class to fetch singleton instance
> > + *
> > + * Returns: the object* if the class is a singleton class and the singleton
> > + *          object is created, NULL otherwise.
> > + */
> > +Object *singleton_get_instance(ObjectClass *class);
> 
> A non-null return value can become dangling when the object gets
> destroyed.  This could conceivably happen in another thread.
> 
> The obviously safe interface would take a reference the caller must
> unref when done.

Ouch, thanks for spotting this!  Yes we definitely need a refcount at
least..

> 
> > +
> >  #endif
> > diff --git a/qom/object.c b/qom/object.c
> > index 11424cf471..ded299ae1a 100644
> > --- a/qom/object.c
> > +++ b/qom/object.c
> > @@ -553,6 +553,9 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type
> >      g_assert(type->abstract == false);
> >      g_assert(size >= type->instance_size);
> >  
> > +    /* Singleton class can only create one object */
> > +    g_assert(!singleton_get_instance(type->class));
> > +
> >      memset(obj, 0, type->instance_size);
> >      obj->class = type->class;
> >      object_ref(obj);
> > diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> > index e0833c8bfe..6766060d0a 100644
> > --- a/qom/object_interfaces.c
> > +++ b/qom/object_interfaces.c
> > @@ -354,6 +354,23 @@ void user_creatable_cleanup(void)
> >      object_unparent(object_get_objects_root());
> >  }
> >  
> > +bool object_class_is_singleton(ObjectClass *class)
> > +{
> > +    return !!object_class_dynamic_cast(class, TYPE_SINGLETON);
> > +}
> > +
> > +Object *singleton_get_instance(ObjectClass *class)
> > +{
> > +    SingletonClass *singleton =
> > +        (SingletonClass *)object_class_dynamic_cast(class, TYPE_SINGLETON);
> > +
> > +    if (!singleton) {
> > +        return NULL;
> > +    }
> > +
> > +    return singleton->get_instance(&error_abort);
> > +}
> > +
> >  static void register_types(void)
> >  {
> >      static const TypeInfo uc_interface_info = {
> > @@ -362,7 +379,14 @@ static void register_types(void)
> >          .class_size = sizeof(UserCreatableClass),
> >      };
> >  
> > +    static const TypeInfo singleton_interface_info = {
> > +        .name          = TYPE_SINGLETON,
> > +        .parent        = TYPE_INTERFACE,
> > +        .class_size = sizeof(SingletonClass),
> > +    };
> > +
> >      type_register_static(&uc_interface_info);
> > +    type_register_static(&singleton_interface_info);
> >  }
> >  
> >  type_init(register_types)
> > diff --git a/qom/qom-qmp-cmds.c b/qom/qom-qmp-cmds.c
> > index e91a235347..ecc1cf781c 100644
> > --- a/qom/qom-qmp-cmds.c
> > +++ b/qom/qom-qmp-cmds.c
> > @@ -126,6 +126,7 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> >      ObjectProperty *prop;
> >      ObjectPropertyIterator iter;
> >      ObjectPropertyInfoList *prop_list = NULL;
> > +    bool create;
> >  
> >      klass = module_object_class_by_name(typename);
> >      if (klass == NULL) {
> > @@ -141,7 +142,15 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> >          return NULL;
> >      }
> >  
> > -    obj = object_new(typename);
> > +    /* Avoid creating multiple instances if the class is a singleton */
> > +    create = !object_class_is_singleton(klass) ||
> > +        !singleton_get_instance(klass);
> > +
> > +    if (create) {
> > +        obj = object_new(typename);
> 
> If the class is not a singleton or else if no instance exists, we create
> a temporary instance.
> 
> > +    } else {
> > +        obj = singleton_get_instance(klass);
> 
> If the class is a singleton and the instance exists, we use that
> instead.
> 
> Any properties the instance has created dynamically after object_new()
> are visible to introspection.  This is not the case when we create a
> temporary instance.  Such subtle differences are problematic.  If we
> decide we're okay with this one, we need to document it.

I am thinking what's the major use case for device-list-properties.

If it's for mgmt to query a device property list for set/get before
operating on a real instance (which represents already somewhere in the
device tree), I hope it's fine.  Basically, it means whatever queried here
can always be used with qom-get/qom-set for this singleton as long as it's
already created and present.

I hope Libvirt cannot cache this results anyway, because we already have:

##
# @device-list-properties:
# ...
# .. note:: Objects can create properties at runtime, for example to
#    describe links between different devices and/or objects.  These
#    properties are not included in the output of this command.

So I think it means mgmt cannot cache these results, but only query before
qom-set/qom-get to make sure it's valid.  In that case, maybe we can change
that to s/are not included/may not be included/?

> 
> > +    }
> >  
> >      object_property_iter_init(&iter, obj);
> >      while ((prop = object_property_iter_next(&iter))) {
> > @@ -172,7 +181,9 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> >          QAPI_LIST_PREPEND(prop_list, info);
> >      }
> >  
> > -    object_unref(obj);
> > +    if (create) {
> > +        object_unref(obj);
> > +    }
> >  
> >      return prop_list;
> >  }
> > @@ -199,7 +210,12 @@ ObjectPropertyInfoList *qmp_qom_list_properties(const char *typename,
> >          return NULL;
> >      }
> >  
> > -    if (object_class_is_abstract(klass)) {
> > +    /*
> > +     * Abstract classes are not for instantiations, meanwhile avoid
> > +     * creating temporary singleton objects because it can cause conflicts
> > +     * if there's already one created.
> > +     */
> > +    if (object_class_is_abstract(klass) || object_class_is_singleton(klass)) {
> >          object_class_property_iter_init(&iter, klass);
> >      } else {
> >          obj = object_new(typename);
> 
> If the class is a singleton, we treat it as if it was abstract.  Its
> instance properties, if any, are not visible in qom-list-properties.
> This is a defect.  If you make an existing class with instance
> properties a singleton, the defect is a regression.

I can switch to get_instance() for singleton when it's available.  Would
that work?

Thanks!

> 
> > diff --git a/system/qdev-monitor.c b/system/qdev-monitor.c
> > index 44994ea0e1..1310f35c9f 100644
> > --- a/system/qdev-monitor.c
> > +++ b/system/qdev-monitor.c
> > @@ -36,6 +36,7 @@
> >  #include "qemu/option.h"
> >  #include "qemu/qemu-print.h"
> >  #include "qemu/option_int.h"
> > +#include "qom/object_interfaces.h"
> >  #include "sysemu/block-backend.h"
> >  #include "migration/misc.h"
> >  #include "qemu/cutils.h"
> > @@ -643,6 +644,12 @@ DeviceState *qdev_device_add_from_qdict(const QDict *opts,
> >          return NULL;
> >      }
> >  
> > +    if (singleton_get_instance(OBJECT_CLASS(dc))) {
> > +        error_setg(errp, "Class '%s' only supports one instance",
> > +                   driver);
> > +        return NULL;
> > +    }
> > +
> >      /* find bus */
> >      path = qdict_get_try_str(opts, "bus");
> >      if (path != NULL) {
>
Peter Xu Oct. 25, 2024, 4:17 p.m. UTC | #7
On Fri, Oct 25, 2024 at 10:51:21AM +0100, Daniel P. Berrangé wrote:
> On Thu, Oct 24, 2024 at 12:56:24PM -0400, Peter Xu wrote:
> 
> Adding significant new functionality to QOM should really come
> with a commit message explaining the rationale and the design
> choices
> 
> > Signed-off-by: Peter Xu <peterx@redhat.com>
> > ---
> >  include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
> >  qom/object.c                    |  3 +++
> >  qom/object_interfaces.c         | 24 +++++++++++++++++
> >  qom/qom-qmp-cmds.c              | 22 ++++++++++++---
> >  system/qdev-monitor.c           |  7 +++++
> >  5 files changed, 100 insertions(+), 3 deletions(-)
> 
> snip
> 
> > + * Singleton class describes the type of object classes that can only
> > + * provide one instance for the whole lifecycle of QEMU.  It will fail the
> > + * operation if one attemps to create more than one instance.
> > + *
> > + * One can fetch the single object using class's get_instance() callback if
> > + * it was created before.  This can be useful for operations like QMP
> > + * qom-list-properties, where dynamically creating an object might not be
> > + * feasible.
> 
> snip
> 
> > +/**
> > + * singleton_get_instance:
> > + *
> > + * @class: the class to fetch singleton instance
> > + *
> > + * Returns: the object* if the class is a singleton class and the singleton
> > + *          object is created, NULL otherwise.
> > + */
> > +Object *singleton_get_instance(ObjectClass *class);
> 
> With this design, all code that uses a given type needs to know
> whether or not it is intended to be a singleton. If some code
> somewhere mistakenly calls 'object_new' instead of 'singleton_get_instance',
> the singleton type  is no longer a singleton, except you handle this by
> adding an assert in object_initialize_with_type.

Yes, that's really the current goal, and why I added that assert(), so it
fails any attempts trying to create one more instance of it, because
normally it's by accident.  The theory issue to solve is when some class is
not ready for being created more than once, so it must not happen.

My plan was to properly guard qdev creation with this which can fail
pretty, so it's a "programming error" otherwise.

> 
> This is still a bit of a loaded foot-gun IMHO, as we don't want random
> code asserting.
> 
> > diff --git a/qom/object.c b/qom/object.c
> > index 11424cf471..ded299ae1a 100644
> > --- a/qom/object.c
> > +++ b/qom/object.c
> > @@ -553,6 +553,9 @@ static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type
> >      g_assert(type->abstract == false);
> >      g_assert(size >= type->instance_size);
> >  
> > +    /* Singleton class can only create one object */
> > +    g_assert(!singleton_get_instance(type->class));
> > +
> >      memset(obj, 0, type->instance_size);
> >      obj->class = type->class;
> >      object_ref(obj);
> 
> > diff --git a/qom/qom-qmp-cmds.c b/qom/qom-qmp-cmds.c
> > index e91a235347..ecc1cf781c 100644
> > --- a/qom/qom-qmp-cmds.c
> > +++ b/qom/qom-qmp-cmds.c
> > @@ -126,6 +126,7 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> >      ObjectProperty *prop;
> >      ObjectPropertyIterator iter;
> >      ObjectPropertyInfoList *prop_list = NULL;
> > +    bool create;
> >  
> >      klass = module_object_class_by_name(typename);
> >      if (klass == NULL) {
> > @@ -141,7 +142,15 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> >          return NULL;
> >      }
> >  
> > -    obj = object_new(typename);
> > +    /* Avoid creating multiple instances if the class is a singleton */
> > +    create = !object_class_is_singleton(klass) ||
> > +        !singleton_get_instance(klass);
> > +
> > +    if (create) {
> > +        obj = object_new(typename);
> > +    } else {
> > +        obj = singleton_get_instance(klass);
> > +    }
> 
> This is the first foot-gun example.
> 
> I really strongly dislike that the design pattern forces us to
> create code like this, as we can never be confident we've
> correctly identified all the places which may call object_new
> on a singleton...

Yeah I agree it's not pretty, IMHO it's a trade-off comparing to glib's,
I'll comment below for that.

Meanwhile I hope there should be very limited places in QEMU to randomly
create "any" object on the fly.. so far only qom/device-list-properties
that I see.

> 
> > @@ -172,7 +181,9 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> >          QAPI_LIST_PREPEND(prop_list, info);
> >      }
> >  
> > -    object_unref(obj);
> > +    if (create) {
> > +        object_unref(obj);
> > +    }
> 
> ...and this just compounds the ugliness.
> 
> > @@ -199,7 +210,12 @@ ObjectPropertyInfoList *qmp_qom_list_properties(const char *typename,
> >          return NULL;
> >      }
> >  
> > -    if (object_class_is_abstract(klass)) {
> > +    /*
> > +     * Abstract classes are not for instantiations, meanwhile avoid
> > +     * creating temporary singleton objects because it can cause conflicts
> > +     * if there's already one created.
> > +     */
> 
> Another example of the foot-gun firing at random code
> 
> > +    if (object_class_is_abstract(klass) || object_class_is_singleton(klass)) {
> >          object_class_property_iter_init(&iter, klass);
> >      } else {
> >          obj = object_new(typename);
> 
> 
> With changes to QOM, I think it is generally informative to look at how
> GLib has handled the problem, since the QOM design has heavily borrowed
> from its GObject design.
> 
> In GObject, singletons are handled in a very differnt way. It has a
> concept of a "constructor" function against the class, which is what is
> responsible for allocating the object. By default the 'constructor' will
> call g_new0, but a class which wishes to become a singleton will override
> the 'constructor' function to allocate on first call, and return the
> cached object on subsequent calls. This is illustrated here:
> 
>   https://gitlab.gnome.org/GNOME/glib/-/blob/main/gobject/gobject.h#L297
> 
> The key benefit of this is that everything can carry on calling
> g_object_new() as before, as it will just "do the right thing"
> in terms of allocation.
> 
> In QOM, we don't have a 'constructor' class function, we just directly
> call g_malloc from object_new_with_type. This is because at the time,
> we didn't see an immediate need for it. We could easily change that
> though to introduce the concept of a 'constructor', which could
> probably make singletons work without needing updates to existing code.

I think glib's implementation is not thread safe on its own... consider two
threads invoke g_object_new() on the singleton without proper locking.  I
am guessing it could be relevant to glib's heavy event model.

And that's fundamentally what I want to have for QEMU's migration object,
so that it doesn't need locking on its own of the singleton idea: the
locking, if necessary, should be done in get_instance(), in this case.
So I did it wrong indeed in this current series at least there, where it
should need to take migration's new mutex, then take a refcount before
return, rightfully as Markus pointed out elsewhere.

The other concern I have with glib's singleton is, fundamentally
object_new() didn't really create a new object, which can be against the
gut feelings of whoever read it.  I'm not sure whether that's really what
we want.. or maybe that's only my own concern, so it might be subjective.

Thanks,
Peter Xu Oct. 25, 2024, 4:21 p.m. UTC | #8
On Fri, Oct 25, 2024 at 12:11:51PM -0300, Philippe Mathieu-Daudé wrote:
> On 24/10/24 17:53, Peter Xu wrote:
> > On Thu, Oct 24, 2024 at 05:02:19PM -0300, Philippe Mathieu-Daudé wrote:
> > > Hi Peter,
> > 
> > Hi, Phil,
> > 
> > Thanks for the quick reviews!
> > 
> > > 
> > > (Cc'ing Mark)
> > > 
> > > On 24/10/24 13:56, Peter Xu wrote:
> > > > Signed-off-by: Peter Xu <peterx@redhat.com>
> > > > ---
> > > >    include/qom/object_interfaces.h | 47 +++++++++++++++++++++++++++++++++
> > > >    qom/object.c                    |  3 +++
> > > >    qom/object_interfaces.c         | 24 +++++++++++++++++
> > > >    qom/qom-qmp-cmds.c              | 22 ++++++++++++---
> > > >    system/qdev-monitor.c           |  7 +++++
> > > >    5 files changed, 100 insertions(+), 3 deletions(-)
> > > 
> > > 
> > > > +/**
> > > > + * SingletonClass:
> > > > + *
> > > > + * @parent_class: the base class
> > > > + * @get_instance: fetch the singleton instance if it is created,
> > > > + *                NULL otherwise.
> > > > + *
> > > > + * Singleton class describes the type of object classes that can only
> > > > + * provide one instance for the whole lifecycle of QEMU.  It will fail the
> > > > + * operation if one attemps to create more than one instance.
> > > > + *
> > > > + * One can fetch the single object using class's get_instance() callback if
> > > > + * it was created before.  This can be useful for operations like QMP
> > > > + * qom-list-properties, where dynamically creating an object might not be
> > > > + * feasible.
> > > > + */
> > > > +struct SingletonClass {
> > > > +    /* <private> */
> > > > +    InterfaceClass parent_class;
> > > > +    /* <public> */
> > > > +    Object *(*get_instance)(Error **errp);
> > > 
> > > IMHO asking get_instance() is overkill ...
> > > 
> > > > +};
> > > > +
> > > > +/**
> > > > + * object_class_is_singleton:
> > > > + *
> > > > + * @class: the class to detect singleton
> > > > + *
> > > > + * Returns: true if it's a singleton class, false otherwise.
> > > > + */
> > > > +bool object_class_is_singleton(ObjectClass *class);
> > > > +
> > > > +/**
> > > > + * singleton_get_instance:
> > > > + *
> > > > + * @class: the class to fetch singleton instance
> > > > + *
> > > > + * Returns: the object* if the class is a singleton class and the singleton
> > > > + *          object is created, NULL otherwise.
> > > > + */
> > > > +Object *singleton_get_instance(ObjectClass *class);
> > > > +
> > > >    #endif
> > > 
> > > > diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> > > > index e0833c8bfe..6766060d0a 100644
> > > > --- a/qom/object_interfaces.c
> > > > +++ b/qom/object_interfaces.c
> > > > @@ -354,6 +354,23 @@ void user_creatable_cleanup(void)
> > > >        object_unparent(object_get_objects_root());
> > > >    }
> > > > +bool object_class_is_singleton(ObjectClass *class)
> > > > +{
> > > > +    return !!object_class_dynamic_cast(class, TYPE_SINGLETON);
> > > > +}
> > > > +
> > > > +Object *singleton_get_instance(ObjectClass *class)
> > > > +{
> > > 
> > > ... when we can use object_resolve_type_unambiguous:
> > > 
> > >        return object_resolve_type_unambiguous(object_class_get_name(class,
> > > NULL));
> > 
> > I think an issue is migration object is nowhere to be find under
> > object_get_root(), so it won't work there.  A side benefit is, it's also
> > faster..
> 
> Maybe a simpler alternative is to add a field in ObjectClass, maintain
> a single GHashTable to store TypeName -> Instance and retrieve as:
> 
> Object *singleton_get_instance(ObjectClass *class)
> {
>     return g_hash_table_lookup(&singletons,
>                                object_class_get_name(class));
> }

This may need some proper locking too as Markus pointed out, so that the
object needs to boost its refcount before returned.  It might be a problem
if the singleton class has its own locking.. I'll need to think about it.

In migration's case, there's going to be a new migration mutex protecting
the singleton object being updated (not included in this series, posted
elsewhere [1]).

[1] https://lore.kernel.org/r/20241024213056.1395400-9-peterx@redhat.com

> 
> TBH the TYPE_SINGLETON interface seems a bit over-engineered and its
> implementation error prone.

Please feel free to suggest something else if you come up with.

The issue so far is qom/device-list-properties now can randomly create
migration object, which is defined to be global, while the plan is we need
to do something tricky in instance_finalize() for migration object (operate
on global vars; which is tricky but it could solve some issue we
encounter), so we must make sure it's never created more than once.

Thanks,

> 
> > How about I use object_resolve_type_unambiguous() as a fallback?  Then it's
> > used only if get_instance() is not provided.
> > 
> > > 
> > > BTW should we pass Error** argument to singleton_get_instance()?
> > 
> > I didn't expect it to fail, hence I didn't even add it to make it more like
> > "this will always success or it asserts" kind of things.  I left Error** in
> > the hook just to be slightly flexible, but I always pass in error_abort()
> > in this patch.
> > 
> > I can either add Error** if anyone thinks it useful, or even drop Error**
> > in ->get_instance() since it's mostly not used at least for now.
> > 
> > Let me know!
> > 
> > > 
> > > > +    SingletonClass *singleton =
> > > > +        (SingletonClass *)object_class_dynamic_cast(class, TYPE_SINGLETON);
> > > > +
> > > > +    if (!singleton) {
> > > > +        return NULL;
> > > > +    }
> > > > +
> > > > +    return singleton->get_instance(&error_abort);
> > > > +}
> > > 
> > > Alternatively call object_resolve_type_unambiguous() in instance_init()?
> > 
> > Thanks,
> > 
>
Daniel P. Berrangé Oct. 25, 2024, 4:22 p.m. UTC | #9
On Fri, Oct 25, 2024 at 12:17:02PM -0400, Peter Xu wrote:
> On Fri, Oct 25, 2024 at 10:51:21AM +0100, Daniel P. Berrangé wrote:
> > > diff --git a/qom/qom-qmp-cmds.c b/qom/qom-qmp-cmds.c
> > > index e91a235347..ecc1cf781c 100644
> > > --- a/qom/qom-qmp-cmds.c
> > > +++ b/qom/qom-qmp-cmds.c
> > > @@ -126,6 +126,7 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> > >      ObjectProperty *prop;
> > >      ObjectPropertyIterator iter;
> > >      ObjectPropertyInfoList *prop_list = NULL;
> > > +    bool create;
> > >  
> > >      klass = module_object_class_by_name(typename);
> > >      if (klass == NULL) {
> > > @@ -141,7 +142,15 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> > >          return NULL;
> > >      }
> > >  
> > > -    obj = object_new(typename);
> > > +    /* Avoid creating multiple instances if the class is a singleton */
> > > +    create = !object_class_is_singleton(klass) ||
> > > +        !singleton_get_instance(klass);
> > > +
> > > +    if (create) {
> > > +        obj = object_new(typename);
> > > +    } else {
> > > +        obj = singleton_get_instance(klass);
> > > +    }
> > 
> > This is the first foot-gun example.
> > 
> > I really strongly dislike that the design pattern forces us to
> > create code like this, as we can never be confident we've
> > correctly identified all the places which may call object_new
> > on a singleton...
> 
> Yeah I agree it's not pretty, IMHO it's a trade-off comparing to glib's,
> I'll comment below for that.
> 
> Meanwhile I hope there should be very limited places in QEMU to randomly
> create "any" object on the fly.. so far only qom/device-list-properties
> that I see.

There's -object/-device CLI, and their corresponding object_add
/ device_add commands.

They are not relevant for the migration object, but you're adding
this feature to QOM, so we have to take them into account. If anyone
defines another Object or Device class as a singleton, we will have
quite a few places where we can trigger the assert. This is especially
bad if we trigger it from anything in QMP as that kills a running
guest.

> 
> I think glib's implementation is not thread safe on its own... consider two
> threads invoke g_object_new() on the singleton without proper locking.  I
> am guessing it could be relevant to glib's heavy event model.

I've not checked the full code path, to see if they have a serialization
point somewhere it the g_object_new call chain, but yes, it is a valid
concern that would need to be resolved.


With regards,
Daniel
Peter Xu Oct. 25, 2024, 4:37 p.m. UTC | #10
On Fri, Oct 25, 2024, 5:51 a.m. Daniel P. Berrangé <berrange@redhat.com>
wrote:

> On Thu, Oct 24, 2024 at 12:56:24PM -0400, Peter Xu wrote:
>
> Adding significant new functionality to QOM should really come
> with a commit message explaining the rationale and the design
> choices
>

Ohh i definitely missed it, my bad.  Luckily i still wrote the cover
letter. I'll prepare that if there is v2.

>
Peter Xu Oct. 25, 2024, 10:10 p.m. UTC | #11
On Fri, Oct 25, 2024 at 05:22:01PM +0100, Daniel P. Berrangé wrote:
> On Fri, Oct 25, 2024 at 12:17:02PM -0400, Peter Xu wrote:
> > On Fri, Oct 25, 2024 at 10:51:21AM +0100, Daniel P. Berrangé wrote:
> > > > diff --git a/qom/qom-qmp-cmds.c b/qom/qom-qmp-cmds.c
> > > > index e91a235347..ecc1cf781c 100644
> > > > --- a/qom/qom-qmp-cmds.c
> > > > +++ b/qom/qom-qmp-cmds.c
> > > > @@ -126,6 +126,7 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> > > >      ObjectProperty *prop;
> > > >      ObjectPropertyIterator iter;
> > > >      ObjectPropertyInfoList *prop_list = NULL;
> > > > +    bool create;
> > > >  
> > > >      klass = module_object_class_by_name(typename);
> > > >      if (klass == NULL) {
> > > > @@ -141,7 +142,15 @@ ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
> > > >          return NULL;
> > > >      }
> > > >  
> > > > -    obj = object_new(typename);
> > > > +    /* Avoid creating multiple instances if the class is a singleton */
> > > > +    create = !object_class_is_singleton(klass) ||
> > > > +        !singleton_get_instance(klass);
> > > > +
> > > > +    if (create) {
> > > > +        obj = object_new(typename);
> > > > +    } else {
> > > > +        obj = singleton_get_instance(klass);
> > > > +    }
> > > 
> > > This is the first foot-gun example.
> > > 
> > > I really strongly dislike that the design pattern forces us to
> > > create code like this, as we can never be confident we've
> > > correctly identified all the places which may call object_new
> > > on a singleton...
> > 
> > Yeah I agree it's not pretty, IMHO it's a trade-off comparing to glib's,
> > I'll comment below for that.
> > 
> > Meanwhile I hope there should be very limited places in QEMU to randomly
> > create "any" object on the fly.. so far only qom/device-list-properties
> > that I see.
> 
> There's -object/-device CLI, and their corresponding object_add
> / device_add commands.

Ah I didn't mention that when reply, but this patch blocks properly any
device-add for singleton objects for more than once.  Please see the change
in qdev_device_add_from_qdict().

For migration object, it means it'll always fail because migration object
is created very early, before someone can try to create it.  Not to mention
it also has dc->hotpluggable==false, so things like -device will never work
on migration object.

For object-add, IIUC migration object should always be safe because it has
no USER_CREATABLE interface.

> 
> They are not relevant for the migration object, but you're adding
> this feature to QOM, so we have to take them into account. If anyone
> defines another Object or Device class as a singleton, we will have
> quite a few places where we can trigger the assert. This is especially
> bad if we trigger it from anything in QMP as that kills a running
> guest.
> 
> > 
> > I think glib's implementation is not thread safe on its own... consider two
> > threads invoke g_object_new() on the singleton without proper locking.  I
> > am guessing it could be relevant to glib's heavy event model.
> 
> I've not checked the full code path, to see if they have a serialization
> point somewhere it the g_object_new call chain, but yes, it is a valid
> concern that would need to be resolved.

Thanks,
diff mbox series

Patch

diff --git a/include/qom/object_interfaces.h b/include/qom/object_interfaces.h
index 02b11a7ef0..9b2cc0e554 100644
--- a/include/qom/object_interfaces.h
+++ b/include/qom/object_interfaces.h
@@ -177,4 +177,51 @@  bool user_creatable_del(const char *id, Error **errp);
  */
 void user_creatable_cleanup(void);
 
+#define TYPE_SINGLETON "singleton"
+
+typedef struct SingletonClass SingletonClass;
+DECLARE_CLASS_CHECKERS(SingletonClass, SINGLETON, TYPE_SINGLETON)
+
+/**
+ * SingletonClass:
+ *
+ * @parent_class: the base class
+ * @get_instance: fetch the singleton instance if it is created,
+ *                NULL otherwise.
+ *
+ * Singleton class describes the type of object classes that can only
+ * provide one instance for the whole lifecycle of QEMU.  It will fail the
+ * operation if one attemps to create more than one instance.
+ *
+ * One can fetch the single object using class's get_instance() callback if
+ * it was created before.  This can be useful for operations like QMP
+ * qom-list-properties, where dynamically creating an object might not be
+ * feasible.
+ */
+struct SingletonClass {
+    /* <private> */
+    InterfaceClass parent_class;
+    /* <public> */
+    Object *(*get_instance)(Error **errp);
+};
+
+/**
+ * object_class_is_singleton:
+ *
+ * @class: the class to detect singleton
+ *
+ * Returns: true if it's a singleton class, false otherwise.
+ */
+bool object_class_is_singleton(ObjectClass *class);
+
+/**
+ * singleton_get_instance:
+ *
+ * @class: the class to fetch singleton instance
+ *
+ * Returns: the object* if the class is a singleton class and the singleton
+ *          object is created, NULL otherwise.
+ */
+Object *singleton_get_instance(ObjectClass *class);
+
 #endif
diff --git a/qom/object.c b/qom/object.c
index 11424cf471..ded299ae1a 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -553,6 +553,9 @@  static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type
     g_assert(type->abstract == false);
     g_assert(size >= type->instance_size);
 
+    /* Singleton class can only create one object */
+    g_assert(!singleton_get_instance(type->class));
+
     memset(obj, 0, type->instance_size);
     obj->class = type->class;
     object_ref(obj);
diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index e0833c8bfe..6766060d0a 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -354,6 +354,23 @@  void user_creatable_cleanup(void)
     object_unparent(object_get_objects_root());
 }
 
+bool object_class_is_singleton(ObjectClass *class)
+{
+    return !!object_class_dynamic_cast(class, TYPE_SINGLETON);
+}
+
+Object *singleton_get_instance(ObjectClass *class)
+{
+    SingletonClass *singleton =
+        (SingletonClass *)object_class_dynamic_cast(class, TYPE_SINGLETON);
+
+    if (!singleton) {
+        return NULL;
+    }
+
+    return singleton->get_instance(&error_abort);
+}
+
 static void register_types(void)
 {
     static const TypeInfo uc_interface_info = {
@@ -362,7 +379,14 @@  static void register_types(void)
         .class_size = sizeof(UserCreatableClass),
     };
 
+    static const TypeInfo singleton_interface_info = {
+        .name          = TYPE_SINGLETON,
+        .parent        = TYPE_INTERFACE,
+        .class_size = sizeof(SingletonClass),
+    };
+
     type_register_static(&uc_interface_info);
+    type_register_static(&singleton_interface_info);
 }
 
 type_init(register_types)
diff --git a/qom/qom-qmp-cmds.c b/qom/qom-qmp-cmds.c
index e91a235347..ecc1cf781c 100644
--- a/qom/qom-qmp-cmds.c
+++ b/qom/qom-qmp-cmds.c
@@ -126,6 +126,7 @@  ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
     ObjectProperty *prop;
     ObjectPropertyIterator iter;
     ObjectPropertyInfoList *prop_list = NULL;
+    bool create;
 
     klass = module_object_class_by_name(typename);
     if (klass == NULL) {
@@ -141,7 +142,15 @@  ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
         return NULL;
     }
 
-    obj = object_new(typename);
+    /* Avoid creating multiple instances if the class is a singleton */
+    create = !object_class_is_singleton(klass) ||
+        !singleton_get_instance(klass);
+
+    if (create) {
+        obj = object_new(typename);
+    } else {
+        obj = singleton_get_instance(klass);
+    }
 
     object_property_iter_init(&iter, obj);
     while ((prop = object_property_iter_next(&iter))) {
@@ -172,7 +181,9 @@  ObjectPropertyInfoList *qmp_device_list_properties(const char *typename,
         QAPI_LIST_PREPEND(prop_list, info);
     }
 
-    object_unref(obj);
+    if (create) {
+        object_unref(obj);
+    }
 
     return prop_list;
 }
@@ -199,7 +210,12 @@  ObjectPropertyInfoList *qmp_qom_list_properties(const char *typename,
         return NULL;
     }
 
-    if (object_class_is_abstract(klass)) {
+    /*
+     * Abstract classes are not for instantiations, meanwhile avoid
+     * creating temporary singleton objects because it can cause conflicts
+     * if there's already one created.
+     */
+    if (object_class_is_abstract(klass) || object_class_is_singleton(klass)) {
         object_class_property_iter_init(&iter, klass);
     } else {
         obj = object_new(typename);
diff --git a/system/qdev-monitor.c b/system/qdev-monitor.c
index 44994ea0e1..1310f35c9f 100644
--- a/system/qdev-monitor.c
+++ b/system/qdev-monitor.c
@@ -36,6 +36,7 @@ 
 #include "qemu/option.h"
 #include "qemu/qemu-print.h"
 #include "qemu/option_int.h"
+#include "qom/object_interfaces.h"
 #include "sysemu/block-backend.h"
 #include "migration/misc.h"
 #include "qemu/cutils.h"
@@ -643,6 +644,12 @@  DeviceState *qdev_device_add_from_qdict(const QDict *opts,
         return NULL;
     }
 
+    if (singleton_get_instance(OBJECT_CLASS(dc))) {
+        error_setg(errp, "Class '%s' only supports one instance",
+                   driver);
+        return NULL;
+    }
+
     /* find bus */
     path = qdict_get_try_str(opts, "bus");
     if (path != NULL) {