Потокобезопасное состояние с Warp/WAI
Я хочу написать веб-сервер, который хранит его состояние в State
монада с wai
/warp
, Что-то вроде этого:
{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types
import Control.Monad.State
import Data.ByteString.Lazy.Char8
main = run 3000 app
text x = responseLBS
status200
[("Content-Type", "text/plain")]
x
app req = return $ text "Hello World"
app1 req = modify (+1) >>= return . text . pack . show
-- main1 = runStateT (run 3000 app1) 0
Закомментированная строка не работает, конечно. Цель состоит в том, чтобы хранить счетчик в монаде состояния и отображать его возрастающее значение при каждом запросе.
Кроме того, как я могу получить безопасность потока? Warp запускает мое промежуточное ПО последовательно или параллельно?
Какие варианты доступны для государства - есть ли вообще что-то кроме IORef
Я могу использовать в этом сценарии?
Я понимаю, что государство обеспечивает безопасность, но кажется, что вай не позволяет государству.
Мне нужен только простой однопоточный RPC, который я могу вызвать откуда-то еще. Haxr
Пакет требует отдельного веб-сервера, который является излишним. См. Calling Haskell из Node.JS - у него не было никаких предложений, поэтому я написал простой сервер, используя Wai/Warp и Aeson. Но похоже, что WAI был разработан для поддержки одновременных реализаций, что усложняет ситуацию.
2 ответа
Если ваше взаимодействие с государством может быть выражено с помощью одного звонка atomicModifyIORef
, вы можете использовать это, и вам не нужно явно сериализовать доступ к состоянию.
import Data.IORef
main = do state <- newIORef 42
run 3000 (app' state)
app' :: IORef Int -> Application
app' ref req
= return . text . pack . show `liftM` atomicModifyIORef ref (\st -> (st + 1, st + 1))
Если ваше взаимодействие является более сложным и вам необходимо обеспечить полную сериализацию запросов, вы можете использовать MVar
в сочетании с StateT
,
import Control.Concurrent.MVar
import Control.Monad.State.Strict
main = do state <- newMVar 42
run 3000 (app' state)
app' :: MVar Int -> Application
app' ref request
= do state <- takeMVar ref
(response, newState) <- runStateT (application request) state
putMVar newState --TODO: ensure putMVar happens even if an exception is thrown
return response
application :: Request -> StateT Int (ResourceT IO) Response
application request = modify (+1) >>= return . text . pack . show
Если это в State
Монада, это потокобезопасный дизайн. Параллельные действия ввода-вывода для общего состояния невозможны. Либо это потокобезопасный, либо он не скомпилируется.
Если у вас есть действительно параллельный доступ к общему состоянию как часть вашего проекта (т. Е. Отдельные потоки forkIO обновляют глобальный счетчик), то вам нужно будет использовать и MVar
или же TVar
в STM
монада (или какой-то другой транзакционный барьер) для обеспечения атомарности.