DevelMain.hs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. -- | Running your app inside GHCi.
  2. --
  3. -- This option provides significantly faster code reload compared to
  4. -- @yesod devel@. However, you do not get automatic code reload
  5. -- (which may be a benefit, depending on your perspective). To use this:
  6. --
  7. -- 1. Start up GHCi
  8. --
  9. -- $ stack ghci MyBlog:lib --no-load --work-dir .stack-work-devel
  10. --
  11. -- 2. Load this module
  12. --
  13. -- > :l app/DevelMain.hs
  14. --
  15. -- 3. Run @update@
  16. --
  17. -- > DevelMain.update
  18. --
  19. -- 4. Your app should now be running, you can connect at http://localhost:3000
  20. --
  21. -- 5. Make changes to your code
  22. --
  23. -- 6. After saving your changes, reload by running:
  24. --
  25. -- > :r
  26. -- > DevelMain.update
  27. --
  28. -- You can also call @DevelMain.shutdown@ to stop the app
  29. --
  30. -- There is more information about this approach,
  31. -- on the wiki: https://github.com/yesodweb/yesod/wiki/ghci
  32. --
  33. -- WARNING: GHCi does not notice changes made to your template files.
  34. -- If you change a template, you'll need to either exit GHCi and reload,
  35. -- or manually @touch@ another Haskell module.
  36. module DevelMain where
  37. import Prelude
  38. import Application (getApplicationRepl, shutdownApp)
  39. import Control.Monad ((>=>))
  40. import Control.Concurrent
  41. import Data.IORef
  42. import Foreign.Store
  43. import Network.Wai.Handler.Warp
  44. import GHC.Word
  45. -- | Start or restart the server.
  46. -- newStore is from foreign-store.
  47. -- A Store holds onto some data across ghci reloads
  48. update :: IO ()
  49. update = do
  50. mtidStore <- lookupStore tidStoreNum
  51. case mtidStore of
  52. -- no server running
  53. Nothing -> do
  54. done <- storeAction doneStore newEmptyMVar
  55. tid <- start done
  56. _ <- storeAction (Store tidStoreNum) (newIORef tid)
  57. return ()
  58. -- server is already running
  59. Just tidStore -> restartAppInNewThread tidStore
  60. where
  61. doneStore :: Store (MVar ())
  62. doneStore = Store 0
  63. -- shut the server down with killThread and wait for the done signal
  64. restartAppInNewThread :: Store (IORef ThreadId) -> IO ()
  65. restartAppInNewThread tidStore = modifyStoredIORef tidStore $ \tid -> do
  66. killThread tid
  67. withStore doneStore takeMVar
  68. readStore doneStore >>= start
  69. -- | Start the server in a separate thread.
  70. start :: MVar () -- ^ Written to when the thread is killed.
  71. -> IO ThreadId
  72. start done = do
  73. (port, site, app) <- getApplicationRepl
  74. forkFinally
  75. (runSettings (setPort port defaultSettings) app)
  76. -- Note that this implies concurrency
  77. -- between shutdownApp and the next app that is starting.
  78. -- Normally this should be fine
  79. (\_ -> putMVar done () >> shutdownApp site)
  80. -- | kill the server
  81. shutdown :: IO ()
  82. shutdown = do
  83. mtidStore <- lookupStore tidStoreNum
  84. case mtidStore of
  85. -- no server running
  86. Nothing -> putStrLn "no Yesod app running"
  87. Just tidStore -> do
  88. withStore tidStore $ readIORef >=> killThread
  89. putStrLn "Yesod app is shutdown"
  90. tidStoreNum :: Word32
  91. tidStoreNum = 1
  92. modifyStoredIORef :: Store (IORef a) -> (a -> IO a) -> IO ()
  93. modifyStoredIORef store f = withStore store $ \ref -> do
  94. v <- readIORef ref
  95. f v >>= writeIORef ref