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)



142
143
144
145
146
147
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 142

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



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 154

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)



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

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)


117
118
119
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 117

def locked?
  @mutex.locked?
end

#owned?Boolean

Check if owned by current thread

Returns:

  • (Boolean)


122
123
124
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 122

def owned?
  @mutex.owned?
end

#sleep(timeout = nil) ⇒ Object

Sleep and release the lock temporarily



127
128
129
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 127

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

#statsObject

Get statistics for this mutex



132
133
134
135
136
137
138
139
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 132

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
75
76
77
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 41

def synchronize(&)
  # Monitoring is off by default; the lock must still be taken. An
  # earlier version returned `yield` here, which made every
  # InstrumentedMutex a no-op outside a monitoring session.
  return @mutex.synchronize(&) 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



105
106
107
108
109
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 105

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

#unlockObject

Release the lock



112
113
114
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 112

def unlock
  @mutex.unlock
end