GObjectClass.dispose and bringing objects back to life



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.

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
* 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

* warn/critical and behave as above
  * as above, but at least it's visible; on the other hand it contradicts
    the documentation

* raise a GError if possible
  * not always possible
  * not really recoverable - what's the user going to do about it?

* none of the above, it Can't Happen
  * contradicts the documentation, but is by far the easiest to explain!

Any thoughts?
    S


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