99 lines
2.2 KiB
Go
99 lines
2.2 KiB
Go
package crashlog
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ── Mutex with debug tracking ──
|
|
|
|
// DebugMutex wraps a sync.Mutex with lock-holder tracking.
|
|
type DebugMutex struct {
|
|
mu sync.Mutex
|
|
holder string
|
|
lockedAt time.Time
|
|
}
|
|
|
|
// Lock acquires the mutex with caller tracking.
|
|
func (m *DebugMutex) Lock() {
|
|
m.mu.Lock()
|
|
_, file, line, _ := runtime.Caller(1)
|
|
if idx := strings.LastIndex(file, "Cyrene/"); idx >= 0 {
|
|
file = file[idx+len("Cyrene/"):]
|
|
}
|
|
m.holder = fmt.Sprintf("%s:%d", file, line)
|
|
m.lockedAt = time.Now()
|
|
}
|
|
|
|
// Unlock releases the mutex.
|
|
func (m *DebugMutex) Unlock() {
|
|
m.holder = ""
|
|
m.mu.Unlock()
|
|
}
|
|
|
|
// TryLock attempts to acquire the lock with a timeout. Returns true if acquired.
|
|
func (m *DebugMutex) TryLock(timeout time.Duration) bool {
|
|
done := make(chan struct{})
|
|
go func() {
|
|
m.mu.Lock()
|
|
m.mu.Unlock()
|
|
close(done)
|
|
}()
|
|
select {
|
|
case <-done:
|
|
return true
|
|
case <-time.After(timeout):
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Holder returns who holds the lock.
|
|
func (m *DebugMutex) Holder() (string, time.Time) {
|
|
return m.holder, m.lockedAt
|
|
}
|
|
|
|
// ── Channel debug helpers ──
|
|
|
|
// ChSend sends to a channel with a timeout and logs if it blocks.
|
|
func ChSend[T any](name string, ch chan<- T, val T, timeout time.Duration) bool {
|
|
select {
|
|
case ch <- val:
|
|
return true
|
|
case <-time.After(timeout):
|
|
log.Printf("[debug] ChSend timeout: %s after %v", name, timeout)
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ── Goroutine lifecycle ──
|
|
|
|
// GoStart logs goroutine start and wraps fn with panic recovery.
|
|
func GoStart(name string, fn func()) {
|
|
log.Printf("[debug] goroutine START: %s", name)
|
|
go func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
stack := make([]byte, 4096)
|
|
n := runtime.Stack(stack, false)
|
|
log.Printf("[debug] goroutine PANIC: %s panic=%v\n%s", name, r, stack[:n])
|
|
}
|
|
log.Printf("[debug] goroutine EXIT: %s", name)
|
|
}()
|
|
fn()
|
|
}()
|
|
}
|
|
|
|
// ── Timing ──
|
|
|
|
// Since logs if a duration exceeds a threshold.
|
|
func Since(name string, start time.Time, threshold time.Duration) {
|
|
elapsed := time.Since(start)
|
|
if elapsed > threshold {
|
|
log.Printf("[debug] SLOW: %s took %v (threshold=%v)", name, elapsed.Round(time.Millisecond), threshold)
|
|
}
|
|
}
|