[racket-dev] Should `register-finalizer` unwrap impersonators?
Short version: the contract system doesn't allow `register-finalizer` to
be used in Typed Racket.
Long version: consider the following Typed Racket program, in which
instances of `os-resource-wrapper` represent an operating system
resource `os-resource`, which itself is just a counter. It attempts to
register a finalizer for allocated wrappers, which decrements the counter.
#lang typed/racket
(require/typed
ffi/unsafe
[register-finalizer (All (A) (-> A (-> A Any) Void))])
(: os-resource Integer)
(define os-resource 0)
(struct os-resource-wrapper ())
(: alloc-os-resource (-> os-resource-wrapper))
(define (alloc-os-resource)
(set! os-resource (add1 os-resource))
(define w (os-resource-wrapper))
(register-finalizer w (λ (w) (set! os-resource (sub1 os-resource))))
w)
(define w (alloc-os-resource))
(printf "os-resource = ~v~n" os-resource)
(collect-garbage)
(sleep 1) ; give finalizers a chance to run
(printf "os-resource = ~v~n" os-resource)
I get this output:
os-resource = 1
os-resource = 0
The finalizer is being run while the program still has a pointer to the
wrapper object. I think it's because the wrapper object is being
impersonated when it's sent across the contract barrier, and the
*impersonator* is getting the finalizer. (Or it's a chaperone, or an
impostor, or a charlatan, or whatever. Let's go with impersonator.)
In my specific case, the OS resources are OpenGL objects; e.g. vertex
object arrays. The call to `register-finalizer` *must* be in Typed
Racket code because the wrapper contains an (Instance GL-Context<%>),
which can't have a contract put on it, so it can't pass from untyped to
typed code.
Is there any reason for `register-finalizer` to behave this way? Does it
ever make sense to register a finalizer on an impersonator?
Neil ⊥