Documentation
¶
Overview ¶
Package rwmutexplus provides an instrumented version of sync.RWMutex that helps detect potential deadlocks and lock contention issues in Go applications.
Key Features:
- Monitors lock wait times and emits warnings for potential deadlocks
- Tracks lock holders with function names and file locations
- Provides detailed stack traces when lock contention occurs
- Filters runtime noise from stack traces for better readability
Basic Usage:
mutex := rwmutexplus.NewInstrumentedRWMutex(time.Second) // Use like a regular RWMutex mutex.Lock() // ... critical section ... mutex.Unlock() mutex.RLock() // ... read-only critical section ... mutex.RUnlock()
The mutex will automatically emit warnings to stdout when locks are held longer than the specified timeout, including information about the current lock holder and stack traces to help diagnose the issue.
Warning Format: When a lock wait timeout occurs, the output includes:
- Wait duration
- Caller information (file and line)
- Last lock holder details
- Filtered stack trace focusing on application code
This package is particularly useful during development and testing to identify potential deadlocks and performance issues related to lock contention.
registry.go to keep track of all the RWMutexPlus instances
Example (LockContention) ¶
package main import ( "fmt" "time" "github.com/christophcemper/rwmutexplus" ) func main() { mutex := rwmutexplus.NewInstrumentedRWMutex(100 * time.Millisecond) fmt.Println("Starting lock contention example...") // Create lock contention by holding the lock in a goroutine go func() { mutex.Lock() fmt.Println("Goroutine acquired lock, holding for 200ms...") time.Sleep(200 * time.Millisecond) mutex.Unlock() fmt.Println("Goroutine released lock") }() // Wait for goroutine to acquire lock time.Sleep(10 * time.Millisecond) // This will trigger a warning after 100ms fmt.Println("Main thread attempting to acquire lock...") mutex.Lock() fmt.Println("Main thread acquired lock") mutex.Unlock() fmt.Println("Main thread released lock") }
Output: Starting lock contention example... Goroutine acquired lock, holding for 200ms... Main thread attempting to acquire lock... === LOCK WAIT WARNING === Waiting for mutex lock for ... Last lock holder: ... ======================== Goroutine released lock Main thread acquired lock Main thread released lock
Example (ReadLockContention) ¶
package main import ( "fmt" "time" "github.com/christophcemper/rwmutexplus" ) func main() { mutex := rwmutexplus.NewInstrumentedRWMutex(100 * time.Millisecond) fmt.Println("Starting read lock contention example...") // Hold write lock go func() { mutex.Lock() fmt.Println("Write lock acquired, holding for 200ms...") time.Sleep(200 * time.Millisecond) mutex.Unlock() fmt.Println("Write lock released") }() // Wait for write lock to be acquired time.Sleep(10 * time.Millisecond) // This will trigger a warning fmt.Println("Attempting to acquire read lock...") mutex.RLock() fmt.Println("Read lock acquired") mutex.RUnlock() fmt.Println("Read lock released") }
Output: Starting read lock contention example... Write lock acquired, holding for 200ms... Attempting to acquire read lock... Write lock released Read lock acquired Read lock released
Index ¶
- func CleanupGoroutineIDs()
- func DumpAllLockInfo(filters ...LockFilter) string
- func ForceCleanup()
- func GetDurationEnvOrDefault(key string, defaultValue time.Duration) time.Duration
- func GetGoroutineID() uint64
- func GetIntEnvOrDefault(key string, defaultValue int) int
- func GetLockInfo(li LockInfo) string
- func GetStoredGoroutineCount() int
- func ParseDuration(s string) (time.Duration, error)
- func PrintLockInfo(li LockInfo)
- func PrintOnce(msg string) bool
- func PrintOncef(format string, args ...interface{}) bool
- func ReleaseGoroutineID()
- func ResetPrintOnce()
- type ActiveLock
- func (lr *ActiveLock) GetCallerInfo() string
- func (al *ActiveLock) GetGoroutineID() uint64
- func (al *ActiveLock) GetLockTX() LockTX
- func (al *ActiveLock) GetLockType() LockType
- func (lr *ActiveLock) GetPosition() string
- func (al *ActiveLock) GetPurpose() string
- func (al *ActiveLock) GetSinceTime() time.Duration
- func (al *ActiveLock) String() string
- type InstrumentedRWMutex
- type LockFilter
- type LockInfo
- type LockRequest
- func (lr *LockRequest) GetCallerInfo() string
- func (lr *LockRequest) GetGoroutineID() uint64
- func (lr *LockRequest) GetLockTX() LockTX
- func (lr *LockRequest) GetLockType() LockType
- func (lr *LockRequest) GetPosition() string
- func (lr *LockRequest) GetPurpose() string
- func (lr *LockRequest) GetSinceTime() time.Duration
- func (lr *LockRequest) String() string
- type LockStats
- type LockTX
- type LockType
- type RWMutexPlus
- func (rw *RWMutexPlus) Close()
- func (rw *RWMutexPlus) DebugPrint(str string, level ...int)
- func (rw *RWMutexPlus) DebugPrintAllLockInfo(pos string)
- func (rw *RWMutexPlus) GetActiveLocksInfo() map[string][]map[string]interface{}
- func (rw *RWMutexPlus) GetPendingLocksInfo() map[string][]map[string]interface{}
- func (rw *RWMutexPlus) GetPurposeStats() map[string]map[string]int64
- func (rw *RWMutexPlus) Lock()
- func (rw *RWMutexPlus) LockWithPurpose(purpose string)
- func (rw *RWMutexPlus) OverrideCallerInfoLinesFromEnv() bool
- func (rw *RWMutexPlus) OverrideCallerInfoSkipFromEnv() bool
- func (rw *RWMutexPlus) OverrideDebugLevelFromEnv() bool
- func (rw *RWMutexPlus) OverrideVerboseLevelFromEnv() bool
- func (rw *RWMutexPlus) OverrideWarningTimeoutFromEnv() bool
- func (m *RWMutexPlus) PrintLockInfo(pos string)
- func (rw *RWMutexPlus) PrintOncef(format string, args ...interface{})
- func (rw *RWMutexPlus) RLock()
- func (rw *RWMutexPlus) RLockWithPurpose(purpose string)
- func (rw *RWMutexPlus) RUnlock()
- func (rw *RWMutexPlus) Unlock()
- func (rw *RWMutexPlus) WithCallerInfoLines(lines int) *RWMutexPlus
- func (rw *RWMutexPlus) WithDebugLevel(level int) *RWMutexPlus
- func (rw *RWMutexPlus) WithVerboseLevel(level int) *RWMutexPlus
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CleanupGoroutineIDs ¶
func CleanupGoroutineIDs()
CleanupGoroutineIDs safely removes unused IDs while preserving active ones Uses a complete map replacement strategy to prevent memory leaks
func DumpAllLockInfo ¶
func DumpAllLockInfo(filters ...LockFilter) string
DumpAllLockInfo prints detailed information about all RWMutexPlus instances filters parameter controls which lock types to show
func ForceCleanup ¶
func ForceCleanup()
ForceCleanup removes all IDs for testing/reset purposes Creates entirely new maps to ensure complete cleanup
func GetDurationEnvOrDefault ¶ added in v0.0.5
GetDurationEnvOrDefault returns the duration value of an environment variable or the default value if not set
func GetGoroutineID ¶
func GetGoroutineID() uint64
GetGoroutineID returns a unique identifier for the current goroutine Uses a two-phase locking strategy to optimize for the common case where the ID already exists
func GetIntEnvOrDefault ¶ added in v0.0.5
GetIntEnvOrDefault returns the integer value of an environment variable or the default value if not set
func GetLockInfo ¶ added in v0.0.5
func GetStoredGoroutineCount ¶
func GetStoredGoroutineCount() int
GetStoredGoroutineCount returns number of stored IDs for monitoring
func PrintLockInfo ¶ added in v0.0.5
func PrintLockInfo(li LockInfo)
PrintLockInfo prints the lock info from LockInfo interface
func PrintOnce ¶ added in v0.0.5
PrintOnce prints a message only once during the process lifetime Returns true if the message was printed, false if it was already printed before
func PrintOncef ¶ added in v0.0.5
PrintOncef formats and prints a message only once during the process lifetime Returns true if the message was printed, false if it was already printed before
func ReleaseGoroutineID ¶
func ReleaseGoroutineID()
ReleaseGoroutineID marks an ID as no longer in active use This allows the cleanup routine to eventually remove it
func ResetPrintOnce ¶ added in v0.0.5
func ResetPrintOnce()
ResetPrintOnce clears all tracked messages (mainly for testing)
Types ¶
type ActiveLock ¶
type ActiveLock struct {
// contains filtered or unexported fields
}
ActiveLock tracks an acquired lock
func (*ActiveLock) GetCallerInfo ¶ added in v0.0.5
func (lr *ActiveLock) GetCallerInfo() string
implements LockInfo
func (*ActiveLock) GetGoroutineID ¶ added in v0.0.5
func (al *ActiveLock) GetGoroutineID() uint64
implements LockInfo
func (*ActiveLock) GetLockTX ¶ added in v0.0.5
func (al *ActiveLock) GetLockTX() LockTX
implements LockInfo
func (*ActiveLock) GetLockType ¶ added in v0.0.5
func (al *ActiveLock) GetLockType() LockType
implements LockInfo
func (*ActiveLock) GetPosition ¶ added in v0.0.5
func (lr *ActiveLock) GetPosition() string
implements LockInfo
func (*ActiveLock) GetPurpose ¶ added in v0.0.5
func (al *ActiveLock) GetPurpose() string
implements LockInfo
func (*ActiveLock) GetSinceTime ¶ added in v0.0.5
func (al *ActiveLock) GetSinceTime() time.Duration
implements LockInfo
func (*ActiveLock) String ¶ added in v0.0.5
func (al *ActiveLock) String() string
implements LockInfo
type InstrumentedRWMutex ¶
InstrumentedRWMutex wraps sync.RWMutex with debugging capabilities. It tracks lock holders, wait times, and can emit warnings when locks are held for too long.
Example ¶
Example of concurrent usage
mutex := NewInstrumentedRWMutex(100 * time.Millisecond) data := make(map[string]int) // Writer go func() { mutex.Lock() data["key"] = 42 time.Sleep(200 * time.Millisecond) // Simulate work mutex.Unlock() }() // Reader go func() { mutex.RLock() _ = data["key"] mutex.RUnlock() }() // Let the example run time.Sleep(3000 * time.Millisecond)
func NewInstrumentedRWMutex ¶
func NewInstrumentedRWMutex(lockWaitTimeout time.Duration) *InstrumentedRWMutex
NewInstrumentedRWMutex creates a new InstrumentedRWMutex with the specified lock wait timeout. The timeout determines how long to wait before emitting a warning about potential deadlocks.
Example:
mutex := NewInstrumentedRWMutex(time.Second) // Warn after 1 second of waiting
func (*InstrumentedRWMutex) Lock ¶
func (m *InstrumentedRWMutex) Lock()
Lock acquires an exclusive lock and monitors the wait time.
func (*InstrumentedRWMutex) LockPurpose ¶
func (m *InstrumentedRWMutex) LockPurpose(purpose string)
LockPurpose acquires an exclusive lock and sets the purpose of the mutex and monitors the wait time.
func (*InstrumentedRWMutex) RLock ¶
func (m *InstrumentedRWMutex) RLock()
RLock acquires a shared read lock and monitors the wait time. Similar to Lock, it will emit warnings if the wait time exceeds lockWaitTimeout.
func (*InstrumentedRWMutex) Unlock ¶
func (m *InstrumentedRWMutex) Unlock()
Unlock releases an exclusive lock. if the lock was held for too long, it prints a warning
type LockFilter ¶
type LockFilter uint8
const ( ShowPendingReads LockFilter = 1 << iota ShowPendingWrites ShowActiveReads ShowActiveWrites )
type LockInfo ¶ added in v0.0.5
type LockInfo interface { GetLockTX() LockTX GetLockType() LockType GetPurpose() string GetGoroutineID() uint64 GetCallerInfo() string GetSinceTime() time.Duration GetPosition() string String() string }
LockInfo is an interface for both LockRequest and ActiveLock to store lockType, purpose, goRoutineID and callerInfo
type LockRequest ¶
type LockRequest struct {
// contains filtered or unexported fields
}
LockRequest implements LockInfo
func (*LockRequest) GetCallerInfo ¶ added in v0.0.5
func (lr *LockRequest) GetCallerInfo() string
implements LockInfo
func (*LockRequest) GetGoroutineID ¶ added in v0.0.5
func (lr *LockRequest) GetGoroutineID() uint64
implements LockInfo
func (*LockRequest) GetLockTX ¶ added in v0.0.5
func (lr *LockRequest) GetLockTX() LockTX
implements LockInfo
func (*LockRequest) GetLockType ¶ added in v0.0.5
func (lr *LockRequest) GetLockType() LockType
implements LockInfo
func (*LockRequest) GetPosition ¶ added in v0.0.5
func (lr *LockRequest) GetPosition() string
implements LockInfo
func (*LockRequest) GetPurpose ¶ added in v0.0.5
func (lr *LockRequest) GetPurpose() string
implements LockInfo
func (*LockRequest) GetSinceTime ¶ added in v0.0.5
func (lr *LockRequest) GetSinceTime() time.Duration
implements LockInfo
func (*LockRequest) String ¶ added in v0.0.5
func (lr *LockRequest) String() string
implements LockInfo
type LockStats ¶
type LockStats struct {
// contains filtered or unexported fields
}
LockStats tracks statistics for a specific lock purpose
type LockTX ¶ added in v0.0.5
type LockTX int
LockTX is the transaction type for the lock object (LockRequest or ActiveLock)
type RWMutexPlus ¶
RWMutexPlus provides a robust logging RWMutex implementation
func NewRWMutexPlus ¶
func (*RWMutexPlus) DebugPrint ¶ added in v0.0.5
func (rw *RWMutexPlus) DebugPrint(str string, level ...int)
DebugPrint prints the string if debug level is greater than 0 if variadic/optional param level is provided, it will print only if level is greater than or equal to debugLevel
func (*RWMutexPlus) DebugPrintAllLockInfo ¶ added in v0.0.5
func (rw *RWMutexPlus) DebugPrintAllLockInfo(pos string)
func (*RWMutexPlus) GetActiveLocksInfo ¶
func (rw *RWMutexPlus) GetActiveLocksInfo() map[string][]map[string]interface{}
GetActiveLocksInfo returns information about all currently held locks
func (*RWMutexPlus) GetPendingLocksInfo ¶
func (rw *RWMutexPlus) GetPendingLocksInfo() map[string][]map[string]interface{}
GetPendingLocksInfo returns information about locks waiting to be acquired
func (*RWMutexPlus) GetPurposeStats ¶
func (rw *RWMutexPlus) GetPurposeStats() map[string]map[string]int64
GetPurposeStats returns statistics for all lock purposes
func (*RWMutexPlus) Lock ¶
func (rw *RWMutexPlus) Lock()
Add these methods to support backward compatibility
func (*RWMutexPlus) LockWithPurpose ¶
func (rw *RWMutexPlus) LockWithPurpose(purpose string)
func (*RWMutexPlus) OverrideCallerInfoLinesFromEnv ¶ added in v0.0.5
func (rw *RWMutexPlus) OverrideCallerInfoLinesFromEnv() bool
func (*RWMutexPlus) OverrideCallerInfoSkipFromEnv ¶ added in v0.0.5
func (rw *RWMutexPlus) OverrideCallerInfoSkipFromEnv() bool
func (*RWMutexPlus) OverrideDebugLevelFromEnv ¶ added in v0.0.5
func (rw *RWMutexPlus) OverrideDebugLevelFromEnv() bool
func (*RWMutexPlus) OverrideVerboseLevelFromEnv ¶ added in v0.0.5
func (rw *RWMutexPlus) OverrideVerboseLevelFromEnv() bool
check os.Getenv variables RWMUTEXTPLUS_VERBOSELEVEL and RWMUTEXTPLUS_DEBUGLEVEL to set the verboseLevel and debugLevel and override any defaults from the constructor code or With...Level setters
func (*RWMutexPlus) OverrideWarningTimeoutFromEnv ¶ added in v0.0.5
func (rw *RWMutexPlus) OverrideWarningTimeoutFromEnv() bool
func (*RWMutexPlus) PrintLockInfo ¶
func (m *RWMutexPlus) PrintLockInfo(pos string)
PrintLockInfo prints detailed information about the current state of the mutex including active locks, pending locks, and statistics for each purpose. pos is a string identifier to help track where the print was called from.
func (*RWMutexPlus) PrintOncef ¶ added in v0.0.5
func (rw *RWMutexPlus) PrintOncef(format string, args ...interface{})
PrintOncef prints a message if it hasn't been printed before
func (*RWMutexPlus) RLock ¶
func (rw *RWMutexPlus) RLock()
func (*RWMutexPlus) RLockWithPurpose ¶
func (rw *RWMutexPlus) RLockWithPurpose(purpose string)
func (*RWMutexPlus) RUnlock ¶
func (rw *RWMutexPlus) RUnlock()
func (*RWMutexPlus) Unlock ¶
func (rw *RWMutexPlus) Unlock()
Fix the Unlock method to properly handle the activeLock check
func (*RWMutexPlus) WithCallerInfoLines ¶ added in v0.0.5
func (rw *RWMutexPlus) WithCallerInfoLines(lines int) *RWMutexPlus
func (*RWMutexPlus) WithDebugLevel ¶
func (rw *RWMutexPlus) WithDebugLevel(level int) *RWMutexPlus
WithDebugLevel sets the debug level (0-1) for the mutex and returns the mutex for chaining
func (*RWMutexPlus) WithVerboseLevel ¶
func (rw *RWMutexPlus) WithVerboseLevel(level int) *RWMutexPlus
WithVerboseLevel sets the verbose level (0-3) for the mutex and returns the mutex for chaining