Skip to content

Commit 0028532

Browse files
mknyszekgopherbot
authored andcommitted
unique: use a bespoke canonicalization map and runtime.AddCleanup
This change moves the unique package away from using a concurrent map and instead toward a bespoke concurrent canonicalization map. The map holds all its keys weakly, though keys may be looked up by value. The result is the strong pointer for the canonical value. Entries in the map are automatically cleaned up once the canonical reference no longer exists. Why do this? There's a problem with the current implementation when it comes to chains of unique.Handle: because the unique map will have a unique.Handle stored in its keys, each nested handle must be cleaned up 1 GC at a time. It takes N GC cycles, at minimum, to clean up a nested chain of N handles. This implementation, where the *only* value in the set is weakly-held, does not have this problem. The entire chain is dropped at once. The canon map implementation is a stripped-down version of HashTrieMap. The weak set implementation also has lower memory overheads by virtue of the fact that keys are all stored weakly. Whereas the previous map had both a T and a weak.Pointer[T], this *only* has a weak.Pointer[T]. The canonicalization map is a better abstraction overall and dramatically simplifies the unique.Make code. While we're here, delete the background goroutine and switch to runtime.AddCleanup. This is a step toward fixing #71772. We still need some kind of back-pressure mechanism, which will be implemented in a follow-up CL. For #71772. Fixes #71846. Change-Id: I5b2ee04ebfc7f6dd24c2c4a959dd0f6a8af24ca4 Reviewed-on: https://go-review.googlesource.com/c/go/+/650256 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: David Chase <[email protected]> Auto-Submit: Michael Knyszek <[email protected]>
1 parent 6681ff9 commit 0028532

File tree

6 files changed

+636
-181
lines changed

6 files changed

+636
-181
lines changed

src/runtime/mfinal.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ func isGoPointerWithoutSpan(p unsafe.Pointer) bool {
324324
// blockUntilEmptyFinalizerQueue blocks until either the finalizer
325325
// queue is emptied (and the finalizers have executed) or the timeout
326326
// is reached. Returns true if the finalizer queue was emptied.
327-
// This is used by the runtime and sync tests.
327+
// This is used by the runtime, sync, and unique tests.
328328
func blockUntilEmptyFinalizerQueue(timeout int64) bool {
329329
start := nanotime()
330330
for nanotime()-start < timeout {
@@ -342,6 +342,11 @@ func blockUntilEmptyFinalizerQueue(timeout int64) bool {
342342
return false
343343
}
344344

345+
//go:linkname unique_runtime_blockUntilEmptyFinalizerQueue unique.runtime_blockUntilEmptyFinalizerQueue
346+
func unique_runtime_blockUntilEmptyFinalizerQueue(timeout int64) bool {
347+
return blockUntilEmptyFinalizerQueue(timeout)
348+
}
349+
345350
// SetFinalizer sets the finalizer associated with obj to the provided
346351
// finalizer function. When the garbage collector finds an unreachable block
347352
// with an associated finalizer, it clears the association and runs

src/runtime/mgc.go

+1-26
Original file line numberDiff line numberDiff line change
@@ -1835,8 +1835,7 @@ func gcResetMarkState() {
18351835
// Hooks for other packages
18361836

18371837
var poolcleanup func()
1838-
var boringCaches []unsafe.Pointer // for crypto/internal/boring
1839-
var uniqueMapCleanup chan struct{} // for unique
1838+
var boringCaches []unsafe.Pointer // for crypto/internal/boring
18401839

18411840
// sync_runtime_registerPoolCleanup should be an internal detail,
18421841
// but widely used packages access it using linkname.
@@ -1857,22 +1856,6 @@ func boring_registerCache(p unsafe.Pointer) {
18571856
boringCaches = append(boringCaches, p)
18581857
}
18591858

1860-
//go:linkname unique_runtime_registerUniqueMapCleanup unique.runtime_registerUniqueMapCleanup
1861-
func unique_runtime_registerUniqueMapCleanup(f func()) {
1862-
// Create the channel on the system stack so it doesn't inherit the current G's
1863-
// synctest bubble (if any).
1864-
systemstack(func() {
1865-
uniqueMapCleanup = make(chan struct{}, 1)
1866-
})
1867-
// Start the goroutine in the runtime so it's counted as a system goroutine.
1868-
go func(cleanup func()) {
1869-
for {
1870-
<-uniqueMapCleanup
1871-
cleanup()
1872-
}
1873-
}(f)
1874-
}
1875-
18761859
func clearpools() {
18771860
// clear sync.Pools
18781861
if poolcleanup != nil {
@@ -1884,14 +1867,6 @@ func clearpools() {
18841867
atomicstorep(p, nil)
18851868
}
18861869

1887-
// clear unique maps
1888-
if uniqueMapCleanup != nil {
1889-
select {
1890-
case uniqueMapCleanup <- struct{}{}:
1891-
default:
1892-
}
1893-
}
1894-
18951870
// Clear central sudog cache.
18961871
// Leave per-P caches alone, they have strictly bounded size.
18971872
// Disconnect cached list before dropping it on the floor,

0 commit comments

Comments
 (0)