Em Programação Paralela e Concorrente em Haskell de Simon Marlow, a implementação de bracket
é mostrada,
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
bracket before after thing =
mask $ \restore -> do
a <- before
r <- restore (thing a) `onException` after a
_ <- after a
return r
e um comentário segue. Aqui está a parte que está me intrigando
[…] É normal que
before
contenha uma operação de bloqueio; se uma exceção for gerada enquantobefore
estiver bloqueado, não haverá danos. Masbefore
deve executar apenas uma operação de bloqueio. Uma exceção gerada por uma segunda operação de bloqueio não resultaria emafter
execução. […]
Não entendi bem esse comentário, então gostaria de alguns esclarecimentos sobre ele.
Para esclarecer, não entendi nem a parte de que nenhum dano é causado : algumas páginas antes (p. 159), a seguinte tentativa falhada (segunda) de chamar takeMVar
, executar uma operação dependendo de seu conteúdo e, finalmente, colocar o resultado dessa operação de volta na MVar
via putMVar
é mostrada,
problem :: MVar a -> (a -> IO a) -> IO ()
problem m f = mask $ \restore -> do
a <- takeMVar m
r <- restore (f a) `catch` \e -> do putMVar m a; throw e
putMVar m r
Ao analisar este exemplo, "aceito" como um fato que takeMVar
não pode ser alterado m
até que retorne; na verdade, isso foi retirado do texto:
seria seguro que exceções fossem levantadas até o ponto em que
takeMVar
retornasse.
e sinto que entendo o que se segue (eu acho ).
Mas voltando à implementação de bracket
, e se before
internamente usar takeMVar
, e uma exceção assíncrona ocorrer após o takeMVar
retorno dela (esvaziando um MVar
)? Isso não é um problema, justamente pelo motivo de estarmos usando mask
+ restore
, ou seja, tal exceção seria adiada até que o argumento para restore
, que é thing a
, comece a ser executado, momento em que o manipulador de exceções necessário está em vigor, neste caso via `onException` after a
?
E o que dá errado se before
fizer outra chamada para a operação de bloqueio, digamos takeMVar
novamente para simplificar? O problema ocorre porque a exceção poderia ocorrer enquanto takeMVar
o bloqueio estiver em andamento, portanto, na janela de tempo em que as exceções não são mascaradas, de modo que a exceção sairia de bracket
, deixando o MVar
argumento that was para o primeiro takeMVar
não vazio, mas em um estado diferente do original, dado que after
não teve a chance de ser executado?
É isso?
Além disso, a página do documento não menciona isso, ou não vejo como isso está implícito.
Imagine que, no argumento before para
bracket
, você tentou abrir dois arquivos diferentes, um depois do outro. (E também, correspondentemente, que você tentou fechá-los no argumento after ).Se a tentativa de abrir o primeiro arquivo falhar por qualquer motivo, nenhum identificador será alocado e não haverá nada para limpar.
Mas suponha que o primeiro arquivo seja aberto com sucesso, mas a tentativa de abrir o segundo arquivo falhe (talvez por si só, talvez por alguma exceção assíncrona recebida durante o bloqueio). Como after não é chamado para exceções geradas durante before , o identificador do primeiro arquivo permanecerá aberto.
Isso está correto. Quando estamos em um estado "mascarado", exceções assíncronas só podem ocorrer quando uma operação interrompível (como
takeMVar
) é bloqueada.Como menciona o capítulo "Mascarando exceções assíncronas" do livro: