Describe the bug
Database tasks are enhanced with a push prerequisite and an appended pop action. If the original action raises, Rake skips the appended cleanup and leaves the writing-role frame on ActiveRecord::Base.connected_to_stack. Nested database tasks also reuse the already-invoked push task, allowing the inner cleanup to pop the outer task frame.
Steps to reproduce
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'activerecord', '8.1.3.1'
gem 'active_record_proxy_adapters', '0.11.1'
gem 'rake'
end
require 'active_record'
require 'active_record_proxy_adapters/rake'
Rake.application = Rake::Application.new
ActiveRecordProxyAdapters::Rake.load_tasks
Rake::Task.define_task('db:raises') { raise 'boom' }
ActiveRecordProxyAdapters::Rake.enhance_db_tasks
before = ActiveRecord::Base.connected_to_stack.length
begin
Rake::Task['db:raises'].invoke
rescue RuntimeError
end
after = ActiveRecord::Base.connected_to_stack.length
p [before, after]
A nested reproduction similarly observes stack depths [1, 1, 0] across the outer action, inner action, and resumed outer action instead of [1, 2, 1].
Expected behavior
Every database task owns one writing-role frame and removes it in exception-safe cleanup. The example prints [0, 0], and nested tasks retain the outer frame until the outer task completes.
Actual behavior
The example prints [0, 1]. A raised task leaks its frame, and a nested task can pop the frame still needed by its caller.
System configuration
Active Record version: 8.1.3.1
Active Record proxy adapters version: 0.11.1
Ruby version: 4.0.6
Describe the bug
Database tasks are enhanced with a push prerequisite and an appended pop action. If the original action raises, Rake skips the appended cleanup and leaves the writing-role frame on
ActiveRecord::Base.connected_to_stack. Nested database tasks also reuse the already-invoked push task, allowing the inner cleanup to pop the outer task frame.Steps to reproduce
A nested reproduction similarly observes stack depths
[1, 1, 0]across the outer action, inner action, and resumed outer action instead of[1, 2, 1].Expected behavior
Every database task owns one writing-role frame and removes it in exception-safe cleanup. The example prints
[0, 0], and nested tasks retain the outer frame until the outer task completes.Actual behavior
The example prints
[0, 1]. A raised task leaks its frame, and a nested task can pop the frame still needed by its caller.System configuration
Active Record version: 8.1.3.1
Active Record proxy adapters version: 0.11.1
Ruby version: 4.0.6