mirror of https://github.com/golang/go.git
192 lines
6.7 KiB
Go
192 lines
6.7 KiB
Go
// Copyright 2024 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"internal/abi"
|
|
"unsafe"
|
|
)
|
|
|
|
// AddCleanup attaches a cleanup function to ptr. Some time after ptr is no longer
|
|
// reachable, the runtime will call cleanup(arg) in a separate goroutine.
|
|
//
|
|
// A typical use is that ptr is an object wrapping an underlying resource (e.g.,
|
|
// a File object wrapping an OS file descriptor), arg is the underlying resource
|
|
// (e.g., the OS file descriptor), and the cleanup function releases the underlying
|
|
// resource (e.g., by calling the close system call).
|
|
//
|
|
// There are few constraints on ptr. In particular, multiple cleanups may be
|
|
// attached to the same pointer, or to different pointers within the same
|
|
// allocation.
|
|
//
|
|
// 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.
|
|
//
|
|
// There is no specified order in which cleanups will run.
|
|
// In particular, if several objects point to each other and all become
|
|
// unreachable at the same time, their cleanups all become eligible to run
|
|
// and can run in any order. This is true even if the objects form a cycle.
|
|
//
|
|
// A single goroutine runs all cleanup calls for a program, sequentially. If a
|
|
// cleanup function must run for a long time, it should create a new goroutine.
|
|
//
|
|
// If ptr has both a cleanup and a finalizer, the cleanup will only run once
|
|
// it has been finalized and becomes unreachable without an associated finalizer.
|
|
//
|
|
// The cleanup(arg) call is not always guaranteed to run; in particular it is not
|
|
// guaranteed to run before program exit.
|
|
//
|
|
// Cleanups are not guaranteed to run if the size of T is zero bytes, because
|
|
// it may share same address with other zero-size objects in memory. See
|
|
// https://go.dev/ref/spec#Size_and_alignment_guarantees.
|
|
//
|
|
// It is not guaranteed that a cleanup will run for objects allocated
|
|
// in initializers for package-level variables. Such objects may be
|
|
// linker-allocated, not heap-allocated.
|
|
//
|
|
// Note that because cleanups may execute arbitrarily far into the future
|
|
// after an object is no longer referenced, the runtime is allowed to perform
|
|
// a space-saving optimization that batches objects together in a single
|
|
// allocation slot. The cleanup for an unreferenced object in such an
|
|
// allocation may never run if it always exists in the same batch as a
|
|
// referenced object. Typically, this batching only happens for tiny
|
|
// (on the order of 16 bytes or less) and pointer-free objects.
|
|
//
|
|
// A cleanup may run as soon as an object becomes unreachable.
|
|
// In order to use cleanups correctly, the program must ensure that
|
|
// the object is reachable until it is safe to run its cleanup.
|
|
// Objects stored in global variables, or that can be found by tracing
|
|
// pointers from a global variable, are reachable. A function argument or
|
|
// receiver may become unreachable at the last point where the function
|
|
// mentions it. To ensure a cleanup does not get called prematurely,
|
|
// pass the object to the [KeepAlive] function after the last point
|
|
// where the object must remain reachable.
|
|
func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup {
|
|
// Explicitly force ptr to escape to the heap.
|
|
ptr = abi.Escape(ptr)
|
|
|
|
// The pointer to the object must be valid.
|
|
if ptr == nil {
|
|
throw("runtime.AddCleanup: ptr is nil")
|
|
}
|
|
usptr := uintptr(unsafe.Pointer(ptr))
|
|
|
|
// Check that arg is not equal to ptr.
|
|
// TODO(67535) this does not cover the case where T and *S are the same
|
|
// type and ptr and arg are equal.
|
|
if unsafe.Pointer(&arg) == unsafe.Pointer(ptr) {
|
|
throw("runtime.AddCleanup: ptr is equal to arg, cleanup will never run")
|
|
}
|
|
if inUserArenaChunk(usptr) {
|
|
// Arena-allocated objects are not eligible for cleanup.
|
|
throw("runtime.AddCleanup: ptr is arena-allocated")
|
|
}
|
|
if debug.sbrk != 0 {
|
|
// debug.sbrk never frees memory, so no cleanup will ever run
|
|
// (and we don't have the data structures to record them).
|
|
// Return a noop cleanup.
|
|
return Cleanup{}
|
|
}
|
|
|
|
fn := func() {
|
|
cleanup(arg)
|
|
}
|
|
// Closure must escape.
|
|
fv := *(**funcval)(unsafe.Pointer(&fn))
|
|
fv = abi.Escape(fv)
|
|
|
|
// Find the containing object.
|
|
base, _, _ := findObject(usptr, 0, 0)
|
|
if base == 0 {
|
|
if isGoPointerWithoutSpan(unsafe.Pointer(ptr)) {
|
|
// Cleanup is a noop.
|
|
return Cleanup{}
|
|
}
|
|
throw("runtime.AddCleanup: ptr not in allocated block")
|
|
}
|
|
|
|
// Ensure we have a finalizer processing goroutine running.
|
|
createfing()
|
|
|
|
id := addCleanup(unsafe.Pointer(ptr), fv)
|
|
return Cleanup{
|
|
id: id,
|
|
ptr: usptr,
|
|
}
|
|
}
|
|
|
|
// Cleanup is a handle to a cleanup call for a specific object.
|
|
type Cleanup struct {
|
|
// id is the unique identifier for the cleanup within the arena.
|
|
id uint64
|
|
// ptr contains the pointer to the object.
|
|
ptr uintptr
|
|
}
|
|
|
|
// Stop cancels the cleanup call. Stop will have no effect if the cleanup call
|
|
// has already been queued for execution (because ptr became unreachable).
|
|
// To guarantee that Stop removes the cleanup function, the caller must ensure
|
|
// that the pointer that was passed to AddCleanup is reachable across the call to Stop.
|
|
func (c Cleanup) Stop() {
|
|
if c.id == 0 {
|
|
// id is set to zero when the cleanup is a noop.
|
|
return
|
|
}
|
|
|
|
// The following block removes the Special record of type cleanup for the object c.ptr.
|
|
span := spanOfHeap(uintptr(unsafe.Pointer(c.ptr)))
|
|
if span == nil {
|
|
return
|
|
}
|
|
// Ensure that the span is swept.
|
|
// Sweeping accesses the specials list w/o locks, so we have
|
|
// to synchronize with it. And it's just much safer.
|
|
mp := acquirem()
|
|
span.ensureSwept()
|
|
|
|
offset := uintptr(unsafe.Pointer(c.ptr)) - span.base()
|
|
|
|
var found *special
|
|
lock(&span.speciallock)
|
|
|
|
iter, exists := span.specialFindSplicePoint(offset, _KindSpecialCleanup)
|
|
if exists {
|
|
for {
|
|
s := *iter
|
|
if s == nil {
|
|
// Reached the end of the linked list. Stop searching at this point.
|
|
break
|
|
}
|
|
if offset == uintptr(s.offset) && _KindSpecialCleanup == s.kind &&
|
|
(*specialCleanup)(unsafe.Pointer(s)).id == c.id {
|
|
// The special is a cleanup and contains a matching cleanup id.
|
|
*iter = s.next
|
|
found = s
|
|
break
|
|
}
|
|
if offset < uintptr(s.offset) || (offset == uintptr(s.offset) && _KindSpecialCleanup < s.kind) {
|
|
// The special is outside the region specified for that kind of
|
|
// special. The specials are sorted by kind.
|
|
break
|
|
}
|
|
// Try the next special.
|
|
iter = &s.next
|
|
}
|
|
}
|
|
if span.specials == nil {
|
|
spanHasNoSpecials(span)
|
|
}
|
|
unlock(&span.speciallock)
|
|
releasem(mp)
|
|
|
|
if found == nil {
|
|
return
|
|
}
|
|
lock(&mheap_.speciallock)
|
|
mheap_.specialCleanupAlloc.free(unsafe.Pointer(found))
|
|
unlock(&mheap_.speciallock)
|
|
}
|