Я пишу базу данных своего рода. Основная функция, которую он экспортирует, следующая:
withDatabase :: FilePath -> (DBHandle -> IO a) -> IO a
, который автоматически управляет временем жизни дескрипторов базы данных.
Внутренне, withDatabase
использует функцию bracket
из Control.Exception .
withDatabase path f = bracket (openDatabase path) closeDatabase f
В моем конкретном случае openDatabase
может выполнять некоторые существенные операции ввода-вывода и, таким образом, блокировать в течение длительного времени. По этой причине я хотел бы запустить некоторую его часть с асинхронными исключениями без маскировки. (Упрощенная) реализация может быть:
openDatabase :: FilePath -> IO DBHandle
openDatabase path = mask $ \restore -> do
h <- openFile path ReadWriteMode
restore (doLongStuff h) `onException` (hClose h)
...
return (DBHandle h)
Я не уверен, что этот код производит желаемый эффект.
Давайте вернемся к withDatabase
, на этот раз заменив bracket
его определением:
withDatabase path f = mask $ \restore -> do
h <- openDatabase path
r <- restore (f h) `onException` closeDatabase h
_ <- closeDatabase h
return r
В определенный момент выполнения стек вызовов становится следующим:
\- withDatabase
\- mask
\- openDatabase
\- mask
\- restore
\- doLongStuff
В документации для модуля Control.Exception есть что-то о вложенных вызовах mask
:
Обратите внимание, что действие восстановления, переданное аргументу mask, не обязательно снимает маску с асинхронных исключений, оно просто восстанавливает состояние маскирования до состояния окружающего контекста. Таким образом, если асинхронные исключения уже замаскированы, маска не может быть использована для повторной маскировки исключений.
Мое понимание этого описания состоит в том, что doLongStuff
будет работать с асинхронными исключениями, замаскированными и не, как мне хотелось бы, разблокированными.
В моем реальном коде я не могу переместить ни openFile
, ни doLongStuff
из openDatabase
: на самом деле openDatabase
может открыть любое количество файлов и / или выполнить различные операции ввода-вывода, прежде чем "решить", какие обработать его хочет вернуться к withDatabase
. Учитывая это противоречие, есть ли способ сделать doLongStuff
прерываемым, даже если он выполняется внутри вложенного mask
вызова?