Ensuring that channels are GC'd when the child thread terminates

34 views
Skip to first unread message

David Storrs

unread,
Oct 1, 2019, 11:52:19 PM10/1/19
to Racket Users
I have a server with a main dispatcher loop that receives messages and
farms them out to processing threads. I'd like to have able to have
later messages inform the behavior of earlier message processing -- an
example would be "message 1: here is a file to download" followed by
"stop downloading if you're still doing it".

I'll do the simple version, where it keeps the channels around until
the thread terminates but doesn't actually use the channels:

; main dispatcher loop in the server. Receives messages, dispatches
to child thread for processing
(struct file-info (hash) #:transparent)
(define channels (make-weak-hash))
(let loop ()
(define msg (get-next-message)) ; e.g. (file-info "sha1-hash-of-file-X")
(hash-set! channels (file-info-hash msg) ch)
(thread (thunk (process-file msg ch)))))
(loop))

1) Am I correct that this does not work? The string that came out of
file-info-hash is not eq? to the one stored inside msg, meaning that
it's not referenced from anywhere else, so it's going to disappear the
moment it goes into the weak hash.


2) The following also would not work:
(let loop ()
(define msg (get-next-message))
(define the-str (file-info-hash msg)) ;; save the value out before using it
(hash-set! channels the-str ch)
(thread (thunk (process-file msg ch)))))
(loop))

The (loop) is in tail position, so this is not a recursive call. That
means the stack frame is cleared, so 'the-str' is not reachable and
it's cleared from the weak hash.


2) This WOULD work:
(let loop ()
(define msg (get-next-message)) ; e.g. (file-info "sha1-hash-of-file-X")
(hash-set! channels msg ch)
(thread (thunk (process-file msg ch)))))
(loop))

In this case, 'msg' is still reachable from the processing thread, so
it remains in the weak hash until the processing thread terminates, at
which point it is removed from the weak hash and the corresponding
channel is GC'd.

Matthew Flatt

unread,
Oct 2, 2019, 8:42:22 AM10/2/19
to David Storrs, Racket Users
At Tue, 1 Oct 2019 23:52:05 -0400, David Storrs wrote:
> ; main dispatcher loop in the server. Receives messages, dispatches
> to child thread for processing
> (struct file-info (hash) #:transparent)
> (define channels (make-weak-hash))
> (let loop ()
> (define msg (get-next-message)) ; e.g. (file-info "sha1-hash-of-file-X")
> (hash-set! channels (file-info-hash msg) ch)
> (thread (thunk (process-file msg ch)))))
> (loop))
>
> 1) Am I correct that this does not work? The string that came out of
> file-info-hash is not eq? to the one stored inside msg, meaning that
> it's not referenced from anywhere else, so it's going to disappear the
> moment it goes into the weak hash.

Right.

> 2) The following also would not work:
> (let loop ()
> (define msg (get-next-message))
> (define the-str (file-info-hash msg)) ;; save the value out before using
> it
> (hash-set! channels the-str ch)
> (thread (thunk (process-file msg ch)))))
> (loop))
>
> The (loop) is in tail position, so this is not a recursive call. That
> means the stack frame is cleared, so 'the-str' is not reachable and
> it's cleared from the weak hash.

Also right.

This variant would retain the hash string until `process-file`
completes:

(define the-str (file-info-hash msg))
(hash-set! channels (file-info-hash msg) ch)
(thread (thunk (process-file msg ch)
(hash-remove! channels the-str)))

In this case, it's handy that `hash-remove!` is a sensible use of
`the-str`, and that use's effect means `the-str` really must be
retained.

In cases where there's no sensible way to use a value like that, as a
last resort `void/reference-sink` from `ffi/unsafe` can ensure that a
value remains reachable.

> 2) This WOULD work:
> (let loop ()
> (define msg (get-next-message)) ; e.g. (file-info "sha1-hash-of-file-X")
> (hash-set! channels msg ch)
> (thread (thunk (process-file msg ch)))))
> (loop))
>
> In this case, 'msg' is still reachable from the processing thread, so
> it remains in the weak hash until the processing thread terminates, at
> which point it is removed from the weak hash and the corresponding
> channel is GC'd.

It depends on `process-file`. If `process-file` retains `msg` until it
is otherwise done, then yes. But if `process-file` ignores the argument
for `msg` or only uses it for the first part of its work, then `msg`
can get GCed before `process-file` returns.

David Storrs

unread,
Oct 2, 2019, 11:06:27 AM10/2/19
to Matthew Flatt, Racket Users
On Wed, Oct 2, 2019 at 8:42 AM Matthew Flatt <mfl...@cs.utah.edu> wrote:
>
> At Tue, 1 Oct 2019 23:52:05 -0400, David Storrs wrote:
> > ; main dispatcher loop in the server. Receives messages, dispatches
> > to child thread for processing
>
> > 2) This WOULD work:
> > (let loop ()
> > (define msg (get-next-message)) ; e.g. (file-info "sha1-hash-of-file-X")
> > (hash-set! channels msg ch)
> > (thread (thunk (process-file msg ch)))))
> > (loop))
> >
> > In this case, 'msg' is still reachable from the processing thread, so
> > it remains in the weak hash until the processing thread terminates, at
> > which point it is removed from the weak hash and the corresponding
> > channel is GC'd.
>
> It depends on `process-file`. If `process-file` retains `msg` until it
> is otherwise done, then yes. But if `process-file` ignores the argument
> for `msg` or only uses it for the first part of its work, then `msg`
> can get GCed before `process-file` returns.

If process-file looked like this:

(define (process-file msg ch)
(define the-hash-str (file-info-hash msg))
...do stuff with the-hash-str...
)

I think this would mean that msg could get GC'd as early as right
after the first line, but it's not guaranteed as to when it would be
GC'd. Yes?

Matthew Flatt

unread,
Oct 2, 2019, 4:16:25 PM10/2/19
to David Storrs, Racket Users
Yes, essentially. But depending on `file-info-hash`, `msg` might be
GC'd before `file-info-hash` returns, which is even before the second
line within `process-file`.

David Storrs

unread,
Oct 2, 2019, 4:57:12 PM10/2/19
to Matthew Flatt, Racket Users
Ok, thanks. 
Reply all
Reply to author
Forward
0 new messages