Re: GObjectClass.dispose and bringing objects back to life



On Fri, Dec 2, 2011 at 4:26 AM, Simon McVittie
<simon mcvittie collabora co uk> wrote:
> Hi,
> I've been looking into the details of how GObjects are destroyed,
> hoping to solve <https://bugzilla.gnome.org/show_bug.cgi?id=665211>, in
> which disposing a global singleton GDBusConnection in one thread races with
> reffing and using it in another thread, causing resurrection and a crash if
> both happen. (Advice from GObject gurus on that bug would be very much
> appreciated.)
>
> While doing so I noticed these bits of documentation:
>
> http://developer.gnome.org/gobject/stable/howto-gobject-destruction.html
>> It is possible that object methods might be invoked after dispose is
>> run and before finalize runs. GObject does not consider this to be a
>> program error: you must gracefully detect this and neither crash nor
>> warn the user.
>
> http://developer.gnome.org/gobject/stable/gobject-memory.html#gobject-destruction-table
>> The object is also expected to be able to answer client method invocations
>> (with possibly an error code but no memory violation) until finalize
>> is executed.
>
> http://developer.gnome.org/gobject/stable/gobject-memory.html#gobject-memory-cycles
>> Object methods should be able to run without program error (that is,
>> without segfault :) in-between the two phases.
>
> I'm pretty sure typical real-world code doesn't follow these (mine certainly
> doesn't); in general it isn't possible to do anything useful in an object
> after its state and the objects it uses have been discarded, however much
> someone might want to ref it halfway through dispose.

Yes, real-world well-written GObjects *must* not crash after being disposed,
code that crashes because apis are called after dispose time are bugs,
and you should fix them as specially if you have possible circular object
references.

For instance, in many cases an object might fire a signal during it's
dispose cycle, that dispose cycle might run a few times and listeners
to a signal fired from dispose might query the disposing object's state.

This should absolutely not cause a crash, if it does, you need to make
sure that:

  o Most resources are normally freed in GObjectClass.finalize()

  o References to other GObjects are broken in dispose, and since
     dispose can run multiple times, care must be taken, i.e.:

     if (priv->reffed_object != NULL) {
         g_object_unref (priv->reffed_object);
         priv->reffed_object = NULL;
     }

> When this documentation says "object method" or "client method", does that
> refer to semi-internal GObject virtual functions like get_property() and
> dispose(), or to methods defined by the object like g_io_stream_close(),
> or both?
>
> I did a quick survey of GLib (in recent master) to get an idea of how widely
> the rules I quoted are followed. Here are the cases I spotted where they
> aren't:
>
> * GDBusConnection
>  * drops its reference to the GDBusWorker
>    * start_message_processing will assert
>    * flush_sync will assert
>    * close will assert
>    * send_message_unlocked will segfault
>    * dispatching incoming messages ceases to happen

GIO is still pretty young and generally, the age-old rule 'if it's not
broken, don't fix it' applies.

In other words, I don't think people are hitting these assertions, because
(as I mention below...), revived objects are not really revived in the
full sense
of the word, they are just temporarily available so that everyone who has
a reference to them can "finish up"...

If you really have 2 threads which refer to the same object, then naturally
they will both hold a reference to it and the object wont die until the last
thread is finished using it.

If for some odd reason you need a second thread to be "notified of the
forced destruction" of a said object, then it's probably best to use
a GtkWidget::destroy paradigm on that object... i.e. both objects
do hold a reference and either one releases their reference at "destroy"
signal emission time.

The bug Ryan is referring to in his reply seems to imply that people
should be allowed to use 'weak references' for this... I dont see how
that could be very safe... personally I would probably prefer using a
"destroy" signal run from the dispose handler for such notifications.

> * GFileEnumerator
>  * drops its ref to contained
>    * get_container will return NULL (which it can't normally), potentially
>      crashing its users
> * GFilterOutputStream
>  * drops its reference to the base_stream
>    * get_base_stream will return NULL, which it can't normally
>    * write, flush, close will critical (or segfault if checks are disabled)
> * GInetSocketAddress
>  * dispose() isn't idempotent (I'll open a bug)
>  * drops its ref to address
>    * get_address will return NULL, which it can't normally
>    * get_family, get_native_size, to_native will critical or segfault
>
> GDBusProxy appears to keep working correctly if resurrected from dispose(),
> which surprised me, but that's only because it doesn't unref its connection
> until finalize() (which is wrong, strictly speaking).
>
> GIOStream assumes its sub-streams still exist in *its* dispose, which breaks
> the "chain up last" pattern unless you skip closing the sub-streams until
> finalize().
>
> If a method is invoked after an object is disposed, which of these is it
> meant to do?
>
> * silently do nothing, return a dummy value if it returns a value
>  * potentially crashes callers that were expecting, e.g., a non-NULL return
>  * silent failure to do what was asked seems non-ideal

Remember that "revived" objects are not meant to really come back to life,
they are only revived because, for instance g_signal_emit() will hold
a reference
to the instance and arguments over a signal emission (allowing
listeners to safely
unref the object and preserving the life-cycle until the end of signal
emission).

When say, "destroy" is emitted on a GtkWidget, then the GtkWidget is revived
for the duration of the destroy signal.

Note also that dispose handlers also run before the object reference count
ever reaches 0, using a forceful g_object_run_dispose() is an important step
in destroying your object in any case where circular object references can
be an issue.

Hope this was helpful, honestly I trust libgio to MT-safe as far as executing
async callbacks from worker threads on my behalf, I would still consider it
my own responsability to implement a certain level of locking myself if I need
to really share/access objects from multiple threads which I create on my own...
I might be wrong to mistrust, I didn't write libgio so I don't know.

Cheers,
          -Tristan


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]