Class: Familia::ThreadSafety::InstrumentedMutex

Inherits:
Object
  • Object
show all
Defined in:
lib/familia/thread_safety/instrumented_mutex.rb

Overview

A Mutex wrapper that automatically reports contention metrics

This class wraps Ruby's standard Mutex to provide automatic instrumentation of lock contention and wait times.

Examples:

Basic usage

mutex = Familia::ThreadSafety::InstrumentedMutex.new('connection_chain')
mutex.synchronize { # critical section }

With monitoring

Familia::ThreadSafety::Monitor.start!
mutex = Familia::ThreadSafety::InstrumentedMutex.new('field_registration')
mutex.synchronize { # automatically tracked }

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, monitor = nil) ⇒ InstrumentedMutex

Create a new instrumented mutex

Parameters:

  • name (String, Symbol)

    Identifier for this mutex in monitoring

  • monitor (Monitor, nil) (defaults to: nil)

    Monitor instance to use (defaults to singleton)



29
30
31
32
33
34
35
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 29

def initialize(name, monitor = nil)
  @name = name.to_s
  @mutex = ::Mutex.new
  @monitor = monitor || Monitor.instance
  @lock_count = Concurrent::AtomicFixnum.new(0)
  @contention_count = Concurrent::AtomicFixnum.new(0)
end

Instance Attribute Details

#mutexObject (readonly)

Returns the value of attribute mutex.



23
24
25
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 23

def mutex
  @mutex
end

#nameObject (readonly)

Returns the value of attribute name.



23
24
25
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 23

def name
  @name
end

Instance Method Details

#contention_rateObject

Calculate contention rate (0.0 to 1.0)



139
140
141
142
143
144
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 139

def contention_rate
  total = @lock_count.value
  return 0.0 if total == 0

  @contention_count.value.to_f / total
end

#double_checked_locking(check, init) ⇒ Object

Create a double-checked locking helper

Parameters:

  • check (Proc)

    Condition to check

  • init (Proc)

    Initialization to perform if check fails

Returns:

  • Result of check or init



151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 151

def double_checked_locking(check, init)
  # Fast path - check without lock
  value = check.call
  return value if value

  # Slow path - check again with lock
  synchronize do
    value = check.call
    return value if value

    init.call
  end
end

#lockObject

Acquire the lock (with monitoring)



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 77

def lock
  return @mutex.lock unless @monitor.enabled

  wait_start = Familia.now_in_μs

  if @mutex.try_lock
    @lock_count.increment
    return true
  end

  # Contention detected
  @contention_count.increment
  @monitor.record_contention(@name)

  result = @mutex.lock
  wait_end = Familia.now_in_μs
  wait_time_μs = wait_end - wait_start

  @lock_count.increment
  @monitor.record_wait_time(@name, wait_time_μs)

  result
end

#locked?Boolean

Check if locked by current thread

Returns:

  • (Boolean)


114
115
116
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 114

def locked?
  @mutex.locked?
end

#owned?Boolean

Check if owned by current thread

Returns:

  • (Boolean)


119
120
121
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 119

def owned?
  @mutex.owned?
end

#sleep(timeout = nil) ⇒ Object

Sleep and release the lock temporarily



124
125
126
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 124

def sleep(timeout = nil)
  @mutex.sleep(timeout)
end

#statsObject

Get statistics for this mutex



129
130
131
132
133
134
135
136
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 129

def stats
  {
    name: @name,
    lock_count: @lock_count.value,
    contention_count: @contention_count.value,
    contention_rate: contention_rate
  }
end

#synchronize { ... } ⇒ Object

Synchronize with automatic monitoring

Yields:

  • Block to execute while holding the lock

Returns:

  • Result of the block



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 41

def synchronize
  return yield unless @monitor.enabled

  acquired = false
  wait_start = Familia.now_in_μs

  # Try non-blocking acquisition first to detect contention
  if @mutex.try_lock
    acquired = true
    wait_time = 0
    @lock_count.increment
  else
    # Contention detected
    @contention_count.increment
    @monitor.record_contention(@name)

    # Now do blocking acquisition
    @mutex.lock
    acquired = true
    wait_end = Familia.now_in_μs
    wait_time_μs = wait_end - wait_start

    @lock_count.increment
    @monitor.record_wait_time(@name, wait_time_μs)

    if wait_time_μs > 10_000  # Log if waited more than 10ms (10,000μs)
      Familia.trace(:MUTEX_WAIT, nil, "Waited #{(wait_time_μs / 1000.0).round(2)}ms for #{@name}")
    end
  end

  yield
ensure
  @mutex.unlock if acquired
end

#try_lockObject

Try to acquire the lock without blocking



102
103
104
105
106
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 102

def try_lock
  result = @mutex.try_lock
  @lock_count.increment if result
  result
end

#unlockObject

Release the lock



109
110
111
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 109

def unlock
  @mutex.unlock
end