[comp.lang.scheme] Ports & GC

sfk@otter.hpl.hp.com (Steve Knight) (04/24/91)

I've been reading the most recent draft standard I've got (Nov89) and can't
determine the answer to the following question.  When a port is garbage
collected, is it automatically closed on your behalf?  Also, when a port is
created, will every attempt (including garbage-collection) be made to free
unused ports?

If this isn't a question answered by the draft standard then I'd appreciate
knowing the general implementation strategies.

Steve

kend@data.UUCP (Ken Dickey) (04/26/91)

sfk@otter.hpl.hp.com (Steve Knight) writes:

>I've been reading the most recent draft standard I've got (Nov89) and can't
>determine the answer to the following question.  When a port is garbage
>collected, is it automatically closed on your behalf?  ...

This is unspecified in the standard.


>If this isn't a question answered by the draft standard then I'd appreciate
>knowing the general implementation strategies.

Here are a couple:

  Maintain a table of open ports.  At collection time, scan the table and
close any ports which have been collected.

 Implement finalization objects.  I did this in a local copy of the
Gambit runtime (v1.5) by implementing weak-pairs (the car goes to nil
when collected).  The finalization procedure registers each object to
be finalized with a thunk which does the cleanup.  The object is in
the car of a weak pair, the thunk in the cdr.  After a collection, the
weak-pairs are scanned and any who's car is nil is thrown away after
the finalization thunk is invoked.  {The rules are that such a thunk
does not cons and does not invoke random continuations.}

==========

A finalization service is also helpful for freeing storage for dumb
allocators like C's malloc.  Weaks/populations, etc. are helpful in a
variety of ways.

Weak-pairs are easy to implement in a stop-and-copy collector, by the
way.  There is a very clear description of this in Jim Millers thesis:
"MultiScheme", MIT/LCS/TR-402, September 1987.

I can send you code if that is interesting.


-Ken Dickey				kend@data.uucp

news@argosy.UUCP (USENET News System) (04/26/91)

In article <6260006@otter.hpl.hp.com> sfk@otter.hpl.hp.com (Steve Knight) write\
s:
>I've been reading the most recent draft standard I've got (Nov89) and can't
>determine the answer to the following question.  When a port is garbage
>collected, is it automatically closed on your behalf?  Also, when a port is
>created, will every attempt (including garbage-collection) be made to free
>unused ports?
 
In Pixie Scheme (Macintosh Shareware) the heap object for a "port" is a
tagged pointer to another heap object that contains more information.  Thus
if you do
 
(define foo (open-output-file "stuff"))
(define bar foo)
 
then foo and bar will connect to the same file, so that alternate
writes to foo and bar will appear in that file in alternating sequence.
 
The unnamed "other heap object" contains, among other things, a
pointer into a table of non-heap data structures that contain the real
definition, status and so forth for each open port (Pixie Scheme
imposes a fixed limit on how many ports may be open at once, so a
non-heap table is reasonable), and which also contains a back pointer
to the (unique) heap object which referenced it.  On garbage
collection (Pixie Scheme uses two heap spaces with a variant on
mark-and-copy), Pixie Scheme updates each back pointer appropriately.
After the copy, Pixie Scheme looks through the non-heap table of ports
to see which ones have had their back pointers updated to point into
the new space.  The others are garbage, and are closed if still open.
 
Thus in the example given, if "foo" becomes garbage but "bar" is not,
then the heap structure associated with it will still have one pointer
to it from non-garbage (bar), so that heap structure will be non-garbage,
so it will get copied to new space, so its back pointer in non-heap
will be updated to point to new space, so the garbage collector will
know not to close the port.  But if foo and bar are both garbaged,
then the heap structure will not be copied, so its back pointer will
still point into old space, so the garbage collector will close the
port if the user has not done so.
 
The implementation of "open-output-file", etc., knows to look through
the table of non-heap port data structures for one which is not in
use, and fails if there are none.  At the moment, these implementation
routines do not force a garbage-collection if there are no ports free,
though they could be made to do so, and probably I will get around to
making that happen some day.  (I haven't done so merely because it is
clumsy to force garbage collection from any place other than the
memory allocator in my particular Scheme implementation, not because
it is a bad idea.)
 
 
I have no idea how other implementations do it, and would be curious
myself about the answers.
 
                                         -- Jay Freeman
 
 
          <canonical disclaimer -- I speak only for myself>

mikpe@ida.liu.se (Mikael Pettersson) (04/27/91)

In article <1262@argosy.UUCP> freeman@cleo.UUCP (Jay R. Freeman) writes:
> [describes the approach taken in Pixie Scheme]
>I have no idea how other implementations do it, and would be curious
>myself about the answers.

Here's how my Scheme handles garbage ports:

* A port object contains, among other things, a C file-pointer and
  a weak pointer to another port (or NIL).

* When a new port is allocated (by open-{input,output}-file), it's added
  to a "list" (using the weak "next port" ptr) of all ports.

* After each collection, this list is scanned. Surviving ports are
  (re-)linked together by updating their "next port" pointers. Dead
  ports are closed (unless close-{input,output}-port did that before)
  and then forgotten.

A disadvantage of this approach is that the last scanning phase could
exhibit poor locality, touching random pages both in the old and new
heaps. On the other hand, the average number of ports is usually less
than 10 (3-6 seems to be the average in my system) so this is probably
not a serious problem. The advantages or having safe and fast deallocation
of a scarce OS resource are much more important.

(I have seen Lisp/Scheme implementations (mark-sweep based) that would
check, for each dead object, whether it was a port, and if so do the
cleanup action. Not quite what I would call "efficient"..)


/Mike
-- 
Mikael Pettersson, Dept of Comp & Info Sci, University of Linkoping, Sweden
email: mpe@ida.liu.se or ...!{mcsun,munnari,uunet,unido,...}!sunic!liuida!mpe

gyro@cymbal.reasoning.COM (Scott Layson Burson) (04/30/91)

   Date: 26 Apr 91 15:40:30 GMT
   From: Ken Dickey <data!kend@bloom-beacon.mit.edu>

   [Answering a question about whether garbage ports are closed automatically]

    Implement finalization objects.  I did this in a local copy of the
   Gambit runtime (v1.5) by implementing weak-pairs (the car goes to nil
   when collected).  The finalization procedure registers each object to
   be finalized with a thunk which does the cleanup.  The object is in
   the car of a weak pair, the thunk in the cdr.  After a collection, the
   weak-pairs are scanned and any who's car is nil is thrown away after
   the finalization thunk is invoked.  {The rules are that such a thunk
   does not cons and does not invoke random continuations.}

   A finalization service is also helpful for freeing storage for dumb
   allocators like C's malloc.  Weaks/populations, etc. are helpful in a
   variety of ways.

Somebody on this list recently alluded to "the well-known difficulty of
defining UNWIND-PROTECT in the presence of firstclass continuations".

I have long thought that what UNWIND-PROTECT ought to mean is that the
cleanup actions are performed when the continuation in which they were
established becomes garbage.

As Ken points out, this really isn't all that hard to implement.

-- Scott
Gyro@Reasoning.COM

jinx@zurich.ai.mit.edu (Guillermo J. Rozas) (04/30/91)

In article <9104291751.AA10911@cymbal.reasoning.com.> gyro@cymbal.reasoning.COM (Scott Layson Burson) writes:

   Somebody on this list recently alluded to "the well-known difficulty of
   defining UNWIND-PROTECT in the presence of firstclass continuations".

   I have long thought that what UNWIND-PROTECT ought to mean is that the
   cleanup actions are performed when the continuation in which they were
   established becomes garbage.

That is not clear at all.
The standard example of UNWIND-PROTECT (from CLtLII) is

(unwind-protect
 (progn (start-motor)
	(drill-hole))
 (stop-motor))

I don't think that waiting until the continuation is garbage to stop
the motor is what the user intended.

Sometimes you want it to happen on any exit from the protected action.
Sometimes you want it to happen on "final" exit, whatever that means.

   As Ken points out, this really isn't all that hard to implement.

The issue is not implementation, but what the correct behavior should
be.

kend@data.UUCP (Ken Dickey) (05/02/91)

gyro@cymbal.reasoning.COM (Scott Layson Burson) writes:
>I have long thought that what UNWIND-PROTECT ought to mean is that the
>cleanup actions are performed when the continuation in which they were
>established becomes garbage.

>As Ken points out, this really isn't all that hard to implement.

A point of clarification.  I was referring to the implementation of a
finalization service on top of weak-pairs.  Weak pairs are very easy
to implement in a stop-and-copy (semi-space) collector.  Your mileage
may vary with other collection strategies.  Also, finalization is
related to lifetime/extent.  Unwind-protect deals more directly with
control flow.

{I still think its a good idea to turn off the motor (8-D)}.

-Ken					kend@data.uucp

sfk@otter.hpl.hp.com (Steve Knight) (05/10/91)

Thanks for the responses.  I'm somewhat disappointed that the standard doesn't
specify that files are closed at both port creation & finalisation times.
I think it's an issue on the same order as tail-call optimisation (although
less important.)

Steve