Безопасно ли использовать unsafeIOToSTM для чтения из базы данных?

В этом блоке псевдокода:

atomically $ do
  if valueInLocalStorage key
      then readValueFromLocalStorage key
      else do
        value <- unsafeIOToSTM $ fetchValueFromDatabase key
        writeValueToLocalStorage key value

Безопасно ли использовать unsafeIOToSTM? Документы говорят:

  • Реализация STM часто запускает транзакции несколько раз, поэтому вы должны быть готовы к этому, если ваш IO имеет какие-либо побочные эффекты.

    По сути, если транзакция завершается неудачей, то это потому, что какой-то другой поток wroteValueToLocalStorage и когда транзакция повторяется, она возвращает сохраненное значение вместо того, чтобы снова извлечь из базы данных.

  • Реализация STM прервет транзакции, которые, как известно, являются недействительными и должны быть перезапущены. Это может произойти в середине unsafeIOToSTM, поэтому убедитесь, что вы не получаете никаких ресурсов, требующих освобождения (обработчики исключений игнорируются при прерывании транзакции). Это включает в себя, например, любой ввод-вывод с использованием дескрипторов. Неправильное понимание этого может привести к случайным тупикам.

    Это беспокоит меня больше всего. Логично, если fetchValueFromDatabase не открывает новое соединение (т.е. используется существующее соединение) все должно быть хорошо. Есть ли другие подводные камни, которые я пропускаю?

  • Транзакция могла видеть несовместимое представление памяти, когда IO выполняется. Инварианты, которые вы ожидаете, чтобы быть истинными во всей вашей программе, могут не быть истинными внутри транзакции, из-за способа реализации транзакций. Обычно это не будет видно программисту, но использование unsafeIOToSTM может раскрыть его.

    key это одно значение, без инвариантов, чтобы сломать.

1 ответ

Решение

Я бы сказал, что ввод-вывод из транзакции STM - просто плохая идея.

Предположительно, вам нужно избегать двух потоков, выполняющих поиск в БД одновременно. Что бы я сделал, это:

  • Посмотрите, находится ли элемент в кеше. Если это так, мы сделали.

  • Если это не так, пометьте его флагом "Я получаю это", зафиксируйте транзакцию STM, перейдите, чтобы получить ее из БД, и выполните вторую транзакцию STM, чтобы вставить ее в кэш (и убрать флаг).

  • Если элемент уже помечен, retry перевод. Это блокирует вызывающий поток, пока первый поток не вставит значение из БД.

Другие вопросы по тегам