Я вижу очень странное поведение, когда bracket
функция Haskell ведет себя по-разному в зависимости от того, используется ли она stack run
или нет stack test
.
Рассмотрим следующий код, в котором две вложенные скобки используются для создания и очистки контейнеров Docker:
module Main where
import Control.Concurrent
import Control.Exception
import System.Process
main :: IO ()
main = do
bracket (callProcess "docker" ["run", "-d", "--name", "container1", "registry:2"])
(\() -> do
putStrLn "Outer release"
callProcess "docker" ["rm", "-f", "container1"]
putStrLn "Done with outer release"
)
(\() -> do
bracket (callProcess "docker" ["run", "-d", "--name", "container2", "registry:2"])
(\() -> do
putStrLn "Inner release"
callProcess "docker" ["rm", "-f", "container2"]
putStrLn "Done with inner release"
)
(\() -> do
putStrLn "Inside both brackets, sleeping!"
threadDelay 300000000
)
)
Когда я запускаю это с stack run
и прерываю с Ctrl+C
, я получаю ожидаемый результат:
Inside both brackets, sleeping!
^CInner release
container2
Done with inner release
Outer release
container1
Done with outer release
И я могу убедиться, что оба контейнера Docker созданы, а затем удалены.
Однако, если я вставлю этот же код в тест и stack test
запустлю, произойдет только (часть) первая очистка:
Inside both brackets, sleeping!
^CInner release
container2
Это приводит к тому, что на моей машине остался контейнер Docker. В чем дело?
- Я убедился, что то же самое
ghc-options
передается обоим. - Полное демонстрационное репо здесь: https://github.com/thomasjm/bracket-issue
.stack-work
и запускаю его напрямую, то проблема не возникает. Это происходит только при работе под stack test
.
stack test
запускает рабочие потоки для обработки тестов. 2) обработчик SIGINT убивает основной поток. 3) Программы на Haskell завершаются, когда основной поток завершает работу, игнорируя любые дополнительные потоки. 2 - поведение по умолчанию в SIGINT для программ, скомпилированных GHC. 3, как работают потоки в Haskell. 1 - полное предположение.