A first attempt at a section explaining reachabilty, weak boxes,
and how to test using them
This commit is contained in:
parent
5163d424c3
commit
280d924349
|
@ -414,6 +414,112 @@ then the expansion of the @racket[let] form to implement
|
||||||
automatically converts the closure to pass itself @racket[n] as an
|
automatically converts the closure to pass itself @racket[n] as an
|
||||||
argument instead.
|
argument instead.
|
||||||
|
|
||||||
|
@section{Reachability and Garbage Collection}
|
||||||
|
|
||||||
|
In general, Racket re-uses the storage for a value when the
|
||||||
|
garbage collector can prove that the object is unreachable from
|
||||||
|
any other (reachable) value. Reachability is a low-level,
|
||||||
|
abstraction breaking concept (and thus one must understand many
|
||||||
|
details of the runtime system's implementation to accurate predicate
|
||||||
|
precisely when values are reachable from each other),
|
||||||
|
but generally speaking one value is reachable from a second one when
|
||||||
|
there is some operation to recover the original value from the second
|
||||||
|
one.
|
||||||
|
|
||||||
|
To help programmers understand when an object is no longer reachable and its
|
||||||
|
storage can be reused,
|
||||||
|
Racket provides @racket[make-weak-box] and @racket[weak-box-value],
|
||||||
|
the creator and accessor for a one-record struct that the garbage
|
||||||
|
collector treats specially. An object inside a weak box does not count
|
||||||
|
as reachable, and so @racket[weak-box-value] might return the object
|
||||||
|
inside the box, but it might also return @racket[#f] to indicate
|
||||||
|
that the object was otherwise unreachable and garbage collected.
|
||||||
|
Note that unless a garbage collection actually occurs, the value will
|
||||||
|
remain inside the weak box, even if it is unreachable.
|
||||||
|
|
||||||
|
For example, consider this program:
|
||||||
|
@racketmod[racket
|
||||||
|
(struct fish (weight color) #:transparent)
|
||||||
|
(define f (fish 7 'blue))
|
||||||
|
(define b (make-weak-box f))
|
||||||
|
(printf "b has ~s\n" (weak-box-value b))
|
||||||
|
(collect-garbage)
|
||||||
|
(printf "b has ~s\n" (weak-box-value b))]
|
||||||
|
It will print @litchar{b has #(struct:fish 7 blue)} twice because the
|
||||||
|
definition of @racket[f] still holds onto the fish. If the program
|
||||||
|
were this, however:
|
||||||
|
@racketmod[racket
|
||||||
|
(struct fish (weight color) #:transparent)
|
||||||
|
(define f (fish 7 'blue))
|
||||||
|
(define b (make-weak-box f))
|
||||||
|
(printf "b has ~s\n" (weak-box-value b))
|
||||||
|
(set! f #f)
|
||||||
|
(collect-garbage)
|
||||||
|
(printf "b has ~s\n" (weak-box-value b))]
|
||||||
|
the second printout will be @litchar{b has #f} because
|
||||||
|
no reference to the fish exists (other than the one in the box).
|
||||||
|
|
||||||
|
As a first approximation, all values in Racket must be allocated and will
|
||||||
|
demonstrate behavior similar to the fish above.
|
||||||
|
There are a number of exceptions, however:
|
||||||
|
@itemlist[@item{Small integers (recognizable with @racket[fixnum?]) are
|
||||||
|
always available without explicit
|
||||||
|
allocation. From the perspective of the garbage collector
|
||||||
|
and weak boxes, their storage is never reclaimed. (Due to
|
||||||
|
clever representation techniques, however, their storage
|
||||||
|
does not count towards the space that Racket uses.
|
||||||
|
That is, they are effectively free.)}
|
||||||
|
@item{Procedures where
|
||||||
|
the compiler can see all of their call sites may never be
|
||||||
|
allocated at all (as discussed above).
|
||||||
|
Similar optimizations may also eliminate
|
||||||
|
the allocation for other kinds of values.}
|
||||||
|
@item{Interned symbols are allocated only once (per place). A table inside
|
||||||
|
Racket tracks this allocation so a symbol may not become garbage
|
||||||
|
because that table holds onto it.}
|
||||||
|
@item{Reachability is only approximate with the CGC collector (i.e.,
|
||||||
|
a value may appear reachable to that collector when there is,
|
||||||
|
in fact, no way to reach it anymore.}]
|
||||||
|
|
||||||
|
@section{Weak Boxes and Testing}
|
||||||
|
|
||||||
|
One important use of weak boxes is in testing that some abstraction properly
|
||||||
|
releases storage for data it no longer needs, but there is a gotcha that
|
||||||
|
can easily cause such test cases to pass improperly.
|
||||||
|
|
||||||
|
Imagine you're designing a data structure that needs to
|
||||||
|
hold onto some value temporarily but then should clear a field or
|
||||||
|
somehow break a link to avoid referencing that value so it can be
|
||||||
|
collected. Weak boxes are a good way to test that your data structure
|
||||||
|
properly clears the value. This is, you might write a test case
|
||||||
|
that builds a value, extracts some other value from it
|
||||||
|
(that you hope becomes unreachable), puts the extracted value into a weak-box,
|
||||||
|
and then checks to see if the value disappears from the box.
|
||||||
|
|
||||||
|
This code is one attempt to follow that pattern, but it has a subtle bug:
|
||||||
|
@racketmod[racket
|
||||||
|
(let* ([fishes (list (fish 8 'red)
|
||||||
|
(fish 7 'blue))]
|
||||||
|
[wb (make-weak-box (list-ref fishes 0))])
|
||||||
|
(collect-garbage)
|
||||||
|
(printf "still there? ~s\n" (weak-box-value wb)))]
|
||||||
|
Specifically, it will show that the weak box is empty, but not
|
||||||
|
beacause @racket[_fishes] no longer holds onto the value, but
|
||||||
|
because @racket[_fishes] itself is not reachable anymore!
|
||||||
|
|
||||||
|
Change the program to this one:
|
||||||
|
@racketmod[racket
|
||||||
|
(let* ([fishes (list (fish 8 'red)
|
||||||
|
(fish 7 'blue))]
|
||||||
|
[wb (make-weak-box (list-ref fishes 0))])
|
||||||
|
(collect-garbage)
|
||||||
|
(printf "still there? ~s\n" (weak-box-value wb))
|
||||||
|
(printf "fishes is ~s\n" fishes))]
|
||||||
|
and now we see the expected result. The difference is that last
|
||||||
|
occurrence of the variable @racket[_fishes]. That constitutes
|
||||||
|
a reference to the list, ensuring that the list is not itself
|
||||||
|
garbage collected, and thus the red fish is not either.
|
||||||
|
|
||||||
@; ----------------------------------------------------------------------
|
@; ----------------------------------------------------------------------
|
||||||
|
|
||||||
@include-section["futures.scrbl"]
|
@include-section["futures.scrbl"]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user