[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-core-devel
Subject:    Re: RFC: Proper destruction of static data?
From:       Michael Matz <matz () ifh ! de>
Date:       2000-06-27 21:27:10
[Download RAW message or body]

Hi,

First: This way lies madness :) May be, thats why this mail is so long.

On Tue, 27 Jun 2000, Werner Trobin wrote:
> After some bug-hunting and discussions on IRC we (Waldo and I) came to
> the conclusion that static objects are evil in DSOs, because they can
> lead to unexpected behavior (crashes :) or at least mem leaks on
> unloading.

That shouldn't be the case. If one uses static objects, one can't rely on
the order of construction or destruction. In case of dlopen'ed DSO one
can't even rely on them being constructed or destructed at all ;) (on some
platforms), so yes, static object are basically evil. Unfortunately I use
them for now for metaunloading (see below).

> We #define KSTATIC static and let some magic tool look for KSTATIC in
> the sources. It extracts all the variable names (only static pointers
> to objects are allowed) and creates a "clean up" function. This
> function deletes all the objects when the lib gets unloaded...

First: Of what "static" you are talking about? Static member variables,
where static simply means, that there is only one copy of that member for
all instances? Or static variables, where static additionally means, that
that variable is file local, meaning it can't be seen from outside that
file.

I guess the latter case, as only those vars are automatically
constructed/destructed. But then a clean-up function can't be easily
constructed, as it would not have access to those file local vars. It
would be needed to construct a per-file cleanup function, and call them
from a per-library cleanup. Or one make those vars global, but that
pollutes the namespace.

Even nastier are static vars declared local to a function, as in "int
a(){static i=0; if(!i) i=1; return i;}". If here "i" would be a pointer to
object, there is absolutely no chance to destruct "i" from the outside
unless a reference to that object i is pointing to also is collected on
some other place. But mostly this is no real problem, as when its
constructed with new, memory is allocated on the heap, and even on
unloading that DSO, the object itself remains valid.

OK, that cleanup function would move the problem of destruction to a
central place (that function) per library. Why should that solve the above
mentioned crashes? The following talks about the problem of calling a
function at DSO load/unload time, but without knowing how that should help
with those crashes.

That moving to a single place is good, but does not solve the problem, of
by whom it should get called. Think of the following situation:
DSO2: DSO0 DSO1
App: DSO0
and App dlopens DSO2.

One first could think to change libltdl a bit, and if ever ltdlclose() is
called that library finalizer is called by hand. But that would forget the
libraries implicitely loaded by loading DSO2 (that of DSO1 in this case).

That is basically the situation also with metaunloading. I have one
function per DSO, which should get called on DSO unload. This are the
solutions to that problem, as I see them:
1) all by hand: construct manually a ref counting system for loaded
   DSO. Each DSO has a initializer and finalizer function, like:
 static int ref_DSO2=0;
 void load_DSO2() {
   if (!ref) {
     /* call load_xxx for each xxx in library dependence list, when xxx
        provides a load_xxx, load_DSO0, load_DSO1 in our case */
     /* do other initialization work */
   }
   ref++;
 }
   similar things for the finalizer. In libltdl call those functions
   appropriately. Note, that the "ref_xxx" does _not_ count the number of
   times the library is in use at all, but only how many paths are
   leading to it from dlopen'ed DSOs. As long as the load_ and unload_
   calls are balanced (by number) this should do the right thing even for
   the stranger dependence patterns. The problem here is of course to find
   out the xxx because not every library on the link line provides
   them. Even more the list is different on different platforms, because
   on some one have to explicitely give all dependence libs on the link
   line, one some not. libtool knows about them all, but... Basically this
   is way is too complicated.

2) rely on systems way: on every dynamic library system (I know) there is
   the concept of initializers/finalizers. Usually at link time or based on
   the functions name one can make some functions special, in the way,
   that they are called at load and unload time. That would be the ideal
   candidate for our purpose. We have a per-DSO function which should be
   called on unload, so that would work. Hah. Its not that simple, for
   e.g. ELF/Linux/GNU ld that function must be called "_fini". But that is
   already defined by crt0.o and is used for static object variables on
   C++. What is more frightening are the number of options to the
   different linkers to make some functions be initializers. E.g. on HP-UX
   there is only one function which is called both on load and unload, on
   linux there is no such option (its doing its job with help of .init
   sections and _init/_fini) ... Bah. I tried to do that for
   metaunloading, gave up, and now use 3).

3) rely on C++: There is one mechanism to do work on loading/unloading
   DSO, namely static objects. The compiler on a platform is supposed to
   know how to do that, and so we could normally rely on that it works. I
   know, that static object work for dlopen'ed DSO on Solaris(g++, CC),
   linux(g++) and IRIX(g++, CC). It does _not_ work on HP-UX for dlopen'ed
   DSOs, though for normally linked DSO (shared libs) it works. For HP-UX
   we need to change libltdl a little bit, to make it work for g++. For CC
   I don't know right now.
   OK, you may say, but that solves the problems of static objects with
   static objects. But I want to use only _one_ static object for the
   whole DSO and mainly as a vehicle to call a function at
   loading/unloading time.
   Anyway, this is the most portable way I could come up with.

Basically there is already some work in am_edit and kunload.h to construct
a function which gets called exactly at unload time (the metaunload
stuff). Its using 3). It could be extended to call other functions.

On the original issue with KSTATIC: Its easy to scan all files for that,
and build a list of variables, its also easy to construct a cleaning
function out of that, even on file basis. But without help from the
developer its not easy to include that code in that file, as would be
needed, if they were really static variables. He at least would have to
#include some code like with .moc files (say a '#include "filename.auto"'
and if it is missing am_edit spits out an error).

On could even invent a method to annotate functions which should be called
on unload or load for additional work, but that is also possible now with
static object and a small helper class. Don't know what would be better.
(But this would be a mechanism for the developer to ensure a particular
order when destructing those pointers, so may be, it would make sense.)

Hmm, now I'm a little bit confused, about where we stand ;) Do you have
already such KSTATIC'ified sources, one could use as a base for am_edit
hackery, preferably with some crashes, which you hope to solve by all
that?

Ciao,
Michael.
PS: sorry for my long mails ;)

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic