0

Let's say that I have an http server based in Scotty

scottyServer :: IO ()
scottyServer = do
    print ("Starting Server at port " ++ show port)
    scotty port routes

routes :: ScottyM()
routes = do get "/service" responseService
            get "/users" responseUsers

And then I have a circuit breaker which I want to keep state CircuitBreakerType

data CircuitBreakerType
  = Close {users::[User], error:: Integer}
  | Open {users::[User], error:: Integer}
  | HalfOpen {users::[User], error:: Integer}
  deriving (Show)

responseUsers :: ActionM ()
responseUsers = do users <- liftAndCatchIO $ searchAllCassandraUsersCB $ Close [] 0
                   json (show users)

searchAllCassandraUsersCB :: CircuitBreakerType ->  IO CircuitBreakerType
searchAllCassandraUsersCB (Close users errors)= do result <- selectAllCassandraUserCB $ Close users errors
                                                   return result

Using Haskell how can I keep the state of the CircuitBreakerType between request/response of Scootty

All the examples of state machine that I did was passing the state in the IO monad one to another, but with a Http server I dont have a clue how to keep the state. Hopefully nothing related with persistance in database since the performance would suck.

Regards

Jeffrey Chung
  • 19,319
  • 8
  • 34
  • 54
paul
  • 12,873
  • 23
  • 91
  • 153
  • Have you considered use a http session to keep the state? See http://hackage.haskell.org/package/scotty-session – jneira Oct 27 '18 at 18:42
  • I'm afraid for a CircuitBreaker I cannot attach a state to a user, but to a backend. So all request need to share the latest state. – paul Oct 27 '18 at 18:49
  • It would blow my mind if is not other more standard way to keep state between request – paul Oct 27 '18 at 18:51

1 Answers1

2

In situations like this partial application is your friend. Where you might, in C, use a sinful global variable you could instead declare your value (a reference) explicitly in the parent (main) routine then pass that via partial application to your web route handlers.

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Control.Monad.IO.Class
import Data.IORef

port = 8080

scottyServer :: IO ()
scottyServer = do
    print ("Starting Server at port " ++ show port)
    ref <- newState initialState
    scotty port (routes ref)

routes :: MyStateRef -> ScottyM()
routes ref = get "/users" (responseUsers ref)

responseUsers :: MyStateRef -> ActionM ()
responseUsers ref = do x <- statefulStuff ref
                       json (show x)

--------------------------------------------------------------------------------
--  Stateful things

initialState :: Int
initialState = 0
type MyState = Int
type MyStateRef = IORef Int -- Could be TVar, MVar, DB address, etc

newState :: MonadIO m => MyState -> m MyStateRef
newState = liftIO . newIORef

statefulStuff :: MonadIO m => MyStateRef -> m MyState
statefulStuff ref =
 do x <- liftIO (readIORef ref)
    -- N.B. lack of atomicity - that isn't the point of this answer
    let y = x + 1
    y `seq` liftIO (writeIORef ref y)
    pure y
Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
  • I was reading this blog https://github.com/yesodweb/yesod/wiki/Keeping-(in-memory)-state-with-warp which is exactly same example using IOref as you're doing here, thanks a lot for the code base! – paul Oct 27 '18 at 19:21
  • Hi I´m trying to understand this section (let y = x + 1 y `seq` liftIO (writeIORef ref y) pure y) are you updating the ref and returning the request data ? – paul Oct 27 '18 at 20:36
  • Consider use a thread safe mvar instead if you are going to run it in a multithreaded env: https://stackoverflow.com/a/5545599/49554 – jneira Oct 27 '18 at 21:20