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
+30 -17
View File
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- | WAI application for the Roux recipe server.
@@ -10,7 +11,7 @@ module Roux.Server (
) where
import Control.Concurrent (forkIO, threadDelay)
import Control.Exception (SomeException, catch)
import Control.Exception (IOException, SomeException, catch, try)
import Control.Monad (forever, when)
import Data.ByteString.Lazy (ByteString)
import Data.Char (toLower)
@@ -76,9 +77,16 @@ watchRecipes dir state = do
let cfg = defaultConfig{confWatchMode = WatchModeOS}
withManagerConf cfg $ \mgr -> do
_stopListening <- watchDir mgr dir (const True) $ \ev ->
when (eventIsDirectory ev == IsFile && ".cook" `isSuffixOf` eventPath ev) $ do
let path = makeRelative dir (eventPath ev)
reReadRecipe dir path state
catch
( do
when (eventIsDirectory ev == IsFile && ".cook" `isSuffixOf` eventPath ev) $ do
let path = makeRelative dir (eventPath ev)
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)
forever $ threadDelay 1000000
@@ -89,22 +97,27 @@ reReadRecipe :: FilePath -> FilePath -> IORef (Map FilePath Idx.RecipeInfo) -> I
reReadRecipe dir path state = do
let key = map toLower (stripCookExt path)
let fullPath = dir </> path
exists <- doesFileExist fullPath
if not exists
then do
result <- try (doesFileExist fullPath)
case result of
Left (_ :: IOException) -> do
putStrLn $ "[roux] IO error checking " <> path <> ", skipping"
Right False -> do
atomicModifyIORef' state $ \m ->
(Map.delete key m, ())
putStrLn $ "[roux] removed " <> path
else do
info <- Idx.loadRecipe dir path
case Idx.riRecipe info of
Left err -> do
putStrLn $ "[roux] parse error in " <> path <> ": " <> err
putStrLn $ "[roux] keeping previous version"
Right _ -> do
atomicModifyIORef' state $ \m ->
(Map.insert key info m, ())
putStrLn $ "[roux] updated " <> path
Right True -> do
content <- try (Idx.loadRecipe dir path)
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
putStrLn $ "[roux] parse error in " <> path <> ": " <> err
putStrLn $ "[roux] keeping previous version"
Right _ -> do
atomicModifyIORef' state $ \m ->
(Map.insert key info m, ())
putStrLn $ "[roux] updated " <> path
-- | Route an incoming request to the appropriate handler.
handleRequest :: IORef (Map FilePath Idx.RecipeInfo) -> Wai.Application