mirror of
https://github.com/hanzoai/docdb.git
synced 2026-10-30 18:39:39 +00:00
141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
// Copyright 2021 Hanzo AI Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package resource provides utilities for tracking resource lifetimes.
|
|
package resource
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
runtimedebug "runtime/debug"
|
|
"runtime/pprof"
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
|
|
"github.com/hanzoai/docdb/internal/util/devbuild"
|
|
)
|
|
|
|
// Token is a field of a tracked object, holding the cleanup handle and a cleanup function with message.
|
|
type Token struct {
|
|
h atomic.Pointer[runtime.Cleanup]
|
|
cleanup func(*Token)
|
|
msg string
|
|
}
|
|
|
|
// cleanup panics with the token's message.
|
|
func cleanup(t *Token) {
|
|
panic(t.msg)
|
|
}
|
|
|
|
// NewToken returns a new Token.
|
|
func NewToken() *Token {
|
|
return &Token{
|
|
cleanup: cleanup,
|
|
}
|
|
}
|
|
|
|
// profilesM protects access to profiles.
|
|
var profilesM sync.Mutex
|
|
|
|
// profileName return pprof profile name for the given object.
|
|
func profileName(obj any) string {
|
|
return "DocDB/" + reflect.TypeOf(obj).Elem().String()
|
|
}
|
|
|
|
// Track tracks the lifetime of an object until Untrack is called on it.
|
|
//
|
|
// Obj should be a pointer to a struct with a field "token" of type *Token.
|
|
func Track[T any](obj *T, token *Token) {
|
|
checkArgs(obj, token)
|
|
|
|
name := profileName(obj)
|
|
|
|
// fast path
|
|
|
|
p := pprof.Lookup(name)
|
|
|
|
if p == nil {
|
|
// slow path
|
|
|
|
profilesM.Lock()
|
|
|
|
// a concurrent call might have created a profile already; check again
|
|
if p = pprof.Lookup(name); p == nil {
|
|
p = pprof.NewProfile(name)
|
|
}
|
|
|
|
profilesM.Unlock()
|
|
}
|
|
|
|
// use token instead of obj itself,
|
|
// because otherwise profile will hold a reference to obj and cleanup will never run
|
|
p.Add(token, 1)
|
|
|
|
token.msg = fmt.Sprintf("%T has not been finalized", obj)
|
|
if devbuild.Enabled {
|
|
token.msg += "\nObject created by " + string(runtimedebug.Stack())
|
|
}
|
|
|
|
h := runtime.AddCleanup(obj, token.cleanup, token)
|
|
token.h.Store(&h)
|
|
}
|
|
|
|
// Untrack stops tracking the lifetime of an object.
|
|
//
|
|
// It is safe to call this function multiple times concurrently.
|
|
func Untrack[T any](obj *T, token *Token) {
|
|
checkArgs(obj, token)
|
|
|
|
if h := token.h.Swap(nil); h != nil {
|
|
h.Stop()
|
|
}
|
|
|
|
p := pprof.Lookup(profileName(obj))
|
|
if p == nil {
|
|
panic("object is not tracked")
|
|
}
|
|
|
|
p.Remove(token)
|
|
}
|
|
|
|
// checkArgs checks Track and Untrack arguments.
|
|
//
|
|
// Other creative misuses of Track should result in panics too, if less clear.
|
|
func checkArgs(obj any, token *Token) {
|
|
if obj == nil {
|
|
panic("obj must not be nil")
|
|
}
|
|
|
|
if token == nil {
|
|
panic("token must not be nil")
|
|
}
|
|
|
|
pv := reflect.ValueOf(obj)
|
|
if pv.Kind() != reflect.Ptr {
|
|
panic(fmt.Sprintf("obj must be a pointer to struct, got %T", obj))
|
|
}
|
|
|
|
v := pv.Elem()
|
|
if v.Kind() != reflect.Struct {
|
|
panic(fmt.Sprintf("obj must be a pointer to struct, got %T", obj))
|
|
}
|
|
|
|
f := v.FieldByName("token")
|
|
if f.Kind() != reflect.Ptr || f.UnsafePointer() != unsafe.Pointer(token) {
|
|
panic("token must be a pointer field of a struct")
|
|
}
|
|
}
|