Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions pkg/sync/rwmutex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package sync

import (
"sync"
"time"

"go.uber.org/zap"
)

type (
// RWMutex is a wrapper around sync.RWMutex that logs lock and unlock events.
RWMutex struct {
rw sync.RWMutex
loggerFn func() *zap.Logger
uuid int64
}
// RWMutexOption is a function that configures an RWMutex.
RWMutexOption func(*RWMutex)
)

// WithLogger sets the logger used by the RWMutex.
func WithLogger(loggerFn func() *zap.Logger) RWMutexOption {
return func(lm *RWMutex) {
lm.loggerFn = loggerFn
}
}

// NewRWMutex creates a new RWMutex with the provided options.
func NewRWMutex(opts ...RWMutexOption) *RWMutex {
rw := &RWMutex{
uuid: time.Now().Unix(),
Copy link

Copilot AI Mar 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using time.Now().Unix() for uuid may lead to collisions if multiple mutex instances are created within the same second. Consider using time.Now().UnixNano() for higher resolution.

Suggested change
uuid: time.Now().Unix(),
uuid: time.Now().UnixNano(),

Copilot uses AI. Check for mistakes.
loggerFn: func() *zap.Logger { return zap.NewNop() },
}
for _, opt := range opts {
opt(rw)
}
return rw
}

// Lock locks the mutex.
func (lm *RWMutex) Lock() {
lm.logger().Debug("request lock")
lm.rw.Lock()
lm.logger().Debug("acquired lock")
}

// Unlock unlocks the mutex.
func (lm *RWMutex) Unlock() {
lm.logger().Debug("request unlock")
lm.rw.Unlock()
lm.logger().Debug("released lock")
}

// RLock locks the mutex for reading.
func (lm *RWMutex) RLock() {
lm.logger().Debug("request rlock")
lm.rw.RLock()
lm.logger().Debug("acquired rlock")
}

// RUnlock unlocks the mutex for reading.
func (lm *RWMutex) RUnlock() {
lm.logger().Debug("request runlock")
lm.rw.RUnlock()
lm.logger().Debug("released rlock")
}

func (lm *RWMutex) logger() *zap.Logger {
return lm.loggerFn().WithOptions(zap.AddCaller(), zap.AddCallerSkip(1)).With(zap.Int64("uuid", lm.uuid))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we keep the instance of lm.loggerFn()?

}
26 changes: 26 additions & 0 deletions pkg/sync/rwmutex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package sync

import (
"testing"

"go.uber.org/zap"
)

func TestRWMutex(t *testing.T) {
t.Run("default", func(t *testing.T) {
rs := NewRWMutex()
rs.Lock()
rs.Unlock()
rs.RLock()
rs.RUnlock()
})
t.Run("with logger", func(t *testing.T) {
rs := NewRWMutex(WithLogger(func() *zap.Logger {
return zap.NewExample()
}))
rs.Lock()
rs.Unlock()
rs.RLock()
rs.RUnlock()
})
}