"Geoff Summerhayes" <sNuOmSrPnA...@hNoOtSmPaAiMl.com> writes: > "Marcin Tustin" <mt...@witch.cheese> wrote in message
> > Is there a way of defining object destructors in CLOS? > > I'd like to have a "create and forget" way of dealing with > > sockets, and this would be just the thing.
> Why not write one without a destructor, and write a with-open-socket > macro (see: with-open-file) to handle the dirty work?
> -- > Geoff
Thanks (And to everyone else who suggested this stuff) for the suggestion. Time to seriously up my level of CL knowledge again...
-- Mummy! Mummy! There's a twelve-foot stoat outside!
In article <3d10bf56$...@nntp2.nac.net>, Vlastimil Adamovsky wrote: > I have the same question as Marcin... > If an object is not referenced by anybody, how can I run a "destruction" > code? > It is necessary for releasing external resources..
The answer is that you don't know when garbage collection will happen. So even if there were a way to run a destruction routine at that time (as for instance, finalization in Java), it's quite likely that it is too late. Resources have to be released in a timely way. For example, you don't want to keep a file or a network connection open indefinitely! It's a bad idea to *want* to just forget about a socket. You cannot do that, because the state of your socket is visible to the world; whether or not you close the socket or file is an externally visible behavior of your program, unlike memory management, which is purely internal.
The solution is to run whatever clean up is needed, and just leave behind the object which holds the handles to these resources. That object's storage will be collected.
The unwind-protect form is your friend, at least for cleaning up resources whose duration is to be tied to the evaluation of a form. unwind-protect can be hidden behind a macro interface. Take a look at some of the standard WITH- macros in Common Lisp like with-open-file. This one internally creates a stream associated with an open file, executes your forms with a variable bound to that stream and then closes the file. Code that uses with-open-file cannot leak file handles unless the implementation of that macro is broken.
> I have the same question as Marcin... > If an object is not referenced by anybody, how can I run a "destruction" > code? > It is necessary for releasing external resources..
Every time I see a question about finalization, I hear lots of admonition about what it shouldn't be used for. So, what would be an appropriate use of finalization?
I hear "external resources", but "not Unix FDs", which are possibly the most forgiving external resource I can think of. So what would be an external resource for which finalization would be appropriate?
In article <y7cznxe4x07....@sindri.juniper.net>, Joel Ray Holveck wrote: >> I have the same question as Marcin... >> If an object is not referenced by anybody, how can I run a "destruction" >> code? >> It is necessary for releasing external resources..
> Every time I see a question about finalization, I hear lots of > admonition about what it shouldn't be used for. So, what would be an > appropriate use of finalization?
They are for just-in-case cleanup after sloppy programming. That is one explanation for their inclusion in the Java language.
What you can do with a finalizer is place an assertion in it which signals an error if it finds the object to be other than cleaned up. Kind of like a mortician checking for any signs of life before replacing the blood with formaldehyde. ;)
> I hear "external resources", but "not Unix FDs", which are possibly > the most forgiving external resource I can think of.
They are? Failing to close a device driver might make the device unavailable to other processes, including the same one which didn't do it! Failing to close a tty might fail to hang up a modem. Failing to close or shutdown a socket results in a connection remaining open, and ports being occupied. You may run out of descriptors if you generate too many open file descriptors before the next garbage collection happens (GC probably won't be triggered on the condition of running out of descriptors!) Descriptors that refer to open file inodes prevent those inodes from being recycled and their data blocks from being reassigned to other files, so your process could occupy, at least temporarily, more disk space than you intended.
* Joel Ray Holveck | Every time I see a question about finalization, I hear lots of admonition | about what it shouldn't be used for. So, what would be an appropriate use of | finalization?
In my somewhat limited experience, it is not useful without "weak pointers", which are objects the weak pointers to which "vanish" when they are the only pointers that keep it alive for purposes of garbage collection and the object is therefore not kept alive, either. Sometimes, you would want a system that uses weak hashtables for cached results of some sort, meaning that until the garbage collector runs, you can reference an object through a weak pointer, but if you have had no use for it at the time garbage collection occurs, it will effectively be tossed. This is a very useful feature, but if you want to make the most of it, you might want some concomitant information that should be alive only when the object itself is alive, but which you would not want to tie together with a lot of pointers all over the place, because that would defeat the purpose of weak pointers. Instead of letting things point to the weak object, you let them reference them indirectly and then let the weak object know. When the weak object is tossed, it will know how to remove any indirect traces of itself with it, that would either not vanish on their own at all or which would need a test for whether their target had vanished. (Weak pointers magically turn to nil after garbage collection.)
This may be very abstract and sound rather weird, but for a more intuitive example, think of human memory. Forgetting is a rather important feature of human memory, which is extremely underrated. Forgetting the conclusions when the observations are invalidated takes conscious effort in most people. You find lots of people who "learn" something, then integrate that knowledge with something else and come up with a conclusion of some sort, which they tend to believe even after the first thing they learned turned out to be all wrong. It would be much better if the first thing you learned had a normal pointer to it on its own and weak pointers to the conclusions that were based upon it (because they should seamlessly vanish if invalidated, too), so that if you dropped the normal pointer, the finalization would know how to invalidate the conclusions based upon it as it went. A simpler example is perhaps that you worked out a difficult probability problem a decade ago and then you remember that you solved it, but not what the solution was. I may not be an old man, in fact I'm pretty sure I'm not, but working through old math textbooks can be a very humbling experience, to be repeated once very decade or so so you at least still know that your brain works, but the kind of ridiculous despair you may feel the first time in remembering that you solved it and trying so much harder to remember it than to work it out again, is probably very close what a Common Lisp program feels when it goes looking for the cached result of a three-second long computation and the weak pointer only goes "no". On the flip side of Alzheimer's, there are many ways to get screwed if you remember something for too long, too. Many Lisp programmers experience that the system behaves differently after a cold start because they forgot that they have done something the system had not forgotten along with their memory of having done it. Emacs (Lisp) users sometimes have this problem, a useful keybinding that isn't, a mouse wheel that beeps or scrolls the wrong window, forgetting the last keyboard macro you used and thinking you invoked the previous one. Such stuff. The best expression of this problem was provided by an acquaintance of mine when his X server crashed and all remote sessions died, his multiple Emacsen croaked, his log output windows closed, and he was facing the standard X root window, all grey. "My context!", he said quietly. Not that finalizers would have helped him.
When I restrict the usefulness of finalization to weak pointers, it is because I strongly favor explicit cleanup for normal objects. However, there is a whole world out there who do not. The C++ crowd, for instance, have a moderately useful feature in that the destructors of stack-allocated objects are called when the stack is, for lack of a precise term, unwound. (It is not, of course, unwound the way Common Lisp does it.) If something like this is needed in Common Lisp, a competent Common Lisp programmer would simply design a new binding form that effectively calls the finalizer when you leave scope unless the object has been passed out of the scope in, say, a returned value. Such a programming style could, for instance, be used with resources, where a "resource" is an object of a type that is picked off a pool of weak pointers to previousely deallocated objects when you need one (or a fresh object is allocated) and effectively returned to the pool when you exit scope. The "finalization" would be to return it to the pool, but you would not do that if you were returning it. Figuring out such lifetime thingies is computer work. A similar stunt _could_ be used when dealing with streams: Suppose you close all streams upon scope exit that you have not returned to your caller as open streams. You would want that stream to be closed when the caller just dropped it sometime later. Note that the file descriptor in Unix is a resource of the above-mentioned kind and that the operating system has a finalization routine that is invoked upon exiting the program. You would want similar precautions in a well-honed Common Lisp program. Leaks are bad, and finalization may be used to ensure that you find them, but in order for leak detection to really work, you want to keep track of things that might leak with weak pointers.
Finally, note that implementationally, finalizers are easily implemented with weak pointers -- instead of just turning the weak pointer to nil when only weak pointers point to the object, you would call the finalizer first. -- Guide to non-spammers: If you want to send me a business proposal, please be specific and do not put "business proposal" in the Subject header. If it is urgent, do not use the word "urgent". If you need an immediate answer, give me a reason, do not shout "for your immediate attention". Thank you.
* Kaz Kylheku | You may run out of descriptors if you generate too many open file descriptors | before the next garbage collection happens (GC probably won't be triggered on | the condition of running out of descriptors!)
This is actually an important point. I had an application once that I tuned down to cons very little memory, but which opened and closed a lot of streams. In Allegro CL at the time, streams allocated buffers from a pool of memory that was not garbage collected the same way other Lisp memory was (called the C heap -- I think the reason was that the buffers should stay put and not move around with their stop-and-copy garbage collector). This memory could also not be released the same way a huge Lisp heap could be released once it had all turned into garbage. My application had been running for about three months when I noticed that it had consumed lots of swap space. It had showed no signs of slowing down, either, bu the dataset was now some 280M of C heap and 120M or so with Lisp heap. By decreasing the garbage collection frequency, I had accidentally let the C heap grow *huge* before the Lisp heap triggered a garbage collection and almost all of it was freed. The Lisp heap was fairly stable -- most of the objects created during a day's run would remain in memory until the midnight cleanup -- so I had effectively turned off garbage collection during the day. Tuning the garbage collection so it happened about every half hour during the working hours led to a 16M C heap and 70M Lisp heap -- because the objects were now in old space instead of the duplicated new space. It was an important lesson in the mechanics of garbage collection, which turned out to be useful when I decided to live the same place for more than two years -- annual copying garbage collection had worked just fine during my university years. -- Guide to non-spammers: If you want to send me a business proposal, please be specific and do not put "business proposal" in the Subject header. If it is urgent, do not use the word "urgent". If you need an immediate answer, give me a reason, do not shout "for your immediate attention". Thank you.
>>>>> On Sat, 29 Jun 2002 05:00:40 GMT, Joe Marshall ("Joe") writes:
Joe> "Kaz Kylheku" <k...@ashi.footprints.net> wrote in message news:afjavp$c0l$1@luna.vcn.bc.ca... >> GC probably won't be triggered on the condition of running out of descriptors! Joe> Perhaps it ought to be.
In MACLISP, I have a vague recollection that the function named "GCTWA" (a version of the function "GC") did something with cleaning up file descriptors (our OS called then "channels"). But I think they had to first be explicitly closed with CLOSE (which was not enough to GC them...some kind of intermediate resource?). I guess I have forgotten exactly what that was all about.
Anyway, having control over the GC in various ways, such as setting up various kinds of triggers for it, is a good idea.
In article <afjavp$c0...@luna.vcn.bc.ca>, Kaz Kylheku wrote: > In article <y7cznxe4x07....@sindri.juniper.net>, Joel Ray Holveck wrote: >>> I have the same question as Marcin... >>> If an object is not referenced by anybody, how can I run a "destruction" >>> code? >>> It is necessary for releasing external resources..
>> Every time I see a question about finalization, I hear lots of >> admonition about what it shouldn't be used for. So, what would be an >> appropriate use of finalization?
> They are for just-in-case cleanup after sloppy programming. That is > one explanation for their inclusion in the Java language.
> What you can do with a finalizer is place an assertion in it which signals an > error if it finds the object to be other than cleaned up. Kind of like a > mortician checking for any signs of life before replacing the blood with > formaldehyde. ;)
Actually here is one more potential use. Suppose that you have some container data structure which holds on to objects, such as perhaps a global list, which allows you to iterate over some collection of related objects. It would be nice to be able to say that ``this collection only tentatively references these objects''. So that when no other part of the program has a reference, a cleanup routine is executed which removes the object from that collection.
In a part of a C++ program that I wrote long ago, there was a master object which held on to a collection of reference-counted subordinate objects. It owned a reference to each one of them. When the reference count dropped to 1, it would remove the object from its collection and then drop the reference one more time. A periodic timer callback, or perhaps it was a dedicated thread, would sweep over the collection from time to time and perform this action. So that's a kind of finalization mechanism.
Finalization cannot exist without a special object state distinct from ``garbage''. Java calls it ``finalizable'', with a further complication that a finalizable object may hold references to others, which are not finalizable independently of it, and so are in the ``finalizer-reachable'' state.
The problem with built-in finalization is that it's useless for doing the kind of thing I describe above unless there is a way to ``bless'' a reference as being tentative, weak or whatever you want to call it.
"Kaz Kylheku" <k...@ashi.footprints.net> wrote in message news:afkl5g$o3v$1@luna.vcn.bc.ca... > In article <Y9bT8.342189$352.40764@sccrnsc02>, Joe Marshall wrote:
> > I have the same question as Marcin... > > If an object is not referenced by anybody, how can I run a "destruction" > > code? > > It is necessary for releasing external resources..
> Every time I see a question about finalization, I hear lots of > admonition about what it shouldn't be used for. So, what would be an > appropriate use of finalization?
> I hear "external resources", but "not Unix FDs", which are possibly > the most forgiving external resource I can think of. So what would be > an external resource for which finalization would be appropriate?
Only someone who's never had a server run out of FDs would think they're forgiving :-). It's okay if your application keeps a predictably and constant sized pool of unused FDs around, but if it leaks them in a way that the number of unused FDs increases with time, long-running applications will get burned.
I can think of an instance when it would make sense to use finalizers to close FDs, though. Say you want to deal with data sets that can't fit in your VM. So you implement a class that's a proxy object that you can use like an array (say), but which manages the retrieval of the information from files itself. To keep the illusion of these objects being arrays, you don't want to have to call a destructor on them by hand, because then your code needs to know which arrays are arrays, and which are your proxy objects. So you use finalization to close the FDs the proxy object has open, possibly free memory in the C heap, etc. You can deal with the problem of keeping the number of FDs bound, seperately: eg, keep a global pool of them for use by this class, and allow them to steal them from one another, as needed.
-- /|_ .-----------------------. ,' .\ / | No to Imperialist war | ,--' _,' | Wage class war! | / / `-----------------------' ( -. | | ) | (`-. '--.) `. )----'
> Every time I see a question about finalization, I hear lots of > admonition about what it shouldn't be used for. So, what would be > an appropriate use of finalization?
I wrote some code that attempted to make it both easy and efficient to do 3D graphics from lisp by interfacing to an existing retained mode API. "Retained mode" means that every time you want to draw a 3D object on screen, instead of explicitly rendering every vertex and surface and normal and texture (as in OpenGL), you pass it an object you've previously built using the API that contains all those things.
I wanted it to be as easy and natural for people to use 3D objects they'd created as it was to use standard lisp data types. E.g., something like this to create a window displaying a rendered sphere:
In particular I wanted to shield users from the reference counting scheme that the underlying API used to manage the memory used by all these objects, so I used MCL's finalization facility (MCL calls it termination). It worked extremely well and freed users from having to deal with the details of memory management that often annoyed (me, at least) when writing code in C that used this API.
There was one hitch, similar to one which Erik Naggum describes running into in one of his postings: The 3D objects were allocated from a different heap than lisp objects, so I sometimes ran out of space in the 3D heap because lisp hadn't felt the need to GC. In practice, this happened very rarely. And later, once 64 MB RAM became a typical amount of memory for a workstation, it never happened again to me.
The other thing I did was to also offer macros for making explicit management of 3D object lifetime easy. So if someone wanted to write code like the examples above, they could (which was especially great for explorative programming), but if they wanted to they could make optimal use of the 3D heap by instead writing
The with-q3-objects macro used unwind-protect cleanup forms that both deallocated the 3D objects and cancelled finalization on them.
So i guess one possible answer to your question is something like "when you are trying to make foreign data with fine granularity as easy to use as native lisp data." I was extremely happy that I had a finalization facility for this use, and it certainly didn't feel at all sloppy. It let me do 3D graphics in a way that felt very lispy.