Я вижу очень странное поведение, когда 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 - полное предположение.