Skip to content

Ephemerons and Guardians (SRFI-254)

Weak references and post-mortem finalization, integrated with the garbage collector. Import with (import (srfi 254)).

An ephemeron is a key/value pair whose value is kept alive only while its key is reachable through some path that does not pass through the value. When the key becomes unreachable, the collector breaks the ephemeron: key and value both read as #f afterwards. Unlike a plain weak-key pair, an ephemeron breaks even when its value references its own key — the classic leak case that motivates the abstraction.

A guardian provides post-mortem finalization: objects registered with a guardian are resurrected by the collector once they become otherwise unreachable, so cleanup code can run for them after death.

The component libraries (srfi 254 ephemerons), (srfi 254 guardians), and (srfi 254 transport-cell-guardians) export each slice separately; (srfi 254 ephemerons-and-guardians) and (srfi 254) export everything.

Note

Kaappi's collector is non-moving, so current-hash is a stable identity hash and transport cell guardians are degenerate: a key is never transported, a registered cell never breaks, and calling a transport cell guardian with no arguments always returns #f. The API is still provided in full for portability.


Ephemerons

make-ephemeron

Syntax: (make-ephemeron key value)

Returns a new ephemeron associating key with value. The collector retains value only while key is reachable through a path that does not pass through value; once key becomes unreachable the ephemeron is broken and both slots read as #f.

kaappi> (define e (make-ephemeron 'k 'v))
kaappi> (ephemeron-key e)
;=> k
kaappi> (ephemeron-value e)
;=> v

ephemeron?

Syntax: (ephemeron? obj)

Returns #t if obj is an ephemeron, #f otherwise.

kaappi> (ephemeron? (make-ephemeron 'k 'v))
;=> #t
kaappi> (ephemeron? 'x)
;=> #f

ephemeron-key

Syntax: (ephemeron-key ephemeron)

Returns the key of ephemeron, or #f if the ephemeron has been broken.

ephemeron-value

Syntax: (ephemeron-value ephemeron)

Returns the value of ephemeron, or #f if the ephemeron has been broken. On break the value slot is cleared, so a broken ephemeron never retains its value.

ephemeron-broken?

Syntax: (ephemeron-broken? ephemeron)

Returns #t if the collector has broken ephemeron (its key became unreachable), #f otherwise.

kaappi> (define e (make-ephemeron 'k 'v))
kaappi> (ephemeron-broken? e)
;=> #f

ephemeron-ref

Syntax: (ephemeron-ref ephemeron key) | (ephemeron-ref ephemeron key default)

Returns the value of ephemeron if it is unbroken and its key is eq? to key; otherwise returns default (#f when omitted). This is the lookup shape used when building weak tables out of ephemerons.

kaappi> (define e (make-ephemeron 'k 'v))
kaappi> (ephemeron-ref e 'k)
;=> v
kaappi> (ephemeron-ref e 'other 'missing)
;=> missing

Guardians

make-guardian

Syntax: (make-guardian)

Returns a new guardian. A guardian is itself a procedure with three calling forms:

  • (g obj) — register obj with the guardian
  • (g obj rep) — register obj, with rep as the representative to hand back in its place
  • (g) — return one object that has died since the last call (or its representative), or #f if none is pending

When a registered object becomes unreachable, the collector resurrects it: subsequent zero-argument calls on the guardian return it (or its representative) for cleanup. Objects that are still reachable are never returned.

(define g (make-guardian))
(g (open-input-file "data.txt") 'data-port)

;; ... after the port becomes unreachable and a collection runs:
(let loop ()
  (let ((dead (g)))
    (when dead
      ;; run finalization for dead, e.g. log or release resources
      (loop))))

guardian?

Syntax: (guardian? obj)

Returns #t if obj is a guardian created by make-guardian, #f otherwise — including for transport cell guardians, which are a distinct type.

kaappi> (guardian? (make-guardian))
;=> #t
kaappi> (guardian? (make-transport-cell-guardian))
;=> #f

Transport cell guardians

Transport cell guardians exist for collectors that move (transport) objects, letting a program observe when a key is relocated. See the note at the top of this page: on Kaappi's non-moving collector they are degenerate but fully present.

make-transport-cell-guardian

Syntax: (make-transport-cell-guardian)

Returns a new transport cell guardian — a procedure with two calling forms:

  • (tcg key value) — register key/value, returning a transport cell
  • (tcg) — return a cell whose key was transported since the last call, or #f if none (always #f on Kaappi's non-moving collector)
kaappi> (define tcg (make-transport-cell-guardian))
kaappi> (define c (tcg 'k 'v))
kaappi> (transport-cell? c)
;=> #t

transport-cell-guardian?

Syntax: (transport-cell-guardian? obj)

Returns #t if obj is a transport cell guardian, #f otherwise.

transport-cell?

Syntax: (transport-cell? obj)

Returns #t if obj is a transport cell (the object returned by registering a key with a transport cell guardian), #f otherwise.

transport-cell-key

Syntax: (transport-cell-key cell)

Returns the key registered in cell, or #f if the cell has been broken.

transport-cell-value

Syntax: (transport-cell-value cell)

Returns the value registered in cell, or #f if the cell has been broken.

transport-cell-broken?

Syntax: (transport-cell-broken? cell)

Returns #t if cell has been broken, #f otherwise. Never #t on Kaappi's non-moving collector.

current-hash

Syntax: (current-hash obj)

Returns an exact non-negative integer identity hash for obj. On Kaappi's non-moving collector the result is stable for the lifetime of the object — two calls on the same (eq?) object always agree, across collections.

kaappi> (define o (list 1 2 3))
kaappi> (= (current-hash o) (current-hash o))
;=> #t

Shared

reference-barrier

Syntax: (reference-barrier obj)

Guarantees that obj is treated as reachable up to this point in the program, preventing the collector from breaking ephemerons keyed on it (or resurrecting it through a guardian) any earlier. Returns an unspecified value.