Безопасно ли использовать 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
перевод. Это блокирует вызывающий поток, пока первый поток не вставит значение из БД.