异步异常的嵌套屏蔽

5

我正在编写一种数据库库。它所导出的基本功能如下:

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,也不能移动doLongStuffopenDatabase:事实上,在“决定”要将哪个句柄返回给withDatabase之前,openDatabase可能会打开任意数量的文件和/或执行各种I/O操作。鉴于这个限制,是否有办法使doLongStuff即使在运行时处于嵌套的mask调用中,也能够被中断?

1个回答

0

doLongStuff 是否在数据库上执行?如果是,则可能已经可以中断。请参阅可中断操作一节,其中指出任何可能阻塞的函数(包括大多数执行IO的函数)都可以在掩码范围内被中断。

如果您不确定doLongStuff是否可中断(取决于它使用哪些函数),则可以使用allowInterrupt在掩码代码中轮询异步异常,或者使用MVar同步从DB读取。这将起作用,因为大多数MVar操作本身也是可中断的,从而允许更大的函数也是可中断的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接