Skip to content

osfs: mmap, use AddCleanup instead of finalizer and add test#218

Merged
pjbgf merged 4 commits into
go-git:mainfrom
jfontan:mmap-addcleanup
Jul 2, 2026
Merged

osfs: mmap, use AddCleanup instead of finalizer and add test#218
pjbgf merged 4 commits into
go-git:mainfrom
jfontan:mmap-addcleanup

Conversation

@jfontan

@jfontan jfontan commented May 28, 2026

Copy link
Copy Markdown
Member

Since go 1.24 it seems that the preferred way to cleanup after GC is runtime.AddCleanup. The change does basically the same. It's even quite the same case as the example in the go blog introducing this function: https://go.dev/blog/cleanups-and-weak

I've also added a test for Linux that checks that the finalizer/cleanup works (works with the two versions) so at least we are sure we have not misplaced the finalizer. The test is in a separate commit in case you are not interested in the AddCleanup change.

This odd dance with variables was done to not trigger the linter as after cleaning f we don't do anything with it.

	_ = f.Name()
	f = nil
	_ = f

I've found that cleaning the variable is not needed as the compiler is smart enough to know that f is not used anymore after f.Name() and runtime.GC triggers the finalizer. I just wanted to be explicit.

jfontan added 2 commits May 28, 2026 23:36
Signed-off-by: Javi Fontan <jfontan@gmail.com>
From go 1.24 this seems the preferred method to clean after GC. The
example from the go blog post is also about mmap files.

https://go.dev/blog/cleanups-and-weak

Signed-off-by: Javi Fontan <jfontan@gmail.com>
Copilot AI review requested due to automatic review settings May 28, 2026 21:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates mmap cleanup in osfs to use Go’s runtime.AddCleanup instead of finalizers, and adds a Linux-specific test to verify abandoned mmap handles are unmapped after GC.

Changes:

  • Adds a runtime.Cleanup handle to mmapFile and stops it during explicit Close.
  • Registers mmap cleanup via runtime.AddCleanup.
  • Adds a Linux /proc/<pid>/maps-based cleanup test.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
osfs/mmap_file.go Replaces finalizer-based mmap cleanup with runtime.AddCleanup.
osfs/mmap_file_cleanup_test.go Adds Linux coverage for GC-triggered mmap cleanup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread osfs/mmap_file.go Outdated
// will munmap and close the fd when m becomes unreachable. Close
// clears this finalizer on the orderly path.
runtime.SetFinalizer(m, (*mmapFile).Close)
m.cleanup = runtime.AddCleanup(m, mmapClean, data)

@jfontan jfontan May 28, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand that from the documentation:

func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup

If ptr is reachable from cleanup or arg, ptr will never be collected and the cleanup will never run. As a protection against simple cases of this, AddCleanup panics if arg is equal to ptr.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a variation of the case right? m holds data and data is passed as argument.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ptr is not reachable from cleanup or arg. In other words m is not reachable from mmapClean or data. GC must be able to collect m without mmapClean or data referencing it. The clean function with data is called after GC is done.

You can collect the struct *m even if it points to other objects, these will be collected later.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment to this effect. The issue is that mmapClean had a direct ref to m's fields. Any ref to a field of an object makes the object itself reachable, and therefore it won't be garbage collected.

Comment thread osfs/mmap_file.go Outdated
Comment on lines 71 to 73
// Belt and braces for callers that forget to Close: the runtime
// will munmap and close the fd when m becomes unreachable. Close
// clears this finalizer on the orderly path.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fd will be closed by the os.File finalizer but it's true that the comment should be changed or make the cleanup function close the file explicitly. I'll take a look.

Comment thread osfs/mmap_file_cleanup_test.go Outdated
Comment on lines +34 to +38
runtime.GC()

ok, err = isFileMapped(path)
require.NoError(t, err)
require.False(t, ok)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check this.

@jfontan jfontan force-pushed the mmap-addcleanup branch 2 times, most recently from 2780a1b to b0664c8 Compare June 2, 2026 20:28
Signed-off-by: Javi Fontan <jfontan@gmail.com>
@jfontan jfontan force-pushed the mmap-addcleanup branch from b0664c8 to 286d10f Compare June 2, 2026 20:31
@jfontan

jfontan commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

Added a new commit with fixes for copilot reviews (but messed up with two different branches in two computers).

If needed I can squash the changes in the original two commits or one.

Comment thread osfs/mmap_file.go Outdated
"github.com/stretchr/testify/require"
)

func TestRootOSMmapCleanup(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it is worth creating a test for this, as this is likely going to become intermittent. As per upstream docs:

The cleanup(arg) call is not always guaranteed to run; in particular it is not guaranteed to run before program exit.
...
Note that because cleanups may execute arbitrarily far into the future after an object is no longer referenced

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've followed the hit from copilot and the test does 10 cycles of GC before failing. If it becomes flaky we can delete it but I believe is good to have a test that checks that we set up the cleanup function.

Maybe we can add a comment stating this.

@pjbgf pjbgf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jfontan thanks for looking into this. I think we need to drop the direct ref to the object as per above, otherwise I agree this is a great addition to the project. 👍

Co-authored-by: Paulo Gomes <paulo.gomes.uk@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
@jfontan jfontan force-pushed the mmap-addcleanup branch from 89b982d to 0741775 Compare July 1, 2026 17:21

@pjbgf pjbgf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jfontan thanks for working on this. 🙇

@pjbgf pjbgf merged commit 606e1d5 into go-git:main Jul 2, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants