Tengo problemas con Haskell bracket
: cuando se ejecuta dentro de un hilo bifurcado (usando forkFinally
) bracket
el segundo argumento, el cálculo que libera recursos no se ejecuta cuando finaliza el programa.
Aquí hay un código que ilustra el problema (soy consciente de que en este caso específico podría deshabilitar el almacenamiento en búfer para escribir en el archivo de inmediato):
import System.IO
import Control.Exception ( bracket
, throwTo
)
import Control.Concurrent ( forkFinally
, threadDelay
)
main = do
threadId <- forkFinally
(writeToFile "first_file")
(\ex -> putStrLn $ "Exception occurred: " ++ show ex)
putStrLn "Press enter to exit"
_ <- getLine
putStrLn "Bye!"
writeToFile :: FilePath -> IO ()
writeToFile file = bracket
(openFile file AppendMode)
(\fileHandle -> do
putStrLn $ "\nClosing handle " ++ show fileHandle
hClose fileHandle
)
(\fileHandle -> mapM_ (addNrAndWait fileHandle) [1 ..])
addNrAndWait :: Handle -> Int -> IO ()
addNrAndWait fileHandle nr =
let nrStr = show nr
in do
putStrLn $ "Appending " ++ nrStr
hPutStrLn fileHandle nrStr
threadDelay 1000000
El cálculo que libera recursos (y escribe en la consola) nunca se llama:
putStrLn $ "\nClosing handle " ++ show fileHandle
hClose fileHandle
Al hacer que el programa main
tenga un solo subproceso al eliminar el código de bifurcación, se elimina el problema y el identificador de archivo se cierra al finalizar el programa con Ctrl+ c:
main = writeToFile "first_file"
¿Cómo me aseguro de que el código de liberación de recursos bracket
se ejecute al usar varios subprocesos?
fuente
threadDelay
antes de imprimir"Closing handle"
.)La causa raíz del problema aquí es que cuando
main
sale, su proceso simplemente muere. No espera en ningún otro hilo que hayas creado para terminar. Entonces, en su código original, creó un hilo para escribir en el archivo, pero no se le permitió terminar.Si quieres matar el hilo pero obligarlo a que se limpie, úsalo
throwTo
como lo hiciste aquí. Si desea que el hilo termine, deberá esperar antes de quemain
vuelva. Vea Cómo obligar al hilo principal a esperar a que todos sus hilos secundarios terminen en Haskellfuente
utilizando
async
Hacer
getLine
bloquear el hilo principal indefinidamente no funciona bien connohup
: fallará conComo alternativa a
getLine
ythrowTo
, puede usarasync
las funciones de :Esto permite ejecutar el programa con
nohup ./theProgram-exe &
¹, por ejemplo en un servidor a través de SSH .async
También brilla cuando se ejecutan varias tareas simultáneamente:La función
race_
ejecuta dos tareas simultáneamente y espera hasta que llegue el primer resultado. Con nuestra finalizaciónwriteToFile
no habrá un resultado regular, pero si una de las tareas arroja una excepción, la otra también se cancelará. Esto es útil para ejecutar un servidor HTTP y un servidor HTTPS simultáneamente, por ejemplo.Para cerrar el programa limpiamente, dando a los hilos la oportunidad de liberar recursos
bracket
, le envío la señal SIGINT :Manejo de SIGTERM
Para finalizar también los subprocesos con gracia en un SIGTERM , podemos instalar un controlador que captará la señal:
Ahora, nuestro programa lanzará sus recursos
bracket
cuando reciba SIGTERM:Aquí está el equivalente para dos tareas concurrentes que admiten SIGTERM:
Para más información sobre el tema de Haskell asíncrono, eche un vistazo a la programación paralela y concurrente en Haskell por Simon Marlow.
¹ Llame
stack build
para obtener un ejecutable en, por ejemplo,.stack-work/dist/x86_64-linux-tinfo6/Cabal-2.4.0.1/build/theProgram-exe/theProgram-exe
fuente