как проверить механизм блокировки - PullRequest
2 голосов
/ 25 марта 2019

У меня есть фрагмент кода, куда я импортирую BankAccountTransaction в BankAccount

 bank_account.with_lock do
   transactions.each do |transaction|
     import(bank_account, transaction)
   end
 end

все работает нормально, но мне нужно записать в него регистр RSpec, чтобы я был на 100% уверен, что я не импортирую транзакции дважды.

Я написал следующий помощник

module ConcurrencyHelper
  def make_concurrent_calls(function, concurrent_calls: 2)
    threads = Array.new(concurrent_calls) do
      thread = Thread.new { function.call }
      thread.abort_on_exception = true

      thread
    end

    threads.each(&:join)
  end
end

и я звоню по RSpec

context 'when importing the same transaction twice' do
  subject(:concurrent_calls) { make_concurrent_calls(operation) }

  let!(:operation) { -> { described_class.call(params) } }
  let(:filename) { 'single-transaction-response.xml' }

  it 'creates only one transaction' do
    expect { concurrent_calls }.to change(BankaccountTransaction, :count).by(1)
  end
end

но ничего не происходит, тестовый костюм застревает на этом этапе и не выдается никаких ошибок или чего-то в этом роде.

Я поставил точку отладки (byebug) сразу после того, как я создал экземпляр потока и попытался вызвать функцию, и она работает нормально, но когда я присоединяюсь к потокам, ничего не происходит.

То, что я до сих пор пробовал

  • точка останова до threads.each(&:join) и вызов функции (работает нормально)
  • точка останова в примере rspec и отладка operation и params (все хорошо)

есть еще идеи?

Редактировать

это моя текущая конфигурация DatabaseCleaner

RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.clean_with(:deletion)
  end

  config.before do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :deletion
  end

  config.before do
    DatabaseCleaner.start
  end

  config.after do
    DatabaseCleaner.clean
  end
end

Я еще не пытался изменить стратегию на :deleteion, я тоже сделаю это

Ответы [ 2 ]

1 голос
/ 26 марта 2019

Похоже, здесь происходит пара вещей.

Во-первых, суть проблемы, вероятно, лежит в нескольких областях:

  1. Стратегия очистки базы данных : вы используете самоцвет DatabaseCleaner , который предоставляет три стратегии очистки: усечение, транзакция и удаление. Я предполагаю, что если вы используете стратегию Transaction, блокировка никогда не снимается, потому что первая транзакция никогда не фиксируется, а второй поток просто ожидает ее освобождения.
  2. Конфигурация пула баз данных : Другая возможная теория заключается в том, что пул соединений для вашей тестовой базы данных слишком мал. Это будет означать, что один (или оба) из ваших потоков ожидают получения соединения с базой данных. Обычно для этого настроено время ожидания, и если оно установлено, вы должны увидеть исключение, которое выглядит примерно так: could not obtain a connection from the pool within 5.000 seconds. Чтобы исправить это, в database.yml в test настройте pool настройку . Вы можете проверить значения, которые установлены, посмотрев на:
irb(main):001:0> ActiveRecord::Base.connection.pool.size
=> 5
irb(main):001:0> ActiveRecord::Base.connection.pool.checkout_timeout
=> 5

Во-вторых, в предоставленном вами коде, если import не изменяет транзакции или импортируемый банковский счет, создается впечатление, что with_lock фактически не будет предотвращать многократную загрузку ... он просто будет гарантировать, что они выполняются последовательно .

Возможно, вам придется сделать что-то вроде этого:

 bank_account.with_lock do
   unimported_transactions.each do |transaction|
     import(bank_account, transaction)
     transaction.mark_as_imported!
   end
 end

Кроме того, если импорт выполняет какой-то внешний запрос, вам следует быть осторожным в отношении частичных сбоев и откатов. (with_lock оборачивает все SQL-запросы внутри него в транзакцию базы данных, и, если выдается исключение, он будет выполнять откат в вашей базе данных, но не во внешней службе)

0 голосов
/ 26 марта 2019

with_lock - это реализация Rails, которую нам не нужно тестировать.Вы можете использовать mock и проверить, звонит ли ваш код with_lock.Единственная хитрость здесь заключается в том, чтобы гарантировать, что транзакция импортируется (т. Е. Выполняется код внутри with_lock).RSpec предоставит блок, который вы можете вызвать.Ниже приведен фрагмент того, как вы можете это сделать - полную рабочую реализацию можно найти здесь .

describe "#import_transactions" do
  it "runs with lock" do
    # Test if with_lock is getting called
    expect(subject).to receive(:with_lock) do |*_args, &block|
      # block is provided to with_lock method
      # execute the block and test if it creates transactions
      expect { block.call }
        .to change { BankAccountTransaction.count }.from(0).to(2)
    end

    ImportService.new.import_transactions(subject, transactions)
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...