Skip to content

Commit f7a3afa

Browse files
authored
Merge pull request rails#42572 from Shopify/async-queries-lock-wait
Report async queries lock wait duration
2 parents c5e46ef + 99913f6 commit f7a3afa

File tree

6 files changed

+52
-43
lines changed

6 files changed

+52
-43
lines changed

activerecord/lib/active_record/future_result.rb

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,34 @@
22

33
module ActiveRecord
44
class FutureResult # :nodoc:
5+
class EventBuffer
6+
def initialize(future_result, instrumenter)
7+
@future_result = future_result
8+
@instrumenter = instrumenter
9+
@events = []
10+
end
11+
12+
def instrument(name, payload = {}, &block)
13+
event = @instrumenter.new_event(name, payload)
14+
@events << event
15+
event.record(&block)
16+
end
17+
18+
def flush
19+
events, @events = @events, []
20+
events.each do |event|
21+
event.payload[:lock_wait] = @future_result.lock_wait
22+
ActiveSupport::Notifications.publish_event(event)
23+
end
24+
end
25+
end
26+
527
Canceled = Class.new(ActiveRecordError)
628

729
delegate :empty?, :to_a, to: :result
830

31+
attr_reader :lock_wait
32+
933
def initialize(pool, *args, **kwargs)
1034
@mutex = Mutex.new
1135

@@ -43,7 +67,7 @@ def execute_or_skip
4367
return unless @mutex.try_lock
4468
begin
4569
if pending?
46-
@event_buffer = @instrumenter.buffer
70+
@event_buffer = EventBuffer.new(self, @instrumenter)
4771
connection.with_instrumenter(@event_buffer) do
4872
execute_query(connection, async: true)
4973
end
@@ -77,12 +101,17 @@ def canceled?
77101
end
78102

79103
def execute_or_wait
80-
return unless pending?
81-
82-
@mutex.synchronize do
83-
if pending?
84-
execute_query(@pool.connection)
104+
if pending?
105+
start = Concurrent.monotonic_time
106+
@mutex.synchronize do
107+
if pending?
108+
execute_query(@pool.connection)
109+
else
110+
@lock_wait = (Concurrent.monotonic_time - start) * 1_000
111+
end
85112
end
113+
else
114+
@lock_wait = 0.0
86115
end
87116
end
88117

activerecord/lib/active_record/log_subscriber.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ def sql(event)
3737

3838
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
3939

40-
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
40+
name = if payload[:async]
41+
"ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)"
42+
else
43+
"#{payload[:name]} (#{event.duration.round(1)}ms)"
44+
end
4145
name = "CACHE #{name}" if payload[:cached]
42-
name = "ASYNC #{name}" if payload[:async]
4346
sql = payload[:sql]
4447
binds = nil
4548

activerecord/test/cases/log_subscriber_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ def test_basic_payload_name_logging_coloration_named_sql
133133
end
134134
end
135135

136+
def test_async_query
137+
logger = TestDebugLogSubscriber.new
138+
logger.sql(Event.new(0.9, sql: "SELECT * from models", name: "Model Load", async: true, lock_wait: 0.01))
139+
assert_match(/ASYNC Model Load \(0\.0ms\) \(db time 0\.9ms\) SELECT/i, logger.debugs.last)
140+
end
141+
136142
def test_query_logging_coloration_with_nested_select
137143
logger = TestDebugLogSubscriber.new
138144
logger.colorize_logging = true

activerecord/test/cases/relation/load_async_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def test_notification_forwarding
6262
status[:executed] = true
6363
status[:async] = event.payload[:async]
6464
status[:thread_id] = Thread.current.object_id
65+
status[:lock_wait] = event.payload[:lock_wait]
6566
end
6667
end
6768

@@ -70,6 +71,11 @@ def test_notification_forwarding
7071
assert_equal expected_records, deferred_posts.to_a
7172
assert_equal Post.connection.supports_concurrent_connections?, status[:async]
7273
assert_equal Thread.current.object_id, status[:thread_id]
74+
if Post.connection.supports_concurrent_connections?
75+
assert_instance_of Float, status[:lock_wait]
76+
else
77+
assert_nil status[:lock_wait]
78+
end
7379
ensure
7480
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
7581
end

activesupport/lib/active_support/notifications/instrumenter.rb

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,6 @@ module ActiveSupport
66
module Notifications
77
# Instrumenters are stored in a thread local.
88
class Instrumenter
9-
class Buffer # :nodoc:
10-
def initialize(instrumenter)
11-
@instrumenter = instrumenter
12-
@events = []
13-
end
14-
15-
def instrument(name, payload = {}, &block)
16-
event = @instrumenter.new_event(name, payload)
17-
@events << event
18-
event.record(&block)
19-
end
20-
21-
def flush
22-
events, @events = @events, []
23-
events.each do |event|
24-
ActiveSupport::Notifications.publish_event(event)
25-
end
26-
end
27-
end
28-
299
attr_reader :id
3010

3111
def initialize(notifier)
@@ -55,10 +35,6 @@ def new_event(name, payload = {}) # :nodoc:
5535
Event.new(name, nil, nil, @id, payload)
5636
end
5737

58-
def buffer # :nodoc:
59-
Buffer.new(self)
60-
end
61-
6238
# Send a start notification with +name+ and +payload+.
6339
def start(name, payload)
6440
@notifier.start name, @id, payload

activesupport/test/log_subscriber_test.rb

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,6 @@ def test_does_not_send_the_event_if_logger_is_nil
107107
end
108108
end
109109

110-
def test_does_not_send_buffered_events_if_logger_is_nil
111-
ActiveSupport::LogSubscriber.logger = nil
112-
assert_not_called(@log_subscriber, :some_event) do
113-
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
114-
buffer = ActiveSupport::Notifications.instrumenter.buffer
115-
buffer.instrument "some_event.my_log_subscriber"
116-
buffer.flush
117-
wait
118-
end
119-
end
120-
121110
def test_does_not_fail_with_non_namespaced_events
122111
ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
123112
instrument "whatever"

0 commit comments

Comments
 (0)