Fix: core.async/timeout combined with core.async/go prevented fat dynamic var GC#75769
Merged
Conversation
async.core/timeout combined with async.core/go prevented fat dynamic var GC
async.core/timeout combined with async.core/go prevented fat dynamic var GCcore.async/timeout combined with core.async/go prevented fat dynamic var GC
ericnormand
approved these changes
Jun 12, 2026
eliotmetabase
pushed a commit
that referenced
this pull request
Jun 12, 2026
… prevented fat dynamic var GC" (#75796) Fix: `core.async/timeout` combined with `core.async/go` prevented fat dynamic var GC (#75769) * This has to be the strangest Clojure footgun I've seen in many years. * Kondo-ignore in the test file what the src file ignores Co-authored-by: Timothy S. Dean <timothy.dean@metabase.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #75748.
Description
Well this was weird.
async.core/gosomewhat reasonably conveys dynamic bindings to a new thread.core.async.impl.timerssomewhat unreasonably stashes timeout queue entries without weak references.It's got to be an unusual scenario, though: we put a long timeout (20 minutes) on an
async.core/timeoutchannel inside agoblock, and that results in our cached metadata provider (which is usually created and thrown away immediately) being un-GC-able for 20 minutes, no matter whether we've forgotten about that metadata-provider and assumed it would be cleaned up. Run this code in a tight loop, multiple times per second, caching data warehouse metadata in each one, and 💥 .Every single query-processor invocation (dashboard card, API query, internal compile...) left behind a ~1MB memory anchor that nothing could release until the 20-minute timer expired.
Regression test included.
Alternatives Considered
a/take!with a callback + a done-flag works (this performs no binding conveyance). But this would be one well-meaning "simplify this to agoblock" away from regressing. We use scheduled-executors elsewhere already.Checklist