forked from Leak_Technologies/VideoTools
Vendor gotk3 library to ensure consistent GTK3 bindings across environments and simplify dependency management. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
136 lines
3.2 KiB
Go
136 lines
3.2 KiB
Go
package closure
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// FrameSize is the number of frames that FuncStack should trace back from.
|
|
const FrameSize = 3
|
|
|
|
// FuncStack wraps a function value and provides function frames containing the
|
|
// caller trace for debugging.
|
|
type FuncStack struct {
|
|
Func reflect.Value
|
|
Frames []uintptr
|
|
}
|
|
|
|
var zeroFuncStack = FuncStack{}
|
|
|
|
// NewFuncStack creates a new FuncStack. It panics if fn is not a function. The
|
|
// given frameSkip is added 2, meaning the first frame from 0 will start from
|
|
// the caller of NewFuncStack.
|
|
func NewFuncStack(fn interface{}, frameSkip int) FuncStack {
|
|
// Create a reflect.Value from f. This is called when the returned
|
|
// GClosure runs.
|
|
rf := reflect.ValueOf(fn)
|
|
|
|
// Closures can only be created from funcs.
|
|
if rf.Type().Kind() != reflect.Func {
|
|
panic("closure value is not a func")
|
|
}
|
|
|
|
frames := make([]uintptr, FrameSize)
|
|
frames = frames[:runtime.Callers(frameSkip+2, frames)]
|
|
|
|
return FuncStack{
|
|
Func: rf,
|
|
Frames: frames,
|
|
}
|
|
}
|
|
|
|
var (
|
|
idleTypeCache sync.Map
|
|
idleTypeSentinel = struct{}{}
|
|
)
|
|
|
|
// NewIdleFuncStack works akin to NewFuncStack, but it also validates the given
|
|
// function type for the correct acceptable signatures for SourceFunc while also
|
|
// caching the checks.
|
|
func NewIdleFuncStack(fn interface{}, frameSkip int) FuncStack {
|
|
fs := NewFuncStack(fn, frameSkip+1)
|
|
funcType := fs.Func.Type()
|
|
|
|
// LoadOrStore will actually ensure that only 1 check is done at a time, but
|
|
// future checks on failed functions may trigger a late panic.
|
|
_, checked := idleTypeCache.LoadOrStore(funcType, idleTypeSentinel)
|
|
if checked {
|
|
return fs
|
|
}
|
|
|
|
// Ensure no parameters prematurely.
|
|
if funcType.NumIn() > 0 {
|
|
fs.Panicf("timeout source should have no parameters")
|
|
}
|
|
|
|
// Ensure proper return types.
|
|
switch out := funcType.NumOut(); out {
|
|
case 0:
|
|
break
|
|
case 1:
|
|
out0 := funcType.Out(0)
|
|
if out0.Kind() != reflect.Bool {
|
|
fs.Panicf("expected bool in return type, got %v", out0.Kind())
|
|
}
|
|
default:
|
|
fs.Panicf("unexpected return count (expecting 0 or 1): %d", out)
|
|
}
|
|
|
|
return fs
|
|
}
|
|
|
|
// IsValid returns true if the given FuncStack is not a zero-value i.e. valid.
|
|
func (fs FuncStack) IsValid() bool {
|
|
return fs.Frames != nil
|
|
}
|
|
|
|
const headerSignature = "closure error: "
|
|
|
|
// Panicf panics with the given FuncStack printed to standard error.
|
|
func (fs FuncStack) Panicf(msgf string, v ...interface{}) {
|
|
msg := strings.Builder{}
|
|
msg.WriteString(headerSignature)
|
|
fmt.Fprintf(&msg, msgf, v...)
|
|
|
|
msg.WriteString("\n\nClosure added at:")
|
|
|
|
frames := runtime.CallersFrames(fs.Frames)
|
|
for {
|
|
frame, more := frames.Next()
|
|
msg.WriteString("\n\t")
|
|
msg.WriteString(frame.Function)
|
|
msg.WriteString(" at ")
|
|
msg.WriteString(frame.File)
|
|
msg.WriteByte(':')
|
|
msg.WriteString(strconv.Itoa(frame.Line))
|
|
|
|
if !more {
|
|
break
|
|
}
|
|
}
|
|
|
|
panic(msg.String())
|
|
}
|
|
|
|
// TryRepanic attempts to recover a panic. If successful, it will re-panic with
|
|
// the trace, or none if there is already one.
|
|
func (fs FuncStack) TryRepanic() {
|
|
panicking := recover()
|
|
if panicking == nil {
|
|
return
|
|
}
|
|
|
|
if msg, ok := panicking.(string); ok {
|
|
if strings.HasPrefix(msg, headerSignature) {
|
|
// We can just repanic as-is.
|
|
panic(msg)
|
|
}
|
|
}
|
|
|
|
fs.Panicf("unexpected panic caught: %v", panicking)
|
|
}
|