Поточно-безопасное состояние с Warp / WAI

Я хочу написать веб-сервер, который хранит свое состояние в монаде State с _2 _ / _ 3_. Что-то вроде этого:

{-# 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 требует отдельного веб-сервера, что является излишним. См. Вызов Haskell из Node.JS - никаких предложений не было, поэтому я написал простой сервер с использованием Wai / Warp и Эсон. Но похоже, что WAI был разработан для поддержки параллельных реализаций, поэтому это усложняет ситуацию.


person nponeccop    schedule 04.05.2012    source источник


Ответы (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
person dave4420    schedule 04.05.2012
comment
Первый дает Precedence parsing error cannot mix '.' [infixr 9] and 'liftM' [infixl 9] in the same infix expression. Во втором сообщается о неоднозначном использовании StateT и runStateT; после комментирования import Control.Monad.State он сообщает Not in scope: type constructor or class 'ResourceT'; после importing Control.Monad.Trans.Resource он сообщает Couldn't match expected type 'ResourceT IO t0' - person Inaimathi; 12.04.2013

Если он находится в монаде State, он изначально ориентирован на многопоточность. Одновременные действия ввода-вывода для общего состояния невозможны. Либо он потокобезопасен, либо не компилируется.

Если у вас есть действительно параллельный доступ к общему состоянию как часть вашего дизайна (т.е. отдельные потоки forkIO, обновляющие глобальный счетчик), вам необходимо использовать и _ 2_ или _ 3_ в STM монада (или какой-либо другой транзакционный барьер) для обеспечения атомарности.

person Don Stewart    schedule 04.05.2012
comment
Я хочу избежать по-настоящему параллельного доступа к общему состоянию, если это возможно. Я хочу обслуживать запросы поочередно. - person nponeccop; 04.05.2012