我正在编写一种数据库库。它所导出的基本功能如下:
withDatabase :: FilePath -> (DBHandle -> IO a) -> IO a
这个功能会自动管理数据库句柄的生命周期。
withDatabase
内部使用了 Control.Exception 中的 bracket
函数。
withDatabase path f = bracket (openDatabase path) closeDatabase f
在我的特定情况下,openDatabase
可能执行一些重要的I/O操作,因此会阻塞很长时间。因此,我想使用异步异常未屏蔽的方式运行其中的一部分。一个(简化的)实现可能是:
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
参数的恢复动作不一定会解除异步异常的屏蔽,它只是将掩码状态恢复到包含上下文的状态。因此,如果异步异常已经被屏蔽,那么就不能使用mask
再次取消屏蔽异常。
我对这个描述的理解是doLongStuff
可以在异步异常被屏蔽和未被屏蔽的情况下工作,而我希望它能够被解除屏蔽。
在我的实际代码中,我既不能移动openFile
,也不能移动doLongStuff
出openDatabase
:事实上,在“决定”要将哪个句柄返回给withDatabase
之前,openDatabase
可能会打开任意数量的文件和/或执行各种I/O操作。鉴于这个限制,是否有办法使doLongStuff
即使在运行时处于嵌套的mask
调用中,也能够被中断?