Define the new start_transaction.active_record event

This commit is contained in:
Xavier Noria 2024-06-10 17:30:37 +02:00
parent 0733ab5118
commit f64a4134df
3 changed files with 110 additions and 6 deletions

@ -91,7 +91,9 @@ def start
raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
@started = true
@payload = @base_payload.dup
ActiveSupport::Notifications.instrument("start_transaction.active_record", @base_payload)
@payload = @base_payload.dup # We dup because the payload for a given event is mutated later to add the outcome.
@handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
@handle.start
end

@ -7,6 +7,62 @@ class TransactionInstrumentationTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :topics
def test_start_transaction_is_triggered_when_the_transaction_is_materialized
transactions = []
subscriber = ActiveSupport::Notifications.subscribe("start_transaction.active_record") do |event|
assert event.payload[:connection]
transactions << event.payload[:transaction]
end
Topic.transaction do |transaction|
assert_empty transactions # A transaction call, per se, does not trigger the event.
topics(:first).touch
assert_equal [transaction], transactions
end
ensure
ActiveSupport::Notifications.unsubscribe(subscriber)
end
def test_start_transaction_is_not_triggered_for_ordinary_nested_calls
transactions = []
subscriber = ActiveSupport::Notifications.subscribe("start_transaction.active_record") do |event|
assert event.payload[:connection]
transactions << event.payload[:transaction]
end
Topic.transaction do |t1|
topics(:first).touch
assert_equal [t1], transactions
Topic.transaction do |_t2|
topics(:first).touch
assert_equal [t1], transactions
end
end
ensure
ActiveSupport::Notifications.unsubscribe(subscriber)
end
def test_start_transaction_is_triggered_for_requires_new
transactions = []
subscriber = ActiveSupport::Notifications.subscribe("start_transaction.active_record") do |event|
assert event.payload[:connection]
transactions << event.payload[:transaction]
end
Topic.transaction do |t1|
topics(:first).touch
assert_equal [t1], transactions
Topic.transaction(requires_new: true) do |t2|
topics(:first).touch
assert_equal [t1, t2], transactions
end
end
ensure
ActiveSupport::Notifications.unsubscribe(subscriber)
end
def test_transaction_instrumentation_on_commit
topic = topics(:fifth)

@ -410,9 +410,56 @@ This event is only emitted when [`config.active_record.action_on_strict_loading_
}
```
#### `start_transaction.active_record`
This event is emitted when a transaction has been started.
| Key | Value |
| -------------------- | ---------------------------------------------------- |
| `:transaction` | Transaction object |
| `:connection` | Connection object |
Please, note that Active Record does not create the actual database transaction
until needed:
```ruby
ActiveRecord::Base.transaction do
# We are inside the block, but no event has been triggered yet.
# The following line makes Active Record start the transaction.
User.count # Event fired here.
end
```
Remember that ordinary nested calls do not create new transactions:
```ruby
ActiveRecord::Base.transaction do |t1|
User.count # Fires an event for t1.
ActiveRecord::Base.transaction do |t2|
# The next line fires no event for t2, because the only
# real database transaction in this example is t1.
User.first.touch
end
end
```
However, if `requires_new: true` is passed, you get an event for the nested
transaction too. This might be a savepoint under the hood:
```ruby
ActiveRecord::Base.transaction do |t1|
User.count # Fires an event for t1.
ActiveRecord::Base.transaction(requires_new: true) do |t2|
User.first.touch # Fires an event for t2.
end
end
```
#### `transaction.active_record`
This event is emmited for every transaction to the database.
This event is emitted when a database transaction finishes. The state of the
transaction can be found in the `:outcome` key.
| Key | Value |
| -------------------- | ---------------------------------------------------- |
@ -420,10 +467,9 @@ This event is emmited for every transaction to the database.
| `:outcome` | `:commit`, `:rollback`, `:restart`, or `:incomplete` |
| `:connection` | Connection object |
Please note that at this point the transaction has been finished, and its state
is in the `:outcome` key. In practice, you cannot do much with the transaction
object, but it may still be helpful for tracing database activity. For example,
by tracking `transaction.uuid`.
In practice, you cannot do much with the transaction object, but it may still be
helpful for tracing database activity. For example, by tracking
`transaction.uuid`.
### Action Mailer