fix: catch IOExceptions in reReadRecipe and wrap watchDir callback for resilience

This commit is contained in:
2026-05-19 23:40:13 -04:00
parent 956a4bc7d7
commit ce9062a4b3
+20 -7
View File
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- | WAI application for the Roux recipe server. {- | WAI application for the Roux recipe server.
@@ -10,7 +11,7 @@ module Roux.Server (
) where ) where
import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent (forkIO, threadDelay)
import Control.Exception (SomeException, catch) import Control.Exception (IOException, SomeException, catch, try)
import Control.Monad (forever, when) import Control.Monad (forever, when)
import Data.ByteString.Lazy (ByteString) import Data.ByteString.Lazy (ByteString)
import Data.Char (toLower) import Data.Char (toLower)
@@ -76,9 +77,16 @@ watchRecipes dir state = do
let cfg = defaultConfig{confWatchMode = WatchModeOS} let cfg = defaultConfig{confWatchMode = WatchModeOS}
withManagerConf cfg $ \mgr -> do withManagerConf cfg $ \mgr -> do
_stopListening <- watchDir mgr dir (const True) $ \ev -> _stopListening <- watchDir mgr dir (const True) $ \ev ->
catch
( do
when (eventIsDirectory ev == IsFile && ".cook" `isSuffixOf` eventPath ev) $ do when (eventIsDirectory ev == IsFile && ".cook" `isSuffixOf` eventPath ev) $ do
let path = makeRelative dir (eventPath ev) let path = makeRelative dir (eventPath ev)
reReadRecipe dir path state reReadRecipe dir path state
)
( \e ->
putStrLn $
"[roux] watchDir callback error: " <> show (e :: SomeException)
)
-- Keep the thread alive (watchDir runs the callback on the manager thread) -- Keep the thread alive (watchDir runs the callback on the manager thread)
forever $ threadDelay 1000000 forever $ threadDelay 1000000
@@ -89,15 +97,20 @@ reReadRecipe :: FilePath -> FilePath -> IORef (Map FilePath Idx.RecipeInfo) -> I
reReadRecipe dir path state = do reReadRecipe dir path state = do
let key = map toLower (stripCookExt path) let key = map toLower (stripCookExt path)
let fullPath = dir </> path let fullPath = dir </> path
exists <- doesFileExist fullPath result <- try (doesFileExist fullPath)
if not exists case result of
then do Left (_ :: IOException) -> do
putStrLn $ "[roux] IO error checking " <> path <> ", skipping"
Right False -> do
atomicModifyIORef' state $ \m -> atomicModifyIORef' state $ \m ->
(Map.delete key m, ()) (Map.delete key m, ())
putStrLn $ "[roux] removed " <> path putStrLn $ "[roux] removed " <> path
else do Right True -> do
info <- Idx.loadRecipe dir path content <- try (Idx.loadRecipe dir path)
case Idx.riRecipe info of case content of
Left (_ :: IOException) -> do
putStrLn $ "[roux] IO error reading " <> path <> ", keeping previous version"
Right info -> case Idx.riRecipe info of
Left err -> do Left err -> do
putStrLn $ "[roux] parse error in " <> path <> ": " <> err putStrLn $ "[roux] parse error in " <> path <> ": " <> err
putStrLn $ "[roux] keeping previous version" putStrLn $ "[roux] keeping previous version"