DB transaction中で非同期処理を防ぐ
DBのトランザクションをBEGINしてからCOMMIT or ROLLBACKする前に非同期処理を挟むのは危険なケースがある ロックを取得している場合はHTTPタイムアウトするまで長時間のロックとなる可能性がある 回避策
トランザクションを分離する
既存gem
Rails
トランザクション中かどうか?は以下のように判定できる
code:ruby
# open transactionsがあればyes
ActiveRecord::Base.connection.open_transactions > 0
# Rails 6.0以上
ActiveRecord::Base.connection.transaction_open?
前者はnested transactionの場合、1つとカウントされるので注意
all database statements in the nested transaction block become part of the parent transaction
テストをマルチスレッドで並列実行している場合に正常にカウントできるかどうか?は要調査
code:ruby
module A
def new(...) # 2.6以前なら def new(*args, **kwargs, &block)
puts 'new'
raise 'Stop open an HTTP connection in transaction' if ActiveRecord::Base.connection.transaction_open?
super
end
end
Net::HTTP.singleton_class.prepend A
Net::HTTP.new('')
Net::HTTPのクラスメソッドもすべてはnewを呼ぶ想定
以下のようなコードをinitializerのどこかに書いておけばよさそう
code:ruby
module Detectable
private
def should_raise?
if Rails.env.test? && defined?(RSpec) && RSpec.configuration.use_transactional_examples
ActiveRecord::Base.connection.open_transactions > 1
else
ActiveRecord::Base.connection.transaction_open?
end
end
end
module NetHTTPDetection
include Detectable
def new(...)
raise 'Stop open an HTTP connection in transaction' if should_raise?
super
end
end
module JobDetection
include Detectable
def enqueue(...)
raise 'Stop enqueue a job in transaction' if should_raise?
super
end
end
require 'net/http'
Net::HTTP.singleton_class.prepend NetHTTPDetection
require 'active_job/enqueuing'
ActiveJob::Enqueuing.prepend JobDetection