DispatchMain gives the block back - #16
Merged
Merged
Conversation
Every call leaked one Objective-C block and one entry in purego's block
table -- the entry that holds the Go closure alive -- for the life of the
process. Nothing ever removed either: purego's dispose helper is what
deletes a table entry, and dispose only runs when the last reference to
the block goes.
Measured, one process posting blocks and draining them on the main thread:
3 576 blocks 14.2 MB resident
42 824 blocks 22.3 MB
161 782 blocks 44.4 MB
about 200 bytes a call, never returned. After:
3 296 blocks 13.5 MB
39 328 blocks 17.8 MB
157 096 blocks 19.7 MB
Growth over the same span falls from +30.2 MB to +6.2 MB. An application
that hops to the main thread once a frame reached the old numbers in
minutes.
Releasing here is safe because dispatch_async takes its own reference, and
must: its contract is that the caller may free the block as soon as the
call returns.
⛔ THE TEST'S SEAM HAD TO BE COMPLETED, NOT JUST USED. The scheduled-hop
test fakes mkBlock with a sentinel; leaving the real rmBlock in place sent
0xB10C to _Block_release and the suite died with "fault 0xb114". A seam
that is only half replaced runs half the real thing.
⚠ NOT a fix for the crash this was found while chasing. go-xrkit/desk's
suite faults inside cfRunLoopRunInMode at iokit hid_darwin.go:434,
invoking a block that belongs here; with this change it still does, 2 runs
in 10 against 4 in 10 before, which is one sample inside another's noise.
That is a separate defect and is filed as one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 8, 2026
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.
Every call leaked one Objective-C block and one entry in purego's block table — the entry that holds the Go closure alive — for the life of the process. Nothing ever removed either: purego's
disposehelper is what deletes a table entry, anddisposeonly runs when the last reference to the block goes.Measured
One process posting blocks and draining them on the main thread:
Growth over the same span falls from +30.2 MB to +6.2 MB — about 200 bytes a call, previously never returned. An application that hops to the main thread once a frame reached the old numbers in minutes.
Releasing here is safe because
dispatch_asynctakes its own reference, and must: its contract is that the caller may free the block as soon as the call returns.The test's seam had to be completed, not just used
The scheduled-hop test fakes
mkBlockwith a sentinel. Leaving the realrmBlockin place sent0xB10Cto_Block_releaseand the suite died withfault 0xb114. A seam that is only half replaced runs half the real thing.⚠ Not a fix for the crash this was found while chasing
go-xrkit/desk's suite faults insidecfRunLoopRunInModeat iokithid_darwin.go:434, invoking a block that belongs to this package. With this change it still does — 2 runs in 10, against 4 in 10 before, which is one small sample inside another's noise. That is a separate defect and is filed as one.🤖 Generated with Claude Code