Ugly devel hack, but it (hopefully) works

This commit is contained in:
Michael Snoyman 2011-04-29 09:24:22 +03:00
parent 36966acc74
commit b04e034b0d
3 changed files with 79 additions and 90 deletions

View File

@ -1,59 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
module Network.Wai.Application.Devel
( -- * Types
AppHolder
, AppRunner
, WithAppRunner
-- * Functions
, initAppHolder
, swapApp
, swapAppSimple
, toApp
) where
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar
( MVar, newEmptyMVar, newMVar
, takeMVar, putMVar, readMVar
)
import Network.Wai (Application, responseLBS)
import Network.HTTP.Types (status500)
import Data.ByteString.Lazy.Char8 ()
import Control.Monad.IO.Class (liftIO)
type AppHolder = MVar (Application, MVar ())
type AppRunner = Application -> IO ()
type WithAppRunner = AppRunner -> IO ()
initAppHolder :: IO AppHolder
initAppHolder = do
flag <- newEmptyMVar
newMVar (initApp, flag)
where
initApp _ = return
$ responseLBS status500 [("Content-Type", "text/plain")]
$ "No app has yet been loaded"
swapAppSimple :: Application -> AppHolder -> IO ()
swapAppSimple app =
swapApp war
where
war f = f app
swapApp :: WithAppRunner -> AppHolder -> IO ()
swapApp war ah = void $ forkIO $ war $ \app -> do
(_, oldFlag) <- takeMVar ah
-- allow the old app to cleanup
putMVar oldFlag ()
-- now place the new app into the AppHolder, waiting for a termination
-- signal
flag <- newEmptyMVar
putMVar ah (app, flag)
takeMVar flag -- this causes execution to hang until we are terminated
where
void x = x >> return ()
toApp :: AppHolder -> Application
toApp ah req = do
(app, _) <- liftIO $ readMVar ah
app req

View File

@ -13,28 +13,40 @@ import Distribution.PackageDescription.Parse (readPackageDescription, readHooked
import Distribution.PackageDescription (FlagName (FlagName), package, emptyHookedBuildInfo)
import Distribution.Simple.LocalBuildInfo (localPkgDescr)
import Scaffold.Build (getDeps, touchDeps, findHaskellFiles)
import System.Plugins (loadDynamic)
import Network.Wai.Handler.Warp (run)
import Network.Wai.Application.Devel
import Network.Wai.Middleware.Debug (debug)
import Data.Dynamic (fromDynamic)
import Distribution.Text (display)
import Distribution.Simple.Install (install)
import Distribution.Simple.Register (register)
import Control.Concurrent (forkIO, threadDelay)
import Control.Exception (try, SomeException)
import Control.Concurrent (forkIO, threadDelay, ThreadId, killThread)
import Control.Exception (try, SomeException, finally)
import System.PosixCompat.Files (modificationTime, getFileStatus)
import qualified Data.Map as Map
import System.Posix.Types (EpochTime)
import Blaze.ByteString.Builder.Char.Utf8 (fromString)
import Network.Wai (Application, Response (ResponseBuilder))
import Network.Wai (Application, Response (ResponseBuilder), responseLBS)
import Network.HTTP.Types (status500)
import Control.Monad (when)
import Control.Monad (when, forever)
import System.Process (runCommand, terminateProcess, getProcessExitCode, waitForProcess)
import qualified Data.IORef as I
import qualified Data.ByteString.Lazy.Char8 as L
import System.Directory (doesFileExist, removeFile)
appMessage :: L.ByteString -> IO ()
appMessage l = forever $ do
run 3000 . const . return $ responseLBS status500 [("Content-Type", "text/plain")] l
threadDelay 10000
swapApp :: I.IORef ThreadId -> IO ThreadId -> IO ()
swapApp i f = do
I.readIORef i >>= killThread
f >>= I.writeIORef i
devel :: IO ()
devel = do
appHolder <- initAppHolder
_ <- forkIO $ run 3000 $ debug $ toApp appHolder
e <- doesFileExist "dist/devel-flag"
when e $ removeFile "dist/devel-flag"
listenThread <- forkIO (appMessage "Initializing, please wait") >>= I.newIORef
cabal <- defaultPackageDesc normal
gpd <- readPackageDescription normal cabal
@ -50,12 +62,15 @@ devel = do
, configUserInstall = Flag True
}
let myTry :: IO (Either String x) -> IO (Either String x)
myTry f = try f >>= \x -> return $ case x of
Left e -> Left $ show (e :: SomeException)
Right y -> y
let getNewApp :: IO (Either String WithAppRunner)
let myTry :: IO () -> IO ()
myTry f = try f >>= \x -> case x of
Left e -> swapApp listenThread $ forkIO $ appMessage $ L.pack $ show (e :: SomeException)
Right y -> return y
let getNewApp :: IO ()
getNewApp = myTry $ do
putStrLn "Rebuilding app"
swapApp listenThread $ forkIO $ appMessage "Rebuilding your app, please wait"
deps <- getDeps
touchDeps deps
@ -69,13 +84,50 @@ devel = do
register (localPkgDescr lbi) lbi defaultRegisterFlags
let pi' = display $ package $ localPkgDescr lbi
dyn <- loadDynamic (pi', "Controller", "withDevelApp")
return $ case fmap fromDynamic dyn of
Nothing -> Left "withDevelApp not found"
Just Nothing -> Left "Not a withApp"
Just (Just withApp) -> Right withApp
writeFile "dist/devel.hs" $ unlines
[ "{-# LANGUAGE PackageImports #-}"
, concat
[ "import \""
, "haskellers" -- FIXME
, "\" Controller (withDevelApp)"
]
, "import Data.Dynamic (fromDynamic)"
, "import Network.Wai.Handler.Warp (run)"
, "import Network.Wai.Middleware.Debug (debug)"
, "import Data.Maybe (fromJust)"
, "import Control.Concurrent (forkIO)"
, "import System.Directory (doesFileExist, removeFile)"
, "import Control.Concurrent (threadDelay)"
, ""
, "main :: IO ()"
, "main = do"
, " putStrLn \"Starting app\""
, " forkIO $ (fromJust $ fromDynamic withDevelApp) $ run 3000"
, " loop"
, ""
, "loop :: IO ()"
, "loop = do"
, " threadDelay 100000"
, " e <- doesFileExist \"dist/devel-flag\""
, " if e then removeFile \"dist/devel-flag\" else loop"
]
swapApp listenThread $ forkIO $ do
putStrLn "Calling runghc..."
ph <- runCommand "runghc dist/devel.hs"
let forceType :: Either SomeException () -> ()
forceType = const ()
fmap forceType $ try sleepForever
writeFile "dist/devel-flag" ""
putStrLn "Terminating external process"
terminateProcess ph
putStrLn "Process terminated"
ec <- waitForProcess ph
putStrLn $ "Exit code: " ++ show ec
loop Map.empty appHolder getNewApp
loop Map.empty getNewApp
sleepForever :: IO ()
sleepForever = forever $ threadDelay 1000000
type FileList = Map.Map FilePath EpochTime
@ -88,16 +140,13 @@ getFileList = do
fs <- getFileStatus f
return (f, modificationTime fs)
loop :: FileList -> AppHolder -> IO (Either String WithAppRunner) -> IO ()
loop oldList appHolder getNewApp = do
loop :: FileList -> IO () -> IO ()
loop oldList getNewApp = do
putStrLn "Testing files..."
newList <- getFileList
when (newList /= oldList) $ do
res <- getNewApp
case res of
Left s -> swapAppSimple (errApp s) appHolder
Right x -> swapApp x appHolder
when (newList /= oldList) getNewApp
threadDelay 1000000
loop newList appHolder getNewApp
loop newList getNewApp
errApp :: String -> Application
errApp s _ = return $ ResponseBuilder status500 [("Content-Type", "text/plain")] $ fromString s

View File

@ -1,5 +1,5 @@
name: yesod
version: 0.8.0
version: 0.8.0.1
license: BSD3
license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com>
@ -59,13 +59,12 @@ executable yesod
, attoparsec-text >= 0.8.5 && < 0.9
, http-types >= 0.6.1 && < 0.7
, blaze-builder >= 0.2 && < 0.4
, direct-plugins >= 1.1 && < 1.2
, process
ghc-options: -Wall -threaded
main-is: scaffold.hs
other-modules: CodeGen
Scaffold.Build
Scaffold.Devel
Network.Wai.Application.Devel
if flag(ghc7)
cpp-options: -DGHC7