packages feed

websockets-rpc 0.1.1 → 0.2.0

raw patch · 2 files changed

+215/−1 lines, 2 filesdep +hashabledep +uuidPVP ok

version bump matches the API change (PVP)

Dependencies added: hashable, uuid

API changes (from Hackage documentation)

+ Network.WebSockets.RPC.ACKable: ACKable :: UUID -> owner -> Maybe a -> ACKable owner a
+ Network.WebSockets.RPC.ACKable: [ackableData] :: ACKable owner a -> Maybe a
+ Network.WebSockets.RPC.ACKable: [ackableID] :: ACKable owner a -> UUID
+ Network.WebSockets.RPC.ACKable: [ackableOwner] :: ACKable owner a -> owner
+ Network.WebSockets.RPC.ACKable: ackableRPCClient :: forall sub sup rep com m owner. (MonadIO m, Eq owner, Hashable owner) => (forall a. m a -> IO a) -> owner -> RPCClient sub sup rep com m -> m (RPCClient (ACKable owner sub) (ACKable owner sup) (ACKable owner rep) com m)
+ Network.WebSockets.RPC.ACKable: ackableRPCServer :: forall sub sup rep com m owner. (MonadIO m, Eq owner, Hashable owner) => (forall a. m a -> IO a) -> owner -> RPCServer sub sup rep com m -> RPCServer (ACKable owner sub) (ACKable owner sup) (ACKable owner rep) com m
+ Network.WebSockets.RPC.ACKable: data ACKable owner a

Files

+ src/Network/WebSockets/RPC/ACKable.hs view
@@ -0,0 +1,211 @@+{-# LANGUAGE+    RankNTypes+  , ScopedTypeVariables+  , NamedFieldPuns+  #-}++module Network.WebSockets.RPC.ACKable+  ( ackableRPCServer+  , ackableRPCClient+  , ACKable (..)+  ) where++import Network.WebSockets.RPC+import Data.UUID (UUID)+import Data.UUID.V4 (nextRandom)+import qualified Data.HashMap.Lazy as HM+import qualified Data.HashSet as HS+import Data.IORef (newIORef, readIORef, writeIORef)+import Data.Hashable (Hashable)+import Control.Applicative ((<|>))+import Control.Monad (when)+import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Concurrent (threadDelay)+import Control.Concurrent.Async (async)+import qualified Control.Concurrent.Async as Async+import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TVar (newTVarIO, readTVarIO, writeTVar, modifyTVar, TVar)+++data ACKable owner a = ACKable+  { ackableID    :: UUID+  , ackableOwner :: owner+  , ackableData  :: Maybe a -- ^ 'Data.Maybe.Nothing' represents an ACK+  }+++ack :: UUID -> owner -> ACKable owner a+ack ackableID ackableOwner = ACKable+  { ackableID+  , ackableOwner+  , ackableData = Nothing+  }+++++ackableRPCServer :: forall sub sup rep com m owner+                  . ( MonadIO m+                    , Eq owner+                    , Hashable owner+                    )+                 => (forall a. m a -> IO a)+                 -> owner+                 -> RPCServer sub sup rep com m+                 -> RPCServer (ACKable owner sub) (ACKable owner sup) (ACKable owner rep) com m+ackableRPCServer runM serverOwner rpc RPCServerParams{reply,complete} eSubSup = do+  replyMailbox <- liftIO $ newTVarIO HM.empty+  ownerPending <- liftIO $ newTVarIO (HM.empty :: HM.HashMap owner (HS.HashSet UUID))++  let params :: owner -> RPCServerParams rep com m+      params clientOwner = RPCServerParams+        { reply = \r -> do+            ackableID <- liftIO nextRandom+            let op =+                  reply ACKable+                    { ackableID+                    , ackableOwner = serverOwner+                    , ackableData = Just r+                    }+            liftIO $ do+              expBackoff <- mkBackoff (runM op) $ do+                replies <- readTVarIO replyMailbox+                atomically $ do+                  modifyTVar replyMailbox $ HM.delete ackableID+                  modifyTVar ownerPending $ HM.delete clientOwner+                case HM.lookup ackableID replies of+                  Nothing -> pure ()+                  Just (_,expBackoff) -> Async.cancel expBackoff+              atomically $ do+                modifyTVar replyMailbox $ HM.insert ackableID (r, expBackoff)+                modifyTVar ownerPending $ HM.insertWith HS.union clientOwner (HS.singleton ackableID)+            op+        , complete+        }++  case eSubSup of+    Left ACKable{ackableID,ackableOwner,ackableData} ->+      case ackableData of+        Nothing ->+          liftIO $ putStrLn "Somehow received an ACK from a Sub on a server"+        Just sub -> do+          reply (ack ackableID serverOwner)+          rpc (params ackableOwner) (Left sub)+    Right ACKable{ackableID,ackableOwner,ackableData} ->+      case ackableData of+        Nothing -> do+          replies <- liftIO $ readTVarIO replyMailbox+          owners <- liftIO $ readTVarIO ownerPending+          case HM.lookup ackableID replies of+            Nothing -> liftIO $ putStrLn $ "Somehow received an ACK that doesn't exist: " ++ show ackableID+            Just (_,expBackoff) -> liftIO $ do+              Async.cancel expBackoff+              atomically $ do+                writeTVar replyMailbox $ HM.delete ackableID replies+                writeTVar ownerPending $ HM.adjust (HS.delete ackableID) ackableOwner owners++        Just sup -> do+          reply (ack ackableID serverOwner)+          rpc (params ackableOwner) (Right sup)++++ackableRPCClient :: forall sub sup rep com m owner+                  . ( MonadIO m+                    , Eq owner+                    , Hashable owner+                    )+                 => (forall a. m a -> IO a)+                 -> owner+                 -> RPCClient sub sup rep com m+                 -> m (RPCClient (ACKable owner sub) (ACKable owner sup) (ACKable owner rep) com m)+ackableRPCClient runM clientOwner RPCClient{subscription,onSubscribe,onReply,onComplete} = do+  subscriptionMailbox <- liftIO $ newTVarIO HM.empty+  supplyMailbox <- liftIO $ newTVarIO HM.empty+  ownerPending <- liftIO $ newTVarIO (HM.empty :: HM.HashMap owner (HS.HashSet UUID))+++  ackableID <- liftIO nextRandom+  let ackParams :: Maybe owner -> RPCClientParams (ACKable owner sup) m -> RPCClientParams sup m+      ackParams mOwner RPCClientParams{supply,cancel} = RPCClientParams+        { supply = \s -> do+            ackableID <- liftIO nextRandom+            let op =+                  supply ACKable+                    { ackableID+                    , ackableOwner = clientOwner+                    , ackableData = Just s+                    }+            liftIO $ do+              expBackoff <- mkBackoff (runM op) $ do+                subscriptions <- readTVarIO subscriptionMailbox+                supplies <- readTVarIO supplyMailbox+                atomically $ do+                  modifyTVar subscriptionMailbox $ HM.delete ackableID+                  modifyTVar supplyMailbox $ HM.delete ackableID+                  case mOwner of+                    Nothing -> pure ()+                    Just serverOwner -> modifyTVar ownerPending $ HM.delete serverOwner+                case Left <$> HM.lookup ackableID subscriptions <|> Right <$> HM.lookup ackableID supplies of+                  Nothing -> pure ()+                  Just (Left (_,expBackoff)) -> Async.cancel expBackoff+                  Just (Right (_,expBackoff)) -> Async.cancel expBackoff+              atomically $ do+                modifyTVar supplyMailbox $ HM.insert ackableID (s,expBackoff)+                case mOwner of+                  Nothing -> pure ()+                  Just serverOwner -> modifyTVar ownerPending $ HM.insertWith HS.union serverOwner (HS.singleton ackableID)+            op+        , cancel+        }+  pure RPCClient+    { subscription = ACKable+        { ackableID+        , ackableOwner = clientOwner+        , ackableData = Just subscription+        }+    , onSubscribe = onSubscribe . ackParams Nothing+    , onReply = \params ACKable{ackableID,ackableData,ackableOwner} -> case ackableData of+        Nothing -> do+          subscriptions <- liftIO $ readTVarIO subscriptionMailbox+          supplies <- liftIO $ readTVarIO supplyMailbox+          owners <- liftIO $ readTVarIO ownerPending+          case Left <$> HM.lookup ackableID subscriptions <|> Right <$> HM.lookup ackableID supplies of+            Nothing -> pure ()+            Just (Left (_,expBackoff)) -> liftIO $ do+              atomically $ do+                writeTVar subscriptionMailbox $ HM.delete ackableID subscriptions+                writeTVar ownerPending $ HM.adjust (HS.delete ackableID) ackableOwner owners+              Async.cancel expBackoff+            Just (Right (_,expBackoff)) -> liftIO $ do+              atomically $ do+                writeTVar supplyMailbox $ HM.delete ackableID supplies+                writeTVar ownerPending $ HM.adjust (HS.delete ackableID) ackableOwner owners+              Async.cancel expBackoff+        Just rep -> do+          (supply params) (ack ackableID clientOwner)+          onReply (ackParams (Just ackableOwner) params) rep+    , onComplete+    }+++++second = 1000000+minute = 60 * second+hour = 60 * minute+day = 24 * hour+week = 7 * day++mkBackoff op x = do+  spentWaiting <- newIORef (0 :: Int)+  async $ do+    toWait <- readIORef spentWaiting+    writeIORef spentWaiting (toWait + 1)+    let toWait' = 2 ^ toWait+        soFar = sum $ (\x -> (2 ^ x) * second) <$> [0..toWait]++    when (soFar > week) x++    threadDelay $ second * (toWait' + 10)+    op
websockets-rpc.cabal view
@@ -1,5 +1,5 @@ Name:                   websockets-rpc-Version:                0.1.1+Version:                0.2.0 Author:                 Athan Clark <athan.clark@gmail.com> Maintainer:             Athan Clark <athan.clark@gmail.com> License:                BSD3@@ -24,6 +24,7 @@   GHC-Options:          -Wall   Exposed-Modules:      Network.WebSockets.RPC                         Network.WebSockets.RPC.Types+                        Network.WebSockets.RPC.ACKable                         Network.WebSockets.RPC.Trans.Client                         Network.WebSockets.RPC.Trans.Server   Build-Depends:        base >= 4.8 && < 5@@ -32,12 +33,14 @@                       , bytestring                       , containers                       , exceptions+                      , hashable                       , mtl                       , QuickCheck                       , stm                       , text                       , transformers                       , unordered-containers+                      , uuid                       , wai-transformers                       , websockets