Потокобезопасное состояние с 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 монада (или какой-то другой транзакционный барьер) для обеспечения атомарности.

Другие вопросы по тегам