Внезапные необъяснимые тайм-ауты подключения активной записи при тестировании с RSPEC
Это та область, о которой я почти ничего не знаю, поэтому заранее извиняюсь. У меня есть набор из более чем 800 тестов rspec. Внезапно и необъяснимо при запуске всего набора или только отдельных тестовых файлов, после всего лишь нескольких из них (скажем, 20 или около того, хотя это число никогда не бывает одинаковым), каждый отдельный тест начинает давать сбой с одной и той же ошибкой:
Failure/Error: Unable to find matching line from backtrace
ActiveRecord::ConnectionTimeoutError:
could not obtain a database connection within 5.000 seconds (waited 5.000 seconds)
В типичном прогоне я начну получать эти ошибки после примерно 20 тестов запроса, а остальные 780+ тестов не пройдут с точно такой же ошибкой, описанной выше. Я попытался вернуться к предыдущему git commit и ветке, которые ранее отлично тестировались. Не повезло - все равно 780+ неудач. Также полностью сброшена воссозданная тестовая БД. Тоже не повезло.
Я читал много веток о пулах соединений и т. Д., Но боюсь, что понятия не имею, как диагностировать даже то, что происходит. Вот факты, которые я знаю прямо сейчас:
- Использование Postgresql
- Среда разработки работает нормально, насколько я могу судить
- В промежутке времени все работало нормально, и теперь я не внес никаких изменений / обновлений в ту среду, о которой мне известно. Даже не миграции баз данных. Просто изменения в модель, вид и код контроллера. И, как я уже говорил, возвращение к предыдущим коммитам ничего не исправит.
config.use_transactional_fixtures = false
в spec_helper.rb, потому что я тестирую функциональность ajax через Selenium.- Тесты, однако, не проходят независимо от того, используется ли Selenium для определенного набора тестов. Даже если я запускаю только те тесты, которые не используют Selenium, сбои все равно начинаются после примерно 20 тестов.
Вместо фиксаций транзакций я использую Database Cleaner со следующей конфигурацией:
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, :js => true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
Есть идеи, что здесь происходит? А точнее, есть идеи, где мне следует искать проблему? У меня почти нет опыта работы с такими проблемами ActiveRecord, и я даже не знаю, с чего начать.
Обновление Хотя я до сих пор точно не знаю, почему это происходит, я точно знаю, какой код вызывает это. В недавнем коммите я добавил отправку уведомления по электронной почте в новой теме. Вот код:
def teacher_notification_email
Thread.new do
UserMailer.accepted_parent_invitation_email(@parent_profile).deliver
ActiveRecord::Base.connection.close
end
end
Я использовал этот точный шаблон (с разными электронными письмами) во многих других местах приложения, и все они проходят проверку. По какой-то причине именно эта причина вызывает ошибки тайм-аута базы данных. Любые идеи о том, почему это происходит, приветствуются.
Обновление Поскольку я не нахожусь на стадии, когда я понимаю, как работает многопоточность в этом случае, я не знаю точного источника проблемы, кроме этого: из того, что я прочитал, очень сложно программно контролировать выполнение темы, созданной таким образом. Тем не менее, я нашел решение. Вместо вышеуказанного блока я изменил блок на следующий:
def teacher_notification_email
if Rails.env.test?
UserMailer.accepted_parent_invitation_email(@parent_profile).deliver
else
Thread.new do
UserMailer.accepted_parent_invitation_email(@parent_profile).deliver
ActiveRecord::Base.connection.close
end
end
end
Так что я в основном запускаю другой код для тестирования, чем для разработки - нет нового потока для теста. Я предполагаю, что это плохая идея, но пока я не могу понять, где настоящая проблема (тест, который по своей сути не проваливается по какой-либо причине, или код, который все еще использует поток, который не вызывает принудительный тест), это это то, что мне нужно идти
Окончательное обновление
Я бросил Thread.new
метод асинхронной отправки электронной почты и, вместо этого, реализовал Sidekiq. Это немного больше работы, но она работает хорошо и тестирует нормально...
1 ответ
Кажется, это связано с касанием активной записи внутри порожденной нити. Похоже, что соединение с БД не возвращается в пул, пока не будет собрано. Я смог решить эту проблему, явно запросив соединение заранее и закрыв его после завершения. Попробуй это:
Thread.new do
ActiveRecord::Base.connection_pool.with_connection do |conn|
UserMailer.accepted_parent_invitation_email(@parent_profile).deliver
end
end