Class: Familia::ThreadSafety::InstrumentedMutex
- Inherits:
-
Object
- Object
- Familia::ThreadSafety::InstrumentedMutex
- 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.
Instance Attribute Summary collapse
-
#mutex ⇒ Object
readonly
Returns the value of attribute mutex.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Instance Method Summary collapse
-
#contention_rate ⇒ Object
Calculate contention rate (0.0 to 1.0).
-
#double_checked_locking(check, init) ⇒ Object
Create a double-checked locking helper.
-
#initialize(name, monitor = nil) ⇒ InstrumentedMutex
constructor
Create a new instrumented mutex.
-
#lock ⇒ Object
Acquire the lock (with monitoring).
-
#locked? ⇒ Boolean
Check if locked by current thread.
-
#owned? ⇒ Boolean
Check if owned by current thread.
-
#sleep(timeout = nil) ⇒ Object
Sleep and release the lock temporarily.
-
#stats ⇒ Object
Get statistics for this mutex.
-
#synchronize { ... } ⇒ Object
Synchronize with automatic monitoring.
-
#try_lock ⇒ Object
Try to acquire the lock without blocking.
-
#unlock ⇒ Object
Release the lock.
Constructor Details
#initialize(name, monitor = nil) ⇒ InstrumentedMutex
Create a new instrumented mutex
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
#mutex ⇒ Object (readonly)
Returns the value of attribute mutex.
23 24 25 |
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 23 def mutex @mutex end |
#name ⇒ Object (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_rate ⇒ Object
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
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 |
#lock ⇒ Object
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
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
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 |
#stats ⇒ Object
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
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_lock ⇒ Object
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 |
#unlock ⇒ Object
Release the lock
112 113 114 |
# File 'lib/familia/thread_safety/instrumented_mutex.rb', line 112 def unlock @mutex.unlock end |