In Haskell we have asynchronous exceptions; we can use throwTo
to raise any exception in another thread:
throwTo :: Exception e => ThreadId -> e -> IO ()
throwTo
raises an arbitrary exception in the target thread (GHC only).
To be able to write code with guarantees like "will always release a lock after acquiring it", we have mask
to run code in which asynchronous exceptions may only be received while the computation is blocking:
mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b
Executes an IO computation with asynchronous exceptions masked. That is, any thread which attempts to raise an exception in the current thread with
throwTo
will be blocked until asynchronous exceptions are unmasked again.
and a stronger uninterruptibleMask
in which async exceptions will not be raised at all during a masked computation:
uninterruptibleMask :: ((forall a. IO a -> IO a) -> IO b) -> IO b
Like
mask
, but the masked computation is not interruptible
Masking is used to implement higher-level abstractions like bracket
:
bracket :: IO a -- computation to run first ("acquire resource") -> (a -> IO b) -- computation to run last ("release resource") -> (a -> IO c) -- computation to run in-between -> IO c -- returns the value from the in-between computation
When you want to acquire a resource, do some work with it, and then release the resource, it is a good idea to use
bracket
, becausebracket
will install the necessary exception handler to release the resource in the event that an exception is raised during the computation. If an exception is raised, thenbracket
will re-raise the exception (after performing the release).
If I understand correctly, Python has a (less general) form of asynchronous exceptions, with the most notable manifestation being KeyboardInterrupt
:
Raised when the user hits the interrupt key (normally Control-C or Delete). During execution, a check for interrupts is made regularly.
The documentation is imprecise about when the "check for interrupts" may occur, but it seems to imply that a KeyboardInterrupt
may be raised at any point in a program's execution. So it seems, then, that Python's async exceptions come with all the same subtle difficulties of maintaining correctness.
For example, consider a pattern like this:
x = None
try:
x = acquire()
do_something(x) # (1)
finally:
if x is not None: # (2)
release(x)
If any exception is raised during (1)
, then we are assured that the contents of the finally
block will be executed. But what happens if a KeyboardInterrupt
is during (2)
?
It seems fundamentally impossible to guarantee resource cleanup in the presence of asyc exceptions without a way to mask them. Is there some facility for this, or do we rely on the ostrich algorithm?
This is what context managers are for.
with acquire() as x:
do_something(x)
acquire
returns an object whose type defines an __enter__
method, which returns the value bound to x
, and an __exit__
method, which is executed at the end of the with
statement regardless of how the statement is exited.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With