packages feed

eventstore 1.3.3 → 1.4.0

raw patch · 35 files changed

+1545/−1968 lines, 35 filesdep −ekg-corePVP ok

version bump matches the API change (PVP)

Dependencies removed: ekg-core

API changes (from Hackage documentation)

- Database.EventStore: OperationMaxAttemptReached :: UUID -> Command -> OperationMaxAttemptReached
- Database.EventStore: data OperationMaxAttemptReached
- Database.EventStore: data SubscriptionId
- Database.EventStore: getSubscriptionDetailsSTM :: Subscription s => s -> STM SubDetails
- Database.EventStore: getSubscriptionId :: Subscription s => s -> IO SubscriptionId
- Database.EventStore: hasCaughtUp :: CatchupSubscription t -> IO Bool
- Database.EventStore: hasCaughtUpSTM :: CatchupSubscription t -> STM Bool
- Database.EventStore: nextEvent :: Subscription s => s -> IO ResolvedEvent
- Database.EventStore: nextEventMaybe :: Subscription s => s -> IO (Maybe ResolvedEvent)
- Database.EventStore: nextEventMaybeSTM :: Subscription s => s -> STM (Maybe ResolvedEvent)
- Database.EventStore: unsubscribeConfirmed :: Subscription s => s -> IO Bool
- Database.EventStore: unsubscribeConfirmedSTM :: Subscription s => s -> STM Bool
- Database.EventStore: waitConfirmation :: Subscription s => s -> IO ()
- Database.EventStore: waitTillCatchup :: CatchupSubscription t -> IO ()
- Database.EventStore: waitUnsubscribeConfirmed :: Subscription s => s -> IO ()
+ Database.EventStore: Confirmed :: SubDetails -> SubAction
+ Database.EventStore: ConnectionHasDropped :: OperationError
+ Database.EventStore: Dropped :: SubDropReason -> SubAction
+ Database.EventStore: MonitoringBackend :: IO () -> IO () -> (Int -> IO ()) -> IO () -> IO () -> MonitoringBackend
+ Database.EventStore: Submit :: ResolvedEvent -> SubAction
+ Database.EventStore: [monitoringBackendAddDataTransmitted] :: MonitoringBackend -> Int -> IO ()
+ Database.EventStore: [monitoringBackendIncrConnectionDrop] :: MonitoringBackend -> IO ()
+ Database.EventStore: [monitoringBackendIncrForceReconnect] :: MonitoringBackend -> IO ()
+ Database.EventStore: [monitoringBackendIncrHeartbeatTimeouts] :: MonitoringBackend -> IO ()
+ Database.EventStore: [monitoringBackendIncrPkgCount] :: MonitoringBackend -> IO ()
+ Database.EventStore: data MonitoringBackend
+ Database.EventStore: data SubAction
+ Database.EventStore: nextSubEvent :: Subscription s => s -> IO SubAction
+ Database.EventStore: noopMonitoringBackend :: MonitoringBackend
+ Database.EventStore: streamSubEvents :: Subscription s => s -> Stream (Of SubAction) IO ()
+ Database.EventStore: streamSubResolvedEvents :: Subscription s => s -> Stream (Of ResolvedEvent) IO ()
- Database.EventStore: AccessDenied :: StreamId t -> OperationError
+ Database.EventStore: AccessDenied :: !StreamId t -> OperationError
- Database.EventStore: InvalidOperation :: Text -> OperationError
+ Database.EventStore: InvalidOperation :: !Text -> OperationError
- Database.EventStore: InvalidServerResponse :: Command -> Command -> OperationError
+ Database.EventStore: InvalidServerResponse :: !Command -> !Command -> OperationError
- Database.EventStore: ProtobufDecodingError :: String -> OperationError
+ Database.EventStore: ProtobufDecodingError :: !String -> OperationError
- Database.EventStore: ServerError :: Maybe Text -> OperationError
+ Database.EventStore: ServerError :: !Maybe Text -> OperationError
- Database.EventStore: Settings :: NominalDiffTime -> NominalDiffTime -> Bool -> Retry -> NominalDiffTime -> Maybe TLSSettings -> LogType -> LoggerFilter -> Bool -> NominalDiffTime -> Retry -> Maybe Store -> Maybe Text -> Maybe Credentials -> Settings
+ Database.EventStore: Settings :: NominalDiffTime -> NominalDiffTime -> Bool -> Retry -> NominalDiffTime -> Maybe TLSSettings -> LogType -> LoggerFilter -> Bool -> NominalDiffTime -> Retry -> MonitoringBackend -> Maybe Text -> Maybe Credentials -> Settings
- Database.EventStore: StreamDeleted :: StreamName -> OperationError
+ Database.EventStore: StreamDeleted :: !StreamName -> OperationError
- Database.EventStore: StreamNotFound :: StreamName -> OperationError
+ Database.EventStore: StreamNotFound :: !StreamName -> OperationError
- Database.EventStore: WrongExpectedVersion :: Text -> ExpectedVersion -> OperationError
+ Database.EventStore: WrongExpectedVersion :: !Text -> !ExpectedVersion -> OperationError
- Database.EventStore: [s_monitoring] :: Settings -> Maybe Store
+ Database.EventStore: [s_monitoring] :: Settings -> MonitoringBackend

Files

CHANGELOG.markdown view
@@ -1,3 +1,10 @@+1.4.0+-----+* Internal: Refactor operation and registry tcp internals.+* Introduces `MonitoringBackend` to stop relying exclusively on `ekg-core`.+* Supports GHC 8.8.*.+* Exposes a stream processing interface for subscriptions.+ 1.3.3 ----- * Add `Pinned` system consumer strategy.
Database/EventStore.hs view
@@ -52,8 +52,6 @@     , eventNumber     , rawEventNumber     , eventNumberToInt64-     -- * Common Operation types-    , OperationMaxAttemptReached(..)      -- * Read Operations     , StreamMetadataResult(..)     , BatchResult@@ -110,30 +108,21 @@     , transactionWrite       -- * Subscription     , SubscriptionClosed(..)-    , SubscriptionId     , SubscriptionStream(..)     , Subscription     , SubDropReason(..)     , SubDetails-    , waitConfirmation-    , unsubscribeConfirmed-    , unsubscribeConfirmedSTM-    , waitUnsubscribeConfirmed-    , nextEventMaybeSTM-    , getSubscriptionDetailsSTM+    , SubAction(..)     , unsubscribe+    , nextSubEvent+    , streamSubEvents+    , streamSubResolvedEvents       -- * Volatile Subscription     , RegularSubscription     , subscribe-    , getSubscriptionId-    , nextEvent-    , nextEventMaybe       -- * Catch-up Subscription     , CatchupSubscription     , subscribeFrom-    , waitTillCatchup-    , hasCaughtUp-    , hasCaughtUpSTM      -- * Persistent Subscription     , PersistentSubscription     , PersistentSubscriptionSettings(..)@@ -184,6 +173,9 @@     , LogLevel(..)     , LogType(..)     , LoggerFilter(..)+      -- * Monitoring+    , MonitoringBackend(..)+    , noopMonitoringBackend       -- * Misc     , Command     , DropReason(..)@@ -226,5 +218,4 @@ import           Database.EventStore.Internal.Subscription.Persistent import           Database.EventStore.Internal.Subscription.Types import           Database.EventStore.Internal.Subscription.Regular-import           Database.EventStore.Internal.Manager.Operation.Registry import           Database.EventStore.Internal.Types
Database/EventStore/Internal.hs view
@@ -23,7 +23,6 @@ import Data.Maybe  ---------------------------------------------------------------------------------import           Database.EventStore.Internal.Callback import           Database.EventStore.Internal.Communication import           Database.EventStore.Internal.Connection (connectionBuilder) import           Database.EventStore.Internal.Control hiding (subscribe)@@ -121,11 +120,8 @@            -> [Event]            -> Maybe Credentials            -> IO (Async WriteResult)-sendEvents Connection{..} evt_stream exp_ver evts cred = do-    p <- newPromise-    let op = Op.writeEvents _settings (streamIdRaw evt_stream) exp_ver cred evts-    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+sendEvents Connection{..} evt_stream exp_ver evts cred =+    Op.writeEvents _settings _exec (streamIdRaw evt_stream) exp_ver cred evts  -------------------------------------------------------------------------------- -- | Deletes given stream.@@ -135,11 +131,8 @@              -> Maybe Bool       -- ^ Hard delete              -> Maybe Credentials              -> IO (Async Op.DeleteResult)-deleteStream Connection{..} evt_stream exp_ver hard_del cred = do-    p <- newPromise-    let op = Op.deleteStream _settings (streamIdRaw evt_stream) exp_ver hard_del cred-    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+deleteStream Connection{..} stream expVer hardDel cred =+    Op.deleteStream _settings _exec (streamIdRaw stream) expVer hardDel cred  -------------------------------------------------------------------------------- -- | Represents a multi-request transaction with the EventStore.@@ -170,11 +163,9 @@                  -> Maybe Credentials                  -> IO (Async Transaction) startTransaction conn@Connection{..} evt_stream exp_ver cred = do-    p <- newPromise-    let op = Op.transactionStart _settings (streamIdRaw evt_stream) exp_ver cred-    publishWith _exec (SubmitOperation p op)+    as <- Op.transactionStart _settings _exec (streamIdRaw evt_stream) exp_ver cred     async $ do-        tid <- retrieve p+        tid <- wait as         return Transaction                { _tStream  = streamIdRaw evt_stream                , _tTransId = TransactionId tid@@ -189,23 +180,17 @@                  -> Maybe Credentials                  -> IO (Async ()) transactionWrite Transaction{..} evts cred = do-    p <- newPromise     let Connection{..} = _tConn         raw_id = _unTransId _tTransId-        op     = Op.transactionWrite _settings _tStream _tExpVer raw_id evts cred-    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+    Op.transactionWrite _settings _exec _tStream _tExpVer raw_id evts cred  -------------------------------------------------------------------------------- -- | Asynchronously commits this transaction. transactionCommit :: Transaction -> Maybe Credentials -> IO (Async WriteResult) transactionCommit Transaction{..} cred = do-    p <- newPromise     let Connection{..} = _tConn         raw_id = _unTransId _tTransId-        op     = Op.transactionCommit _settings _tStream _tExpVer raw_id cred-    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+    Op.transactionCommit _settings _exec _tStream _tExpVer raw_id cred  -------------------------------------------------------------------------------- -- | There isn't such of thing in EventStore parlance. Basically, if you want to@@ -221,13 +206,10 @@           -> ResolveLink           -> Maybe Credentials           -> IO (Async (ReadResult EventNumber Op.ReadEvent))-readEvent Connection{..} stream_id evtNum resLinkTos cred = do-    p <- newPromise-    let evt_num = eventNumberToInt64 evtNum-        res_link_tos = resolveLinkToBool resLinkTos-        op = Op.readEvent _settings (streamIdRaw stream_id) evt_num res_link_tos cred-    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+readEvent Connection{..} stream evtNum resLinkTos cred = do+    let evtNumRaw = eventNumberToInt64 evtNum+        linkTos = resolveLinkToBool resLinkTos+    Op.readEvent _settings _exec (streamIdRaw stream) evtNumRaw linkTos cred  -------------------------------------------------------------------------------- -- | When batch-reading a stream, this type-level function maps the result you@@ -271,20 +253,15 @@                  -> Maybe Credentials                  -> IO (Async (BatchResult t)) readEventsCommon Connection{..} dir streamId start cnt resLinkTos cred = do-    p <- newPromise     let res_link_tos = resolveLinkToBool resLinkTos-        op =-            case streamId of-                StreamName{} ->-                    let name   = streamIdRaw streamId-                        evtNum = eventNumberToInt64 start in-                    Op.readStreamEvents _settings dir name evtNum cnt res_link_tos cred-                All ->-                    let Position c_pos p_pos = start in-                    Op.readAllEvents _settings c_pos p_pos cnt res_link_tos dir cred--    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+    case streamId of+        StreamName{} ->+            let name   = streamIdRaw streamId+                evtNum = eventNumberToInt64 start in+            Op.readStreamEvents _settings _exec dir name evtNum cnt res_link_tos cred+        All ->+            let Position c_pos p_pos = start in+            Op.readAllEvents _settings _exec c_pos p_pos cnt res_link_tos dir cred  -------------------------------------------------------------------------------- -- | Subscribes to a stream.@@ -340,11 +317,8 @@                   -> Maybe Credentials                   -> IO (Async WriteResult) setStreamMetadata Connection{..} evt_stream exp_ver metadata cred = do-    p <- newPromise     let name = streamIdRaw evt_stream-        op = Op.setMetaStream _settings name exp_ver cred metadata-    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+    Op.setMetaStream _settings _exec name exp_ver cred metadata  -------------------------------------------------------------------------------- -- | Asynchronously gets the metadata of a stream.@@ -352,11 +326,8 @@                   -> StreamName                   -> Maybe Credentials                   -> IO (Async StreamMetadataResult)-getStreamMetadata Connection{..} evt_stream cred = do-    p <- newPromise-    let op = Op.readMetaStream _settings (streamIdRaw evt_stream) cred-    publishWith _exec (SubmitOperation p op)-    async (retrieve p)+getStreamMetadata Connection{..} stream cred =+    Op.readMetaStream _settings _exec (streamIdRaw stream) cred  -------------------------------------------------------------------------------- -- | Asynchronously create a persistent subscription group on a stream.@@ -366,11 +337,8 @@                              -> PersistentSubscriptionSettings                              -> Maybe Credentials                              -> IO (Async (Maybe PersistActionException))-createPersistentSubscription Connection{..} grp stream sett cred = do-    p <- newPromise-    let op = Op.createPersist grp (streamIdRaw stream) sett cred-    publishWith _exec (SubmitOperation p op)-    async (persistAsync p)+createPersistentSubscription Connection{..} grp stream sett cred =+    Op.createPersist _exec grp (streamIdRaw stream) sett cred  -------------------------------------------------------------------------------- -- | Asynchronously update a persistent subscription group on a stream.@@ -380,11 +348,8 @@                              -> PersistentSubscriptionSettings                              -> Maybe Credentials                              -> IO (Async (Maybe PersistActionException))-updatePersistentSubscription Connection{..} grp stream sett cred = do-    p <- newPromise-    let op = Op.updatePersist grp (streamIdRaw stream) sett cred-    publishWith _exec (SubmitOperation p op)-    async (persistAsync p)+updatePersistentSubscription Connection{..} grp stream sett cred =+    Op.updatePersist _exec grp (streamIdRaw stream) sett cred  -------------------------------------------------------------------------------- -- | Asynchronously delete a persistent subscription group on a stream.@@ -393,11 +358,8 @@                              -> StreamName                              -> Maybe Credentials                              -> IO (Async (Maybe PersistActionException))-deletePersistentSubscription Connection{..} grp stream cred = do-    p <- newPromise-    let op = Op.deletePersist grp (streamIdRaw stream) cred-    publishWith _exec (SubmitOperation p op)-    async (persistAsync p)+deletePersistentSubscription Connection{..} grp stream cred =+    Op.deletePersist _exec grp (streamIdRaw stream) cred  -------------------------------------------------------------------------------- -- | Asynchronously connect to a persistent subscription given a group on a@@ -410,8 +372,3 @@                                 -> IO PersistentSubscription connectToPersistentSubscription Connection{..} group stream bufSize cred =     newPersistentSubscription _exec group stream bufSize cred-----------------------------------------------------------------------------------persistAsync :: Callback (Maybe PersistActionException)-             -> IO (Maybe PersistActionException)-persistAsync = either throw return <=< tryRetrieve
− Database/EventStore/Internal/Callback.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE GADTs               #-}-{-# LANGUAGE RankNTypes          #-}-{-# LANGUAGE ScopedTypeVariables #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Callback--- Copyright : (C) 2017 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable-------------------------------------------------------------------------------------module Database.EventStore.Internal.Callback-  ( Callback-  , newPromise-  , newCallback-  , fulfill-  , reject-  , retrieve-  , tryRetrieve-  , fromEither-  ) where-----------------------------------------------------------------------------------import Database.EventStore.Internal.Prelude-----------------------------------------------------------------------------------newtype Callback a =-  Callback { runCallback :: forall b. Stage a b -> IO b }-----------------------------------------------------------------------------------data Stage a b where-  Fulfill  :: a -> Stage a ()-  Reject   :: Exception e => e -> Stage a ()-  Retrieve :: Stage a (Either SomeException a)-----------------------------------------------------------------------------------fulfill :: MonadIO m => Callback a -> a -> m ()-fulfill cb a = liftIO $ runCallback cb (Fulfill a)-----------------------------------------------------------------------------------reject :: (Exception e, MonadIO m) => Callback a -> e -> m ()-reject cb e = liftIO $ runCallback cb (Reject e)-----------------------------------------------------------------------------------tryRetrieve :: Callback a -> IO (Either SomeException a)-tryRetrieve cb = runCallback cb Retrieve-----------------------------------------------------------------------------------retrieve :: Callback a -> IO a-retrieve p = do-  tryRetrieve p >>= \case-    Left e  -> throw e-    Right a -> return a-----------------------------------------------------------------------------------fromEither :: Exception e => Callback a -> Either e a -> IO ()-fromEither p (Left e)  = reject p e-fromEither p (Right a) = fulfill p a-----------------------------------------------------------------------------------newPromise :: IO (Callback a)-newPromise = do-  mvar <- newEmptyTMVarIO-  return $ promise mvar-----------------------------------------------------------------------------------newCallback :: (Either SomeException a -> IO ()) -> IO (Callback a)-newCallback k = do-  mvar <- newEmptyTMVarIO-  return $ callback mvar k-----------------------------------------------------------------------------------promise :: forall a. TMVar (Either SomeException a) -> Callback a-promise mvar = Callback go-  where-    go :: forall b. Stage a b -> IO b-    go (Fulfill a) = atomically $-      whenM (isEmptyTMVar mvar) $-        putTMVar mvar (Right a)--    go (Reject e) = atomically $-      whenM (isEmptyTMVar mvar) $-        putTMVar mvar (Left $ toException e)--    go Retrieve = atomically $ readTMVar mvar-----------------------------------------------------------------------------------callback :: forall a. TMVar (Either SomeException a)-         -> (Either SomeException a -> IO ())-         -> Callback a-callback mvar k = Callback go-  where-    go :: forall b. Stage a b -> IO b-    go (Fulfill a) = do-      atomically $-        unlessM (tryPutTMVar mvar (Right a)) $ do-          _ <- swapTMVar mvar (Right a)-          return ()-      k (Right a)-    go (Reject e) = do-      let err = Left $ toException e-      atomically $ do-        unlessM (tryPutTMVar mvar err) $ do-          _ <- swapTMVar mvar err--          return ()-      k err-    go Retrieve = atomically $ readTMVar mvar
Database/EventStore/Internal/Communication.hs view
@@ -18,8 +18,7 @@ import Data.Typeable  ---------------------------------------------------------------------------------import Database.EventStore.Internal.Callback-import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Operation (Mailbox, Lifetime) import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types @@ -51,11 +50,6 @@   deriving Typeable  ---------------------------------------------------------------------------------data SubmitOperation =-  forall a. SubmitOperation (Callback a) (Operation a)-  deriving Typeable---------------------------------------------------------------------------------- data ServiceTerminated = ServiceTerminated Service deriving Typeable  --------------------------------------------------------------------------------@@ -65,3 +59,6 @@  -------------------------------------------------------------------------------- newtype SendPackage = SendPackage Package deriving Typeable++--------------------------------------------------------------------------------+data Transmit = Transmit Mailbox Lifetime Package deriving Typeable
Database/EventStore/Internal/ConnectionManager.hs view
@@ -25,7 +25,6 @@ import Data.Time  ---------------------------------------------------------------------------------import           Database.EventStore.Internal.Callback import           Database.EventStore.Internal.Command import           Database.EventStore.Internal.Communication import           Database.EventStore.Internal.Connection@@ -36,7 +35,7 @@ import           Database.EventStore.Internal.Operation import           Database.EventStore.Internal.Operation.Authenticate (newAuthenticatePkg) import           Database.EventStore.Internal.Operation.Identify (newIdentifyPkg)-import qualified Database.EventStore.Internal.OperationManager as Operation+import qualified Database.EventStore.Internal.Manager.Operation.Registry as Operation import           Database.EventStore.Internal.Prelude import           Database.EventStore.Internal.Stopwatch import           Database.EventStore.Internal.Types@@ -70,7 +69,7 @@   Init -> False   Connected{} -> True   Closed -> True-  Connecting a s ->+  Connecting _ s ->     case s of       Reconnecting -> False       EndpointDiscovery -> False@@ -156,7 +155,7 @@            , _stage         :: IORef Stage            , _last          :: IORef (Maybe EndPoint)            , _sending       :: TVar Bool-           , _opMgr         :: Operation.Manager+           , _opMgr         :: Operation.Registry            , _stopwatch     :: Stopwatch            , _lastCheck     :: IORef NominalDiffTime            , _lastConnected :: IORef Bool@@ -171,20 +170,20 @@   monitorIncrPkgCount  ---------------------------------------------------------------------------------connectionManager :: ConnectionBuilder+connectionManager :: Settings+                  -> ConnectionBuilder                   -> Discovery                   -> Hub                   -> IO ()-connectionManager builder disc mainBus = do+connectionManager setts builder disc mainBus = do   stageRef <- newIORef Init   let mkInternal = Internal disc builder stageRef-      connRef    = ConnectionRef $ lookingUpConnectionWhenConnected stageRef    stopwatch    <- newStopwatch   timeoutCheck <- stopwatchElapsed stopwatch   internal <- mkInternal <$> newIORef Nothing                          <*> newTVarIO False-                         <*> Operation.new connRef+                         <*> Operation.registryNew (s_operationTimeout setts) (s_operationRetry setts)                          <*> return stopwatch                          <*> newIORef timeoutCheck                          <*> newIORef False@@ -195,7 +194,7 @@   subscribe mainBus (onEstablish internal)   subscribe mainBus (onEstablished internal)   subscribe mainBus (onArrived internal)-  subscribe mainBus (onSubmitOperation internal)+  subscribe mainBus (onTransmit internal)   subscribe mainBus (onConnectionError internal)   subscribe mainBus (onConnectionClosed internal)   subscribe mainBus (onCloseConnection internal)@@ -318,7 +317,8 @@       -- ms. This could lead the first operation to take time before gettings.       -- FIXME: We might consider doing that hack only if it's the first time       -- we connect with the server.-      Operation.check _opMgr+      pkgs <- Operation.registryCheckAndRetry _opMgr (connectionId conn)+      traverse_ (enqueuePackage conn) pkgs     _ -> pure ()  --------------------------------------------------------------------------------@@ -330,7 +330,7 @@ closeConnection self@Internal{..} cause = do   $logDebug [i|CloseConnection: #{cause}.|]   mConn <- lookupConnectionAndSwitchToClosed self-  Operation.cleanup _opMgr+  Operation.registryAbort _opMgr   traverse_ (closeTcpConnection self cause) mConn   $logInfo [i|CloseConnection: connection cleanup done for [#{cause}].|]   publish (FatalException cause)@@ -423,12 +423,15 @@      (defaultConnecting -> True) -> manageHeartbeats self -    Connected _ -> do+    Connected conn -> do       elapsed           <- stopwatchElapsed _stopwatch       timeoutCheckStart <- readIORef _lastCheck        when (elapsed - timeoutCheckStart >= s_operationTimeout setts) $ do-        Operation.check _opMgr+        $logDebug "Start check and retry..."+        pkgs <- Operation.registryCheckAndRetry _opMgr (connectionId conn)+        traverse_ (enqueuePackage conn) pkgs+        $logDebug "Completed check and retry"         atomicWriteIORef _lastCheck elapsed      _ -> return ()@@ -561,12 +564,9 @@       | packageCmd == heartbeatRequestCmd =         enqueuePackage conn heartbeatResponse       | otherwise =-        Operation.handle _opMgr pkg >>= \case-          Nothing       -> $logWarn [i|Package not handled: #{pkg}|]-          Just decision ->-            case decision of-              Operation.Handled        -> return ()-              Operation.Reconnect node -> forceReconnect self node+        Operation.registryHandle _opMgr pkg >>= \case+          Nothing -> pure ()+          Just node -> forceReconnect self node      closedOrInit = \case       Init -> True@@ -605,17 +605,21 @@ onShutdown self@Internal{..} _ = do   $logDebug "Shutting down..."   mConn <- lookupConnectionAndSwitchToClosed self-  Operation.cleanup _opMgr+  Operation.registryAbort _opMgr   traverse_ dispose mConn   $logDebug "Shutdown properly."   publish (ServiceTerminated ConnectionManager)  ---------------------------------------------------------------------------------onSubmitOperation :: Internal -> SubmitOperation -> EventStore ()-onSubmitOperation Internal{..} (SubmitOperation callback op) =+onTransmit :: Internal -> Transmit -> EventStore ()+onTransmit Internal{..} (Transmit m lifetime pkg) =   readIORef _stage >>= \case-    Closed -> reject callback Aborted-    _      -> Operation.submit _opMgr op callback+    Closed+      -> mailboxFail m Aborted+    Connected conn+      -> do enqueuePackage conn pkg+            Operation.registryRegister _opMgr (connectionId conn) lifetime pkg m+    _ -> Operation.registryPostpone _opMgr m lifetime pkg  -------------------------------------------------------------------------------- onCloseConnection :: Internal -> CloseConnection -> EventStore ()@@ -637,13 +641,6 @@         Identification _ _ conn -> Just conn         _ -> Nothing     go _ = Nothing-----------------------------------------------------------------------------------lookingUpConnectionWhenConnected :: IORef Stage -> EventStore (Maybe Connection)-lookingUpConnectionWhenConnected = fmap go . readIORef-  where-    go (Connected conn) = Just conn-    go _                = Nothing  -------------------------------------------------------------------------------- onSendPackage :: Internal -> SendPackage -> EventStore ()
Database/EventStore/Internal/Control.hs view
@@ -66,9 +66,6 @@ import Control.Monad.Reader import Data.UUID import Data.UUID.V4-import System.Metrics-import System.Metrics.Counter hiding (add)-import System.Metrics.Distribution  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Logger@@ -77,10 +74,10 @@  -------------------------------------------------------------------------------- data Env =-  Env { __logRef   :: LoggerRef-      , __settings :: Settings-      , __bus      :: Bus-      , __monitor  :: Maybe Monitoring+  Env { __logRef   :: !LoggerRef+      , __settings :: !Settings+      , __bus      :: !Bus+      , __monitor  :: !MonitoringBackend       }  --------------------------------------------------------------------------------@@ -107,7 +104,7 @@ getSettings = __settings <$> getEnv  ---------------------------------------------------------------------------------freshUUID :: EventStore UUID+freshUUID :: MonadIO m => m UUID freshUUID = liftIO nextRandom  --------------------------------------------------------------------------------@@ -302,7 +299,7 @@       , _busEventHandlers  :: IORef EventHandlers       , _busQueue          :: TBMQueue Message       , _workerAsync       :: Async ()-      , _monitoring        :: Maybe Monitoring+      , _monitoring        :: MonitoringBackend       }  --------------------------------------------------------------------------------@@ -324,7 +321,7 @@     Bus ref setts <$> newIORef mempty                   <*> newTBMQueueIO 500                   <*> async (worker b)-                  <*> traverse configureMonitoring (s_monitoring setts)+                  <*> pure (s_monitoring setts)    return bus @@ -375,55 +372,33 @@ -------------------------------------------------------------------------------- -- Monitoring ---------------------------------------------------------------------------------data Monitoring =-  Monitoring-  { _pkgCount     :: Counter-  , _connDrops    :: Counter-  , _dataTx       :: Distribution-  , _forceReco    :: Counter-  , _heartTimeout :: Counter-  }  ---------------------------------------------------------------------------------configureMonitoring :: Store -> IO Monitoring-configureMonitoring store =-  Monitoring <$> createCounter "eventstore.packages.received" store-             <*> createCounter "eventstore.connection.drops" store-             <*> createDistribution "eventstore.data.transmitted" store-             <*> createCounter "eventstore.force_reconnect" store-             <*> createCounter "eventstore.heartbeat.timeouts" store---------------------------------------------------------------------------------- monitorIncrPkgCount :: EventStore () monitorIncrPkgCount = do   Env{..} <- getEnv-  for_ __monitor  $ \Monitoring{..}->-    liftIO $ inc _pkgCount+  liftIO $ monitoringBackendIncrPkgCount __monitor  -------------------------------------------------------------------------------- monitorIncrConnectionDrop :: EventStore () monitorIncrConnectionDrop = do   Env{..} <- getEnv-  for_ __monitor  $ \Monitoring{..}->-    liftIO $ inc _connDrops+  liftIO $ monitoringBackendIncrConnectionDrop __monitor  -------------------------------------------------------------------------------- monitorAddDataTransmitted :: Int -> EventStore () monitorAddDataTransmitted siz = do   Env{..} <- getEnv-  for_ __monitor  $ \Monitoring{..}->-    liftIO $ add _dataTx (fromIntegral siz)+  liftIO $ monitoringBackendAddDataTransmitted __monitor siz  -------------------------------------------------------------------------------- monitorIncrForceReconnect :: EventStore () monitorIncrForceReconnect = do   Env{..} <- getEnv-  for_ __monitor  $ \Monitoring{..}->-    liftIO $ inc _forceReco+  liftIO $ monitoringBackendIncrForceReconnect __monitor  -------------------------------------------------------------------------------- monitorIncrHeartbeatTimeouts :: EventStore () monitorIncrHeartbeatTimeouts = do   Env{..} <- getEnv-  for_ __monitor  $ \Monitoring{..}->-    liftIO $ inc _heartTimeout+  liftIO $ monitoringBackendIncrHeartbeatTimeouts __monitor
Database/EventStore/Internal/Discovery.hs view
@@ -34,7 +34,7 @@     ) where  ---------------------------------------------------------------------------------import Prelude (String)+import Prelude (String, fail) import Data.Maybe  --------------------------------------------------------------------------------
Database/EventStore/Internal/Exec.hs view
@@ -108,7 +108,7 @@       hub      = asHub mainBus    timerService hub-  connectionManager builder disc hub+  connectionManager setts builder disc hub    subscribe mainBus (onInit internal)   subscribe mainBus (onInitFailed internal)
Database/EventStore/Internal/Manager/Operation/Registry.hs view
@@ -1,437 +1,389 @@-{-# LANGUAGE DeriveDataTypeable        #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE RecordWildCards           #-}-{-# LANGUAGE TypeFamilies              #-} -------------------------------------------------------------------------------- -- |--- Module : Database.EventStore.Internal.Manager.Operation.Registry--- Copyright : (C) 2017 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable+-- Module    :  Database.EventStore.Internal.Manager.Registry+-- Copyright :  (C) 2020 Yorick Laupa+-- License   :  (see the file LICENSE)+-- Maintainer:  Yorick Laupa <yo.eight@gmail.com>+-- Stability :  experimental+-- Portability: non-portable ----- Main operation bookkeeping structure. ---------------------------------------------------------------------------------module Database.EventStore.Internal.Manager.Operation.Registry-    ( Registry-    , OperationMaxAttemptReached(..)-    , Decision(..)-    , newRegistry-    , register-    , handlePackage-    , abortPendingRequests-    , checkAndRetry-    , startAwaitings-    ) where+module Database.EventStore.Internal.Manager.Operation.Registry where  --------------------------------------------------------------------------------+import qualified Data.HashMap.Strict as HashMap++-------------------------------------------------------------------------------- import Data.ProtocolBuffers import Data.Serialize-import Data.Time-import Data.UUID-import Data.UUID.V4-import Data.Void (absurd)  ---------------------------------------------------------------------------------import Database.EventStore.Internal.Callback import Database.EventStore.Internal.Command import Database.EventStore.Internal.Control-import Database.EventStore.Internal.Connection import Database.EventStore.Internal.EndPoint import Database.EventStore.Internal.Logger-import Database.EventStore.Internal.Operation hiding (retry) import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Stopwatch import Database.EventStore.Internal.Types  -------------------------------------------------------------------------------- data Request =-  Request { _requestCmd     :: !Command-          , _requestPayload :: !ByteString-          , _requestCred    :: !(Maybe Credentials)-          }-----------------------------------------------------------------------------------data Suspend a-  = Required !(Package -> Operation a)-  | Optional !(Maybe Package -> Operation a)-  | Resolved !(Operation a)-----------------------------------------------------------------------------------type SessionId  = Integer-type SessionMap = HashMap SessionId Session-----------------------------------------------------------------------------------data Session =-  forall result.-  Session { sessionId       :: !SessionId-          , sessionOp       :: !(Operation result)-          , sessionStack    :: !(IORef (Suspend result))-          , sessionCallback :: !(Callback result)-          }-----------------------------------------------------------------------------------rejectSession :: Exception e => Session -> e -> EventStore ()-rejectSession Session{..} = reject sessionCallback+  Request+  { requestOriginal :: !Package+  , requestConnId :: !UUID+  , requestRetries :: !Int+  , requestStarted :: !NominalDiffTime+  , requestMailbox :: !Mailbox+  , requestLifetime :: !Lifetime+  }  ---------------------------------------------------------------------------------resumeSession :: Registry -> Session -> Package -> EventStore ()-resumeSession reg session@Session{..} pkg = do-  atomicModifyIORef' sessionStack $ \case-      Required k -> (Resolved $ k pkg, ())-      Optional k -> (Resolved $ k (Just pkg), ())-      same       -> (same, ())--  execute reg session+requestIsKeepAlive :: Request -> Bool+requestIsKeepAlive req+  = case requestLifetime req of+      OneTime -> False+      KeepAlive _ -> True  ---------------------------------------------------------------------------------resumeNoPkgSession :: Registry -> Session -> EventStore ()-resumeNoPkgSession reg session@Session{..} = do-  atomicModifyIORef' sessionStack $ \case-    Optional k -> (Resolved $ k Nothing, ())-    same       -> (same, ())--  execute reg session+requestToWaiting :: Request -> Waiting+requestToWaiting req =+  Waiting+  { waitingLifetime = requestLifetime req+  , waitingPkg = requestOriginal req+  , waitingMailbox = requestMailbox req+  }  ---------------------------------------------------------------------------------reinitSession :: Session -> EventStore ()-reinitSession Session{..} = atomicWriteIORef sessionStack (Resolved sessionOp)+waitingToRequest+  :: UUID -- Connection id.+  -> NominalDiffTime+  -> Waiting+  -> Request+waitingToRequest connId started w+  = Request+    { requestOriginal = waitingPkg w+    , requestConnId = connId+    , requestRetries = 1+    , requestStarted = started+    , requestMailbox = waitingMailbox w+    , requestLifetime = waitingLifetime w+    }  ---------------------------------------------------------------------------------restartSession :: Registry -> Session -> EventStore ()-restartSession reg session = do-  reinitSession session-  execute reg session+data Waiting =+  Waiting+  { waitingLifetime :: !Lifetime+  , waitingPkg :: !Package+  , waitingMailbox :: !Mailbox+  }  ---------------------------------------------------------------------------------data Sessions =-  Sessions { sessionsNextId :: IORef SessionId-           , sessionsMap    :: IORef SessionMap-           }+type Requests = HashMap UUID Request  ---------------------------------------------------------------------------------createSession :: Sessions -> Operation a -> Callback a -> EventStore Session-createSession Sessions{..} op cb = do-  sid <- atomicModifyIORef' sessionsNextId $ \n -> (succ n, n)-  ref <- newIORef (Resolved op)-  atomicModifyIORef' sessionsMap $ \m ->-    let session =-          Session { sessionId       = sid-                  , sessionOp       = op-                  , sessionStack    = ref-                  , sessionCallback = cb-                  } in-    (insertMap sid session m, session)+data Registry' =+  Registry'+  { registryRequests :: !Requests+  , registryWaitings :: ![Waiting]+  , registryTimeout :: !NominalDiffTime+  , registryMaxRetry :: !Retry+  }  ---------------------------------------------------------------------------------destroySession :: Sessions -> Session -> EventStore ()-destroySession Sessions{..} s =-  atomicModifyIORef' sessionsMap $ \m -> (deleteMap (sessionId s) m, ())+registryClear :: Registry' -> Registry'+registryClear reg =+  reg+  { registryRequests = mempty+  , registryWaitings = []+  }  ---------------------------------------------------------------------------------sessionDisposed :: Sessions -> Session -> EventStore Bool-sessionDisposed Sessions{..} s =-  notMember (sessionId s) <$> readIORef sessionsMap+data Registry =+  Registry+  { registryState :: IORef Registry'+  , registryStopwatch :: Stopwatch+  }  ---------------------------------------------------------------------------------newSessions :: IO Sessions-newSessions =-  Sessions <$> newIORef 0 -           <*> newIORef mempty+-- | I'm bad at naming thing however, we are going to use that datastructure+--  so we could lookup and delete in one single pass.+data Blob a b = Blob a b  ---------------------------------------------------------------------------------packageOf :: Request -> UUID -> Package-packageOf Request{..} uuid =-  Package { packageCmd         = _requestCmd-          , packageCorrelation = uuid-          , packageData        = _requestPayload-          , packageCred        = _requestCred-          }+instance Functor (Blob a) where+  fmap f (Blob a b) = Blob a (f b)  ---------------------------------------------------------------------------------data Pending =-    Pending { _pendingRequest :: !(Maybe Request)-            , _pendingSession :: !Session-            , _pendingRetries :: !Int-            , _pendingLastTry :: !NominalDiffTime-            , _pendingConnId  :: !UUID-            }+registryRemoveRequest+  :: UUID+  -> Registry'+  -> (Maybe Request, Registry')+registryRemoveRequest key reg+  = let Blob result newMap+          = HashMap.alterF go key (registryRequests reg)+    in (result, reg { registryRequests = newMap })+  where+    go Nothing = Blob Nothing Nothing+    go (Just e) = Blob (Just e) Nothing  ---------------------------------------------------------------------------------destroyPendingSession :: Sessions -> Pending -> EventStore ()-destroyPendingSession sessions Pending{..} =-  destroySession sessions _pendingSession+registryNew :: NominalDiffTime -> Retry -> IO Registry+registryNew timeout maxRetry+  = Registry+    <$> newIORef state+    <*> newStopwatch+  where+    state =+      Registry'+      { registryRequests = mempty+      , registryWaitings = []+      , registryTimeout = timeout+      , registryMaxRetry = maxRetry+      }  ---------------------------------------------------------------------------------type PendingRequests = HashMap UUID Pending+registryRegister+  :: Registry+  -> UUID -- Connection Id.+  -> Lifetime+  -> Package+  -> Mailbox+  -> EventStore ()+registryRegister reg connId lifetime pkg mailbox+  = do started <- stopwatchElapsed (registryStopwatch reg)+       modifyIORef' (registryState reg) (go started)+  where+    go started state =+      let req = Request+                { requestOriginal = pkg+                , requestConnId = connId+                , requestRetries = 1+                , requestStarted = started+                , requestMailbox = mailbox+                , requestLifetime = lifetime+                } ----------------------------------------------------------------------------------data Awaiting-  = Awaiting !Session-  | AwaitingRequest !Session !Request+          correlation = packageCorrelation pkg+          nextReqs = insertMap correlation req (registryRequests state)+      in state { registryRequests = nextReqs }  ---------------------------------------------------------------------------------type Awaitings = [Awaiting]+registryPostpone+  :: Registry+  -> Mailbox+  -> Lifetime+  -> Package+  -> EventStore ()+registryPostpone reg mailbox lifetime pkg+  = modifyIORef' (registryState reg) go+  where+    go state+      = let waiting+              = Waiting+                { waitingLifetime = lifetime+                , waitingPkg = pkg+                , waitingMailbox = mailbox+                } ----------------------------------------------------------------------------------rejectPending :: Exception e => Pending -> e -> EventStore ()-rejectPending Pending{..} = rejectSession _pendingSession+            nextWs = waiting : registryWaitings state+        in state { registryWaitings = nextWs }  ---------------------------------------------------------------------------------rejectAwaiting :: Exception e => Awaiting -> e -> EventStore ()-rejectAwaiting (Awaiting s) = rejectSession s-rejectAwaiting (AwaitingRequest s _) = rejectSession s+registryHandle+  :: Registry+  -> Package+  -> EventStore (Maybe NodeEndPoints)+registryHandle reg pkg+  = do state <- readIORef (registryState reg)+       case registryRemoveRequest (packageCorrelation pkg) state of+         (Nothing, _)+           -> do $logWarn [i|No operation associated to package: #{pkg}|]+                 pure Nothing+         (Just req, stateWithoutReq)+           -> case packageCmd pkg of+                cmd | cmd == badRequestCmd+                  -> do let reason = packageDataAsText pkg+                        mailboxFail (requestMailbox req) (ServerError reason)+                        pure Nothing ----------------------------------------------------------------------------------applyResponse :: Registry -> Pending -> Package -> EventStore ()-applyResponse reg Pending{..} = resumeSession reg _pendingSession+                    | cmd == notAuthenticatedCmd+                  -> do mailboxFail (requestMailbox req) NotAuthenticatedOp+                        pure Nothing ----------------------------------------------------------------------------------restartPending :: Registry -> Pending -> EventStore ()-restartPending reg Pending{..} = restartSession reg _pendingSession+                    | cmd == notHandledCmd -- In all cases, we decide to postpone that command.+                  -> do $(logWarn) [i|Not handled response received: #{pkg}.|]+                        let Just msg = maybeDecodeMessage $ packageData pkg+                            reason = getField $ notHandledReason msg+                            waiting = requestToWaiting req+                            nextWs = waiting : registryWaitings stateWithoutReq+                            finalState = stateWithoutReq { registryWaitings = nextWs }+                            origCmd = packageCmd (requestOriginal req)+                            pkgId = packageCorrelation pkg ----------------------------------------------------------------------------------data Registry =-    Registry  { _regConnRef   :: ConnectionRef-              , _regPendings  :: IORef PendingRequests-              , _regAwaitings :: IORef Awaitings-              , _stopwatch    :: Stopwatch-              , _sessions     :: Sessions-              }+                        writeIORef (registryState reg) finalState ----------------------------------------------------------------------------------newRegistry :: ConnectionRef -> IO Registry-newRegistry ref =-   Registry ref <$> newIORef mempty-                <*> newIORef []-                <*> newStopwatch-                <*> newSessions+                        case reason of+                          N_NotMaster+                            -> do let Just details = getField $ notHandledAdditionalInfo msg+                                      info = masterInfo details+                                      node = masterInfoNodeEndPoints info ----------------------------------------------------------------------------------scheduleAwait :: Registry -> Awaiting -> EventStore ()-scheduleAwait Registry{..} aw =-  atomicModifyIORef' _regAwaitings $ \stack ->-    (aw : stack, ())+                                  $(logWarn) [i|Received a non master error on command #{origCmd} [#{pkgId}] on #{node}|]+                                  pure (Just node) ----------------------------------------------------------------------------------execute :: Registry -> Session -> EventStore ()-execute self@Registry{..} session@Session{..} =-  readIORef sessionStack >>= \case-    Resolved action -> loop action-    _               -> return ()-  where-    loop stream =-      case stream of-        Return x -> absurd x-        Effect m ->-          case m of-            Failed e -> do-              destroySession _sessions session-              rejectSession session e-            Retry -> do-              reinitSession session-              execute self session-            Proceed inner ->-              loop inner-        Step step ->-          case step of-            Stop  -> destroySession _sessions session-            Yield a next -> do-              fulfill sessionCallback a-              loop next-            Await k tpe _ ->-              case tpe of-                NeedUUID  -> loop . k =<< freshUUID-                NeedRemote p -> do-                  let req = Request { _requestCmd     = payloadCmd p-                                    , _requestPayload = payloadData p-                                    , _requestCred    = payloadCreds p-                                    }-                  atomicWriteIORef sessionStack (Required k)-                  maybeConnection _regConnRef >>= \case-                    Nothing   -> scheduleAwait self (AwaitingRequest session req)-                    Just conn -> issueRequest self session conn req+                          _ -> do $(logWarn) [i|The server has either not started or is too busy. Retrying command #{origCmd} #{pkgId}|]+                                  pure Nothing -                WaitRemote uuid -> do-                  atomicWriteIORef sessionStack (Optional k)-                  let mkNewPending conn = do-                        let connId = connectionId conn-                        pending <- createPending session _stopwatch connId Nothing-                        insertPending self uuid pending+                    | otherwise+                  -> do let respCmd = packageCmd pkg -                  traverse_ mkNewPending =<< maybeConnection _regConnRef+                        mailboxSendPkg (requestMailbox req) pkg ----------------------------------------------------------------------------------issueRequest :: Registry-             -> Session-             -> Connection-             -> Request-             -> EventStore ()-issueRequest reg@Registry{..} session conn req = do-  uuid    <- liftBase nextRandom-  pending <- createPending session _stopwatch (connectionId conn) (Just req)+                        case requestLifetime req of+                          OneTime+                            -> do writeIORef (registryState reg) stateWithoutReq+                                  pure Nothing -  insertPending reg uuid pending-  enqueuePackage conn (packageOf req uuid)+                          KeepAlive endCmd+                            | endCmd == respCmd+                            -> do writeIORef (registryState reg) stateWithoutReq+                                  pure Nothing+                            | otherwise -- Means we keep the previous state (Subscription).+                            -> pure Nothing  ---------------------------------------------------------------------------------createPending :: MonadBaseControl IO m-              => Session-              -> Stopwatch-              -> UUID-              -> Maybe Request-              -> m Pending-createPending session stopwatch connId mReq = do-  elapsed <- stopwatchElapsed stopwatch-  let pending =-        Pending { _pendingRequest = mReq-                , _pendingSession = session-                , _pendingRetries = 1-                , _pendingLastTry = elapsed-                , _pendingConnId  = connId-                }--  return pending+data CRState =+  CRState+  { crsState :: !Registry'+  , crsPkgs :: ![Package]+  }  ---------------------------------------------------------------------------------insertPending :: Registry -> UUID -> Pending -> EventStore ()-insertPending Registry{..} key pending =-  atomicModifyIORef' _regPendings $ \pendings ->-    (insertMap key pending pendings, ())+crsStateNew :: Registry' -> CRState+crsStateNew reg =+  CRState+  { crsState = reg+  , crsPkgs = []+  }  ---------------------------------------------------------------------------------register :: Registry -> Operation a -> Callback a -> EventStore ()-register reg op cb = do-  session <- createSession (_sessions reg) op cb-  execute reg session+crsStateDeleteReq :: Request -> CRState -> CRState+crsStateDeleteReq req reg+  = let state+          = crsState reg+        nextReqs+          = deleteMap+              (packageCorrelation . requestOriginal $ req)+              (registryRequests state)+        nextState+          = state { registryRequests = nextReqs } in+    reg { crsState = nextState }  ---------------------------------------------------------------------------------abortPendingRequests :: Registry -> EventStore ()-abortPendingRequests Registry{..} = do-  m <- atomicModifyIORef' _regPendings $ \pendings -> (mempty, pendings)-  ws <- atomicModifyIORef' _regAwaitings $ \aw -> (mempty, aw)--  for_ m $ \p -> rejectPending p Aborted-  for_ ws $ \a -> rejectAwaiting a Aborted+crsStateRegisterReq :: Request -> CRState -> CRState+crsStateRegisterReq req reg+  = let state+          = crsState reg+        nextReqs+          = insertMap+              (packageCorrelation . requestOriginal $ req)+              req+              (registryRequests state)+        nextState+          = state { registryRequests = nextReqs } in+    reg { crsState = nextState }  ---------------------------------------------------------------------------------data Decision-  = Handled-  | Reconnect NodeEndPoints+crsStateAddPkg :: Package -> CRState -> CRState+crsStateAddPkg pkg reg+  = let nextPkgs = pkg : crsPkgs reg in+    reg { crsPkgs = nextPkgs }  ---------------------------------------------------------------------------------handlePackage :: Registry -> Package -> EventStore (Maybe Decision)-handlePackage reg@Registry{..} pkg@Package{..} = do-  outcome <- atomicModifyIORef' _regPendings $ \pendings ->-    let uuid = packageCorrelation in-    (deleteMap uuid pendings, lookup uuid pendings)--  case outcome of-    Nothing      -> return Nothing-    Just pending -> Just <$> executePending reg pkg pending+registryCheckAndRetry+  :: Registry+  -> UUID -- Connection id.+  -> EventStore [Package]+registryCheckAndRetry reg connId+  = do state <- readIORef (registryState reg)+       elapsed <- stopwatchElapsed (registryStopwatch reg)+       let reqs = mapToList $ registryRequests state ----------------------------------------------------------------------------------executePending :: Registry -> Package -> Pending -> EventStore Decision-executePending reg@Registry{..} pkg@Package{..} p@Pending{..} =-  case packageCmd of-    cmd | cmd == badRequestCmd -> do-            let reason = packageDataAsText pkg+       newState <- foldM (checking elapsed) (crsStateNew state) reqs+       let newStateTemp = crsState newState+           awaitings = registryWaitings newStateTemp+           tempState = newStateTemp { registryWaitings = [] }+           newCRState = newState { crsState = tempState }+           finalState = foldl' (sending elapsed) newCRState awaitings -            rejectPending p (ServerError reason)-            return Handled-        | cmd == notAuthenticatedCmd -> do-            rejectPending p NotAuthenticatedOp-            return Handled-        | cmd == notHandledCmd -> do-            $(logDebug) [i|Not handled response received: #{pkg}.|]-            let Just msg = maybeDecodeMessage packageData-                reason   = getField $ notHandledReason msg-            case reason of-              N_NotMaster -> do-                let Just details = getField $ notHandledAdditionalInfo msg-                    info         = masterInfo details-                    node         = masterInfoNodeEndPoints info+       writeIORef (registryState reg) (crsState finalState)+       pure (crsPkgs finalState)+  where+    checking elapsed state (_, req)+      = do let maxTimeout = registryTimeout . crsState $ state+               hasTimeout = elapsed - (requestStarted req) >= maxTimeout+               maxRetry = registryMaxRetry . crsState $ state+           if requestConnId req /= connId+             then+               do mailboxFail (requestMailbox req) ConnectionHasDropped+                  pure (crsStateDeleteReq req state)+           else if not (requestIsKeepAlive req) && hasTimeout+             then case maxRetry of+                    AtMost maxAtt+                      | requestRetries req + 1 > maxAtt+                      -> do let pkg = requestOriginal req+                                pkgId = packageCorrelation pkg+                                cmd = packageCmd pkg -                restartPending reg p-                return $ Reconnect node-              -- In this case with just retry the operation.-              _ -> Handled <$ restartPending reg p-        | otherwise -> do-            applyResponse reg p pkg-            return Handled+                            $(logError) [i|Command #{cmd} [#{pkgId}] maximum retries threshold reached (#{maxAtt}), aborted!|]+                            mailboxFail (requestMailbox req) Aborted+                            pure (crsStateDeleteReq req state)+                      | otherwise+                      -> retryReq+                    KeepRetrying+                      -> retryReq+           else+             pure state+      where+        retryReq+          = do let nextRetries+                     = requestRetries req + 1+                   nextReq+                     = req+                       { requestRetries = nextRetries+                       , requestStarted = elapsed+                       } ------------------------------------------------------------------------------------ | Occurs when an operation has been retried more than 's_operationRetry'.-data OperationMaxAttemptReached =-  OperationMaxAttemptReached UUID Command-  deriving (Show, Typeable)+                   maxAtt+                     = case registryMaxRetry . crsState $ state of+                         AtMost n -> n+                         KeepRetrying -> maxBound ----------------------------------------------------------------------------------instance Exception OperationMaxAttemptReached+                   pkg = requestOriginal req+                   cmd = packageCmd pkg+                   pkgId = packageCorrelation pkg ----------------------------------------------------------------------------------checkAndRetry :: Registry -> EventStore ()-checkAndRetry self@Registry{..} = do-  pendings    <- readIORef _regPendings-  elapsed     <- stopwatchElapsed _stopwatch-  newPendings <- foldM (checking elapsed) pendings (mapToList pendings)-  atomicWriteIORef _regPendings newPendings-  where-    checking elapsed reg (key, p) = do-      conn  <- getConnection _regConnRef-      setts <- getSettings-      case _pendingRequest p of-        Nothing-          | connectionId conn == _pendingConnId p -> return reg-          | otherwise -> do-            resumeNoPkgSession self (_pendingSession p)-            disposed <- sessionDisposed _sessions (_pendingSession p)-            let newReg = if disposed then deleteMap key reg else reg-            return newReg-        Just req -> do-          let lastTry    = _pendingLastTry p-              hasTimeout = elapsed - lastTry >= s_operationTimeout setts-          if hasTimeout || _pendingConnId p /= connectionId conn-            then do-              let retry = do-                    uuid <- liftBase nextRandom-                    let pkg     = packageOf req uuid-                        pending =-                          p { _pendingLastTry = elapsed-                            , _pendingRetries = _pendingRetries p + 1-                            , _pendingConnId  = connectionId conn-                            }-                        nextReg = deleteMap key $ insertMap uuid pending reg+               $(logWarn) [i|Command #{cmd} [#{pkgId} has timeout. Retrying (attempt #{nextRetries}/#{maxAtt})|] -                    enqueuePackage conn pkg-                    return nextReg+               pure . crsStateRegisterReq nextReq . crsStateAddPkg pkg $ state -              case s_operationRetry setts of-                AtMost maxAttempts-                  | _pendingRetries p <= maxAttempts-                    -> retry-                  | otherwise -> do-                    let cmd = _requestCmd req-                    destroyPendingSession _sessions p-                    rejectPending p (OperationMaxAttemptReached key cmd)-                    return $ deleteMap key reg-                KeepRetrying -> retry-            else return reg+    sending elapsed state w+      = let req = waitingToRequest connId elapsed w+            pkg = requestOriginal req in+        crsStateRegisterReq req . crsStateAddPkg pkg $ state  ---------------------------------------------------------------------------------startAwaitings :: Registry -> EventStore ()-startAwaitings reg@Registry{..} = do-  awaitings <- atomicModifyIORef' _regAwaitings $ \stack ->-    ([], reverse stack)+registryAbort :: Registry -> EventStore ()+registryAbort reg+  = do state <- readIORef (registryState reg)+       writeIORef (registryState reg) (registryClear state) -  traverse_ starting awaitings-  where-    starting (Awaiting session)            = execute reg session-    starting (AwaitingRequest session req) = do-      conn <- getConnection _regConnRef-      issueRequest reg session conn req+       for_ (registryRequests state) $ \req+         -> mailboxFail (requestMailbox req) Aborted++       for_ (registryWaitings state) $ \w+         -> mailboxFail (waitingMailbox w) Aborted  -------------------------------------------------------------------------------- maybeDecodeMessage :: Decode a => ByteString -> Maybe a
Database/EventStore/Internal/Operation.hs view
@@ -1,404 +1,159 @@-{-# LANGUAGE DataKinds                  #-}-{-# LANGUAGE DeriveDataTypeable         #-}-{-# LANGUAGE GADTs                      #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE KindSignatures             #-}-{-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE Rank2Types                 #-}-{-# LANGUAGE StandaloneDeriving         #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE StandaloneDeriving #-} -------------------------------------------------------------------------------- -- |--- Module : Database.EventStore.Internal.Operation--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable+-- Module    :  Database.EventStore.Internal.Operation+-- Copyright :  (C) 2020 Yorick Laupa+-- License   :  (see the file LICENSE)+-- Maintainer:  Yorick Laupa <yo.eight@gmail.com>+-- Stability :  experimental+-- Portability: non-portable -- ---------------------------------------------------------------------------------module Database.EventStore.Internal.Operation-  ( OpResult(..)-  , OperationError(..)-  , Operation-  , Code-  , Need(..)-  , Execution(..)-  , Expect(..)-  , Coroutine(..)-  , Payload(..)-  , freshId-  , failure-  , retry-  , send-  , request-  , waitForOr-  , wrongVersion-  , streamDeleted-  , invalidTransaction-  , accessDenied-  , protobufDecodingError-  , serverError-  , invalidServerResponse-  , construct-  , yield-  , traversing-  , stop-  , (<~)-  , unfolding-  , append-  , Stream(..)-  ) where+module Database.EventStore.Internal.Operation where  -------------------------------------------------------------------------------- import Prelude (String)-import Control.Category---------------------------------------------------------------------------------- import Data.ProtocolBuffers-import Data.Serialize-import Data.UUID-import Data.Void (Void, absurd)-import Streaming.Internal+import Data.Serialize (runPut, runGet)  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Control import Database.EventStore.Internal.Prelude hiding ((.), id)-import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types  ---------------------------------------------------------------------------------infixr 9 <~------------------------------------------------------------------------------------- | Operation result sent by the server.-data OpResult-    = OP_SUCCESS-    | OP_PREPARE_TIMEOUT-    | OP_COMMIT_TIMEOUT-    | OP_FORWARD_TIMEOUT-    | OP_WRONG_EXPECTED_VERSION-    | OP_STREAM_DELETED-    | OP_INVALID_TRANSACTION-    | OP_ACCESS_DENIED-    deriving (Eq, Enum, Show)------------------------------------------------------------------------------------- | Operation exception that can occurs on an operation response.-data OperationError-    = WrongExpectedVersion Text ExpectedVersion -- ^ Stream and Expected Version-    | StreamDeleted StreamName                        -- ^ Stream-    | InvalidTransaction-    | forall t. AccessDenied (StreamId t)                   -- ^ Stream-    | InvalidServerResponse Command Command     -- ^ Expected, Found-    | ProtobufDecodingError String-    | ServerError (Maybe Text)                  -- ^ Reason-    | InvalidOperation Text-    | StreamNotFound StreamName-    | NotAuthenticatedOp-      -- ^ Invalid operation state. If happens, it's a driver bug.-    | Aborted-      -- ^ Occurs when the user asked to close the connection or if the-      --   connection can't reconnect anymore.-    deriving Typeable-----------------------------------------------------------------------------------deriving instance Show OperationError-----------------------------------------------------------------------------------instance Exception OperationError-----------------------------------------------------------------------------------data Payload =-  Payload-  { payloadCmd :: !Command-  , payloadData :: !ByteString-  , payloadCreds :: !(Maybe Credentials)-  }-----------------------------------------------------------------------------------data Coroutine k o a where-  Yield :: o -> a -> Coroutine k o a-  Await :: (i -> a) -> k i -> a -> Coroutine k o a-  Stop  :: Coroutine k o a-----------------------------------------------------------------------------------instance Functor (Coroutine k o) where-  fmap f (Yield o a)   = Yield o (f a)-  fmap f (Await k n a) = Await (f . k) n (f a)-  fmap _ Stop          = Stop-----------------------------------------------------------------------------------data Is a b where-  Same :: Is a a+newtype Mailbox = Mailbox (Chan (Either OperationError Package))  ---------------------------------------------------------------------------------instance Category Is where-  id = Same-  Same . Same = Same+mailboxSendPkg :: MonadBase IO m => Mailbox -> Package -> m ()+mailboxSendPkg (Mailbox chan) pkg = writeChan chan (Right pkg)  ---------------------------------------------------------------------------------data Need a where-  NeedUUID   :: Need UUID-  NeedRemote :: Payload -> Need Package-  WaitRemote :: UUID -> Need (Maybe Package)+mailboxFail :: MonadBase IO m => Mailbox -> OperationError -> m ()+mailboxFail (Mailbox chan) e = writeChan chan (Left e)  ---------------------------------------------------------------------------------data Execution a-  = Proceed a-  | Retry-  | Failed !OperationError+mailboxRead :: MonadBase IO m => Mailbox -> m (Either OperationError Package)+mailboxRead (Mailbox chan) = readChan chan  ---------------------------------------------------------------------------------instance Functor Execution where-  fmap f (Proceed a) = Proceed (f a)-  fmap _ Retry       = Retry-  fmap _ (Failed e)  = Failed e+mailboxReadDecoded+  :: (MonadBase IO m, Decode resp)+  => Mailbox+  -> m (Either OperationError resp)+mailboxReadDecoded (Mailbox chan)+  = fmap (decodePkg =<<) $ readChan chan  ---------------------------------------------------------------------------------instance Applicative Execution where-  pure = return-  (<*>) = ap+mailboxNew :: MonadBase IO m => m Mailbox+mailboxNew = Mailbox <$> newChan  ---------------------------------------------------------------------------------instance Monad Execution where-  return = Proceed+createPkg+  :: (Encode msg, MonadIO m)+  => Command+  -> Maybe Credentials+  -> msg+  -> m Package+createPkg cmd creds msg+  = do pkgId <- freshUUID+       let dat = runPut $ encodeMessage msg+           pkg+             = Package+               { packageCmd = cmd+               , packageCorrelation = pkgId+               , packageData = dat+               , packageCred = creds+               } -  Proceed a >>= f = f a-  Retry     >>= _ = Retry-  Failed e  >>= _ = Failed e+       pure pkg  ---------------------------------------------------------------------------------type Machine k o m r  = Stream (Coroutine k o) m r-type Code output a    = Machine Need output Execution a-type Operation output = Code output Void-type Process m a b    = Stream (Coroutine (Is a) b) m Void+-- FIXME We could use Bifunctor but can't I am not sure it covers all the GHC+-- we support at that time.+decodePkg :: Decode msg => Package -> Either OperationError msg+decodePkg pkg+  = case runGet decodeMessage (packageData pkg) of+      Left e -> Left $ ProtobufDecodingError e+      Right resp -> Right resp  ---------------------------------------------------------------------------------awaits :: Monad m => k i -> Machine k o m i-awaits instr = Step (Await pure instr (Step Stop))+-- | Operation exception that can occurs on an operation response.+data OperationError+  = WrongExpectedVersion !Text !ExpectedVersion -- ^ Stream and Expected Version+  | StreamDeleted !StreamName                        -- ^ Stream+  | InvalidTransaction+  | forall t. AccessDenied !(StreamId t)                   -- ^ Stream+  | InvalidServerResponse !Command !Command     -- ^ Expected, Found+  | ProtobufDecodingError !String+  | ServerError !(Maybe Text)                  -- ^ Reason+  | InvalidOperation !Text+  | StreamNotFound !StreamName+  | NotAuthenticatedOp+    -- ^ Invalid operation state. If happens, it's a driver bug.+  | Aborted+    -- ^ Occurs when the user asked to close the connection or if the+    --   connection can't reconnect anymore.+  | ConnectionHasDropped+  deriving Typeable  ---------------------------------------------------------------------------------await :: (Monad m, Category k) => Machine (k i) o m i-await = awaits id+deriving instance Show OperationError  ---------------------------------------------------------------------------------stop :: Machine k o m a-stop = Step Stop+instance Exception OperationError  ---------------------------------------------------------------------------------yield :: Monad m => o -> Machine k o m ()-yield o = Step (Yield o (pure ()))+-- | Operation result sent by the server.+data OpResult+    = OP_SUCCESS+    | OP_PREPARE_TIMEOUT+    | OP_COMMIT_TIMEOUT+    | OP_FORWARD_TIMEOUT+    | OP_WRONG_EXPECTED_VERSION+    | OP_STREAM_DELETED+    | OP_INVALID_TRANSACTION+    | OP_ACCESS_DENIED+    deriving (Eq, Enum, Show)  ---------------------------------------------------------------------------------traversing :: Monad m => (a -> m b) -> Process m a b-traversing k = repeatedly $ do-  a <- await-  b <- lift (k a)-  yield b+data Lifetime+  = OneTime+  | KeepAlive !Command  ---------------------------------------------------------------------------------append :: Operation o -> Operation o -> Operation o-append start right = go start-  where-    go cur =-      case cur of-        Return x  -> absurd x-        Effect m  -> Effect (fmap go m)-        Step step ->-          case step of-            Yield o next ->-              Step $ Yield o (append next right)-            Await k instr failed ->-              Step $ Await (\i -> append (k i) right) instr (append failed right)-            Stop ->-              right+data Loop a+  = Loop+  | Break !a  ---------------------------------------------------------------------------------stepping :: Operation a -> Code o (a, Operation a)-stepping = go-  where-    go cur =-      case cur of-        Return x  -> absurd x-        Effect m  -> Effect (fmap go m)-        Step step ->-          case step of-            Yield a next         -> pure (a, next)-            Await k instr failed -> Step $ Await (go . k) instr (go failed)-            Stop                 -> stop+data LoopS s a+  = LoopS !s+  | BreakS !a  ---------------------------------------------------------------------------------unfolding :: (Maybe a -> Code o (Operation a)) -> Operation o-unfolding k = k Nothing >>= go+keepLooping :: Monad m => m (Loop a) -> m a+keepLooping action+  = go   where-    go cur = do-      (a, next) <- stepping cur-      newState  <- k (Just a)-      go (append next newState)+    go = do result <- action+            case result of+              Loop -> go+              Break a -> pure a  ---------------------------------------------------------------------------------repeatedly :: Functor m => Machine k o m x -> Machine k o m r-repeatedly start = go start+keepLoopingS :: Monad m => s -> (s -> m (LoopS s a)) -> m a+keepLoopingS seed action+  = go seed   where-    go (Return _)  = go start-    go (Effect m)  = Effect (fmap go m)-    go (Step step) =-      case step of-        Yield o next         -> Step $ Yield o (go next)-        Await k instr failed -> Step $ Await (go . k) instr (go failed)-        Stop                 -> stop-----------------------------------------------------------------------------------(<~) :: Monad m => Process m a b -> Machine k a m r -> Machine k b m r-mp <~ mb =-  case mp of-    Return x  -> absurd x-    Effect m  -> Effect (fmap (<~ mb) m)-    Step consumer ->-      case consumer of-        Yield c next        -> Step $ Yield c (next <~ mb)-        Stop                -> stop-        Await k Same failed ->-          case mb of-            Return _      -> failed <~ stop-            Effect m      ->  Effect (fmap (Step consumer <~) m)-            Step producer ->-              case producer of-                Yield b next -> k b <~ next-                Await kb instr kfailed ->-                  Step (Await ((mp <~) . kb) instr (mp <~ kfailed))-                Stop -> failed <~ stop------------------------------------------------------------------------------------- | Asks for a unused 'UUID'.-freshId :: Code o UUID-freshId = Step $ Await pure NeedUUID (Step Stop)------------------------------------------------------------------------------------- | Raises an 'OperationError'.-failure :: OperationError -> Code o a-failure = lift . Failed------------------------------------------------------------------------------------- | Asks to resume the interpretation from the beginning.-retry :: Code o a-retry = lift Retry------------------------------------------------------------------------------------- | Sends a package to the server and wait for a response.-sendRemote :: Payload -> Code o Package-sendRemote p = Step $ Await pure (NeedRemote p) (Step Stop)------------------------------------------------------------------------------------- | Waits package from the server.-waitRemote :: UUID -> Code o (Maybe Package)-waitRemote c = Step $ Await pure (WaitRemote c) (Step Stop)-----------------------------------------------------------------------------------construct :: Code o a -> Operation o-construct m = m >> stop------------------------------------------------------------------------------------- | Like 'request' except it discards the correlation id of the network---   exchange.-send :: (Encode req, Decode resp)-     => Command-     -> Command-     -> Maybe Credentials-     -> req-     -> Code o resp-send reqCmd expCmd cred req = do-  let dat     = runPut $ encodeMessage req-      payload = Payload reqCmd dat cred-  pkg <- sendRemote payload-  let gotCmd = packageCmd pkg--  when (gotCmd /= expCmd)-    (invalidServerResponse expCmd gotCmd)--  case runGet decodeMessage (packageData pkg) of-    Left e     -> protobufDecodingError e-    Right resp -> return resp-----------------------------------------------------------------------------------data Expect o where-  Expect :: Decode resp => Command -> (UUID -> resp -> Code o ()) -> Expect o------------------------------------------------------------------------------------- | Runs the first expection that matches.-runFirstMatch :: Package -> [Expect o] -> Code o ()-runFirstMatch _ [] = invalidOperation "No expectation was fulfilled"-runFirstMatch pkg (Expect cmd k:rest)-  | packageCmd pkg /= cmd = runFirstMatch pkg rest-  | otherwise =-    case runGet decodeMessage (packageData pkg) of-      Left e     -> protobufDecodingError e-      Right resp -> k (packageCorrelation pkg) resp------------------------------------------------------------------------------------- | Sends a message to remote server. It returns the expected deserialized---   message along with the correlation id of the network exchange.-request :: Encode req-        => Command-        -> Maybe Credentials-        -> req-        -> [Expect o]-        -> Code o ()-request reqCmd cred rq exps = do-  let dat = runPut $ encodeMessage rq-      payload = Payload reqCmd dat cred-  pkg <- sendRemote payload-  runFirstMatch pkg exps------------------------------------------------------------------------------------- | @waitForElse uuid alternative expects@ Waits for a message from the server---   at the given /uuid/. If the connection has been reset in the meantime, it---   will use /alternative/.-waitForOr :: UUID -> Code o () -> [Expect o] -> Code o ()-waitForOr pid alt exps =-  waitRemote pid >>= \case-    Nothing  -> alt-    Just pkg ->-      runFirstMatch pkg exps------------------------------------------------------------------------------------- | Raises 'WrongExpectedVersion' exception.-wrongVersion :: Text -> ExpectedVersion -> Code o a-wrongVersion stream ver = failure (WrongExpectedVersion stream ver)------------------------------------------------------------------------------------- | Raises 'StreamDeleted' exception.-streamDeleted :: StreamName -> Code o a-streamDeleted stream = failure (StreamDeleted stream)------------------------------------------------------------------------------------- | Raises 'InvalidTransaction' exception.-invalidTransaction :: Code o a-invalidTransaction = failure InvalidTransaction------------------------------------------------------------------------------------- | Raises 'AccessDenied' exception.-accessDenied :: StreamId t -> Code o a-accessDenied = failure . AccessDenied------------------------------------------------------------------------------------- | Raises 'ProtobufDecodingError' exception.-protobufDecodingError :: String -> Code o a-protobufDecodingError = failure . ProtobufDecodingError------------------------------------------------------------------------------------- | Raises 'ServerError' exception.-serverError :: Maybe Text -> Code o a-serverError = failure . ServerError------------------------------------------------------------------------------------- | Raises 'InvalidServerResponse' exception.-invalidServerResponse :: Command -> Command -> Code o a-invalidServerResponse expe got = failure $ InvalidServerResponse expe got------------------------------------------------------------------------------------invalidOperation :: Text -> Code o a-invalidOperation = failure . InvalidOperation+    go cur+      = do result <- action cur+           case result of+             LoopS next+               -> go next+             BreakS a+               -> pure a
Database/EventStore/Internal/Operation/Catchup.hs view
@@ -19,16 +19,20 @@ -------------------------------------------------------------------------------- import Data.Int import Data.Maybe+import Data.ProtocolBuffers  --------------------------------------------------------------------------------+import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation-import Database.EventStore.Internal.Operation.Read.Common-import Database.EventStore.Internal.Operation.ReadAllEvents-import Database.EventStore.Internal.Operation.ReadStreamEvents-import Database.EventStore.Internal.Operation.Volatile+import qualified Database.EventStore.Internal.Operation.ReadAllEvents.Message as ReadAll+import qualified Database.EventStore.Internal.Operation.ReadStreamEvents.Message as ReadStream import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream+import Database.EventStore.Internal.Subscription.Message import Database.EventStore.Internal.Subscription.Types import Database.EventStore.Internal.Types @@ -37,77 +41,212 @@ defaultBatchSize = 500  ---------------------------------------------------------------------------------streamNotFound :: Text -> OperationError-streamNotFound stream = StreamNotFound $ StreamName stream+data State s+  = Init s+  | Catchup UUID UUID s+  | Live UUID s  ---------------------------------------------------------------------------------fetchStream :: Settings-            -> Text -- Stream name.-            -> Int32 -- Batch size.-            -> Bool -- Resolve link tos.-            -> Maybe Credentials-            -> EventNumber-            -> Operation (Slice EventNumber)-fetchStream setts stream batch tos cred (EventNumber n) =-    traversing go <~ readStreamEvents setts Forward stream n batch tos cred-  where-    go outcome =-        fromReadResult stream outcome pure+createReadPkg+  :: Settings+  -> StreamId t+  -> t+  -> Int32 -- Batch size+  -> Bool -- Resolve links+  -> Maybe Credentials+  -> IO Package+createReadPkg setts (StreamName stream) evtNum batch tos cred+  = let+      req =+        ReadStream.newRequest+          stream+          (eventNumberToInt64 evtNum)+          batch+          tos+          (s_requireMaster setts) in+    createPkg readStreamEventsForwardCmd cred req+createReadPkg setts All pos batch tos cred+  = let+      req =+        ReadAll.newRequest+          (positionCommit pos)+          (positionPrepare pos)+          batch+          tos+          (s_requireMaster setts) in+    createPkg readAllEventsForwardCmd cred req  ---------------------------------------------------------------------------------fetchAll :: Settings-         -> Int32 -- Batch size.-         -> Bool -- Resolve link tos.-         -> Maybe Credentials-         -> Position-         -> Operation (Slice Position)-fetchAll setts batch tos cred (Position com pre) =-    traversing pure <~ readAllEvents setts com pre batch tos Forward cred+catchup+  :: Settings+  -> Exec+  -> StreamId t+  -> t+  -> Bool        -- Resolve link tos.+  -> Maybe Int32 -- Batch size.+  -> Maybe Credentials+  -> IO (TVar (Maybe UUID), Chan SubAction)+catchup setts exec streamId from tos batchSiz cred+  = do m <- mailboxNew+       subM <- newChan+       var <- newTVarIO Nothing+       _ <- async $ keepLoopingS (Init from) $ \case+         Init pos+           -> do let subReq = subscribeToStream stream tos+                 subPkg <- createPkg subscribeToStreamCmd cred subReq+                 readPkg <- createReadPkg setts streamId pos batch tos cred ----------------------------------------------------------------------------------sourceStream :: (t -> Operation (Slice t))-             -> t-             -> Operation SubAction-sourceStream fetch start = unfolding go-  where-    go Nothing =-        pure (fetch start)-    go (Just s) = do-        traverse_ (yield . Submit) (sliceEvents s)+                 publishWith exec (Transmit m (KeepAlive subscriptionDroppedCmd) subPkg)+                 publishWith exec (Transmit m OneTime readPkg)+                 let theSubId = packageCorrelation subPkg -        case sliceNext s of-            Just next -> pure (fetch next)-            Nothing   -> stop+                 atomically $ writeTVar var (Just theSubId) ----------------------------------------------------------------------------------catchup :: forall t. Settings-        -> StreamId t-        -> t-        -> Bool        -- Resolve link tos.-        -> Maybe Int32 -- Batch size.-        -> Maybe Credentials-        -> Operation SubAction-catchup setts streamId from tos batchSiz cred =-    append (sourceStream iteratee from) (volatile streamId tos cred)-  where-    batch = fromMaybe defaultBatchSize batchSiz+                 pure $ LoopS (Catchup theSubId (packageCorrelation readPkg) pos) -    iteratee :: t -> Operation (Slice t)-    iteratee =-        case streamId of-            StreamName n -> fetchStream setts n batch tos cred-            All          -> fetchAll setts batch tos cred+         unchanged@(Catchup theSubId readId pos)+           -> do outcome <- mailboxRead m+                 case outcome of+                   Left e+                     -> case e of+                          ConnectionHasDropped+                            -> pure $ LoopS (Init pos)+                          _ -> BreakS () <$ writeChan subM (Dropped SubAborted) ----------------------------------------------------------------------------------fromReadResult :: Text-               -> ReadResult EventNumber a-               -> (a -> Execution x)-               -> Execution x-fromReadResult stream res k =-    case res of-        ReadNoStream        -> Failed $ streamNotFound stream-        ReadStreamDeleted s -> Failed $ StreamDeleted s-        ReadNotModified     -> Failed $ ServerError Nothing-        ReadError e         -> Failed $ ServerError e-        ReadAccessDenied s  -> Failed $ AccessDenied s-        ReadSuccess ss      -> k ss+                   Right respPkg+                     | theSubId == packageCorrelation respPkg+                     && packageCmd respPkg == subscriptionDroppedCmd+                     -> let Right resp = decodePkg respPkg+                            reason = fromMaybe D_Unsubscribed (getField $ dropReason resp)+                            subReason = toSubDropReason reason in+                        BreakS () <$ writeChan subM (Dropped subReason)++                     | theSubId == packageCorrelation respPkg+                     && packageCmd respPkg == subscriptionConfirmationCmd+                     -> let Right resp = decodePkg respPkg+                            lcp = getField $ subscribeLastCommitPos resp+                            len = getField $ subscribeLastEventNumber resp+                            details =+                              SubDetails+                              { subId = theSubId+                              , subCommitPos = lcp+                              , subLastEventNum = len+                              , subSubId = Nothing+                              } in+                        LoopS unchanged <$ writeChan subM (Confirmed details)++                     | theSubId == packageCorrelation respPkg+                     -> pure $ LoopS unchanged++                     | readId == packageCorrelation respPkg+                     -> case streamId of+                          StreamName _+                            -> do let+                                    Right resp = decodePkg respPkg+                                    r = getField $ ReadStream._result resp+                                    es = getField $ ReadStream._events resp+                                    evts = fmap newResolvedEvent es+                                    eos = getField $ ReadStream._endOfStream resp+                                    nxt = getField $ ReadStream._nextNumber resp+                                  case r of+                                    ReadStream.NO_STREAM+                                      -> pure $ LoopS (Live theSubId pos)++                                    ReadStream.SUCCESS+                                      -> do traverse_ (writeChan subM . Submit) evts+                                            if eos+                                              then+                                                pure $ LoopS (Live theSubId (rawEventNumber nxt))+                                              else+                                                do newReadPkg <- createReadPkg setts streamId (rawEventNumber nxt) batch tos cred+                                                   let newReadId = packageCorrelation newReadPkg++                                                   publishWith exec (Transmit m OneTime newReadPkg)+                                                   pure $ LoopS (Catchup theSubId newReadId (rawEventNumber nxt))++                                         -- TODO - Do we have to close the subscription?+                                         -- Pretty sure the subcription has failed already at+                                         -- this point.+                                    _ -> BreakS () <$ writeChan subM (Dropped SubAborted)++                          All+                            -> do let+                                    Right resp = decodePkg respPkg+                                    r = getField $ ReadAll._Result resp+                                    nc_pos = getField $ ReadAll._NextCommitPosition resp+                                    np_pos = getField $ ReadAll._NextPreparePosition resp+                                    es = getField $ ReadAll._Events resp+                                    evts = fmap newResolvedEventFromBuf es+                                    eos = null evts+                                    n_pos = Position nc_pos np_pos++                                  case fromMaybe ReadAll.SUCCESS r of+                                    ReadAll.SUCCESS+                                      -> do traverse_ (writeChan subM . Submit) evts+                                            if eos+                                              then+                                                pure $ LoopS (Live theSubId n_pos)+                                              else+                                                do newReadPkg <- createReadPkg setts streamId n_pos batch tos cred+                                                   let newReadId = packageCorrelation newReadPkg++                                                   publishWith exec (Transmit m OneTime newReadPkg)+                                                   pure $ LoopS (Catchup theSubId newReadId n_pos)++                                         -- TODO - Do we have to close the subscription?+                                         -- Pretty sure the subcription has failed already at+                                         -- this point.+                                    _ -> BreakS () <$ writeChan subM (Dropped SubAborted)+                     | otherwise+                     -> pure $ LoopS unchanged++         unchanged@(Live theSubId pos)+           -> do outcome <- mailboxRead m+                 case outcome of+                   Left e+                     -> case e of+                          ConnectionHasDropped+                            -> pure $ LoopS (Init pos)+                          _ -> BreakS () <$ writeChan subM (Dropped SubAborted)++                   Right respPkg+                     | theSubId == packageCorrelation respPkg+                     && packageCmd respPkg == subscriptionDroppedCmd+                     -> let Right resp = decodePkg respPkg+                            reason = fromMaybe D_Unsubscribed (getField $ dropReason resp)+                            subReason = toSubDropReason reason in+                        BreakS () <$ writeChan subM (Dropped subReason)+                     | theSubId == packageCorrelation respPkg+                     && packageCmd respPkg == subscriptionConfirmationCmd+                     -> let Right resp = decodePkg respPkg+                            lcp = getField $ subscribeLastCommitPos resp+                            len = getField $ subscribeLastEventNumber resp+                            details =+                              SubDetails+                              { subId = theSubId+                              , subCommitPos = lcp+                              , subLastEventNum = len+                              , subSubId = Nothing+                              } in+                        LoopS unchanged <$ writeChan subM (Confirmed details)+                     | theSubId == packageCorrelation respPkg+                     && packageCmd respPkg == streamEventAppearedCmd+                     -> let+                          Right resp = decodePkg respPkg+                          evt = newResolvedEventFromBuf $ getField $ streamResolvedEvent resp+                          nextState =+                            case streamId of+                              StreamName _+                                -> let nxt = resolvedEventOriginalEventNumber evt+                                   in Live theSubId (rawEventNumber nxt)+                              All+                                -> let Just nxtPos = resolvedEventPosition evt+                                   in Live theSubId nxtPos in+                        LoopS nextState <$ writeChan subM (Submit evt)+                     | otherwise+                     -> pure $ LoopS unchanged++       pure (var, subM)+  where+    batch = fromMaybe defaultBatchSize batchSiz+    stream = streamIdRaw streamId
Database/EventStore/Internal/Operation/DeleteStream.hs view
@@ -26,6 +26,9 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Exec import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.DeleteStream.Message import Database.EventStore.Internal.Prelude@@ -37,30 +40,44 @@ -- | Returned after deleting a stream. 'Position' of the write. newtype DeleteResult = DeleteResult Position deriving (Eq, Show) ------------------------------------------------------------------------------------ | Delete a regular stream operation.-deleteStream :: Settings-             -> Text-             -> ExpectedVersion-             -> Maybe Bool-             -> Maybe Credentials-             -> Operation DeleteResult-deleteStream Settings{..} s v hard cred = construct $ do-    let msg = newRequest s (expVersionInt64 v) s_requireMaster hard-    resp <- send deleteStreamCmd deleteStreamCompletedCmd cred msg-    let r            = getField $ _result resp-        com_pos      = getField $ _commitPosition resp-        prep_pos     = getField $ _preparePosition resp-        com_pos_int  = fromMaybe (-1) com_pos-        prep_pos_int = fromMaybe (-1) prep_pos-        pos          = Position com_pos_int prep_pos_int-        res          = DeleteResult pos-    case r of-        OP_SUCCESS                -> yield res-        OP_PREPARE_TIMEOUT        -> retry-        OP_FORWARD_TIMEOUT        -> retry-        OP_COMMIT_TIMEOUT         -> retry-        OP_WRONG_EXPECTED_VERSION -> wrongVersion s v-        OP_STREAM_DELETED         -> streamDeleted $ StreamName s-        OP_INVALID_TRANSACTION    -> invalidTransaction-        OP_ACCESS_DENIED          -> accessDenied (StreamName s)+-------------------------------------------------------------------------------+deleteStream+  :: Settings+  -> Exec+  -> Text+  -> ExpectedVersion+  -> Maybe Bool+  -> Maybe Credentials+  -> IO (Async DeleteResult)+deleteStream setts exec stream version hard creds+  = do m <- mailboxNew+       async $+         do let req = newRequest stream (expVersionInt64 version) (s_requireMaster setts) hard++            pkg <- createPkg deleteStreamCmd creds req++            keepLooping $ do+              publishWith exec (Transmit m OneTime pkg)+              outcome <- mailboxReadDecoded m+              case outcome of+                Left e+                  -> throw e+                Right resp+                  -> let r = getField $ _result resp+                         com_pos = getField $ _commitPosition resp+                         prep_pos = getField $ _preparePosition resp+                         com_pos_int = fromMaybe (-1) com_pos+                         prep_pos_int = fromMaybe (-1) prep_pos+                         pos = Position com_pos_int prep_pos_int+                         res = DeleteResult pos in+                     case r of+                       OP_SUCCESS -> pure $ Break res+                       OP_PREPARE_TIMEOUT -> pure Loop+                       OP_FORWARD_TIMEOUT -> pure Loop+                       OP_COMMIT_TIMEOUT -> pure Loop+                       OP_WRONG_EXPECTED_VERSION -> throw $ WrongExpectedVersion stream version+                       OP_STREAM_DELETED -> throw $ StreamDeleted $ StreamName stream+                       OP_INVALID_TRANSACTION -> throw InvalidTransaction+                       OP_ACCESS_DENIED -> throw $ AccessDenied (StreamName stream)++
Database/EventStore/Internal/Operation/Persist.hs view
@@ -17,6 +17,9 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Settings@@ -25,57 +28,51 @@ import Database.EventStore.Internal.Types  ---------------------------------------------------------------------------------persist :: Text -> Text -> Int32 -> Maybe Credentials -> Operation SubAction-persist grp stream bufSize cred =-  construct (issueRequest grp stream bufSize cred)-----------------------------------------------------------------------------------issueRequest :: Text -> Text -> Int32 -> Maybe Credentials -> Code SubAction ()-issueRequest grp stream bufSize cred = do-  let req = _connectToPersistentSubscription grp stream bufSize-  request connectToPersistentSubscriptionCmd cred req-    [ Expect subscriptionDroppedCmd $ \_ d ->-        handleDropped d-    , Expect persistentSubscriptionConfirmationCmd $ \sid c -> do-        let lcp      = getField $ pscLastCommitPos c-            subSubId = getField $ pscId c-            len      = getField $ pscLastEvtNumber c-            details  =-              SubDetails-              { subId           = sid-              , subCommitPos    = lcp-              , subLastEventNum = len-              , subSubId        = Just subSubId-              }-        yield (Confirmed details)-        live sid-    ]-----------------------------------------------------------------------------------eventAppeared :: PersistentSubscriptionStreamEventAppeared -> Code SubAction ()-eventAppeared e = do-  let evt = newResolvedEvent $ getField $ psseaEvt e-  yield (Submit evt)-----------------------------------------------------------------------------------live :: UUID -> Code SubAction ()-live subscriptionId = loop-  where-    loop =-      waitForOr subscriptionId connectionReset-        [ Expect subscriptionDroppedCmd $ \_ d ->-            handleDropped d-        , Expect persistentSubscriptionStreamEventAppearedCmd $ \_ e -> do-            eventAppeared e-            loop-        ]-----------------------------------------------------------------------------------connectionReset :: Code SubAction ()-connectionReset = yield ConnectionReset+persist+  :: Exec+  -> Text+  -> Text+  -> Int32+  -> Maybe Credentials+  -> IO (UUID, TVar (Maybe Text), Chan SubAction)+persist exec grp stream bufSize cred+  = do m <- mailboxNew+       subM <- newChan+       var <- newTVarIO Nothing+       let req = _connectToPersistentSubscription grp stream bufSize+       pkg <- createPkg connectToPersistentSubscriptionCmd cred req+       let theSubId = packageCorrelation pkg+       publishWith exec (Transmit m (KeepAlive subscriptionDroppedCmd) pkg)+       _ <- async $ keepLooping $+         do outcome <- mailboxRead m+            case outcome of+              Left _+                -> Break () <$ writeChan subM (Dropped SubAborted)+              Right respPkg+                | packageCmd respPkg == subscriptionDroppedCmd+                -> let Right resp = decodePkg respPkg+                       reason = fromMaybe D_Unsubscribed (getField $ dropReason resp)+                       subReason = toSubDropReason reason in+                   Break () <$ writeChan subM (Dropped subReason)+                | packageCmd respPkg == persistentSubscriptionConfirmationCmd+                -> do let Right resp = decodePkg respPkg+                          lcp = getField $ pscLastCommitPos resp+                          subSubId = getField $ pscId resp+                          len = getField $ pscLastEvtNumber resp+                          details =+                            SubDetails+                            { subId = theSubId+                            , subCommitPos = lcp+                            , subLastEventNum = len+                            , subSubId = Just subSubId+                            }+                      atomically $ writeTVar var (Just subSubId)+                      Loop <$ writeChan subM (Confirmed details)+                | packageCmd respPkg == persistentSubscriptionStreamEventAppearedCmd+                -> let Right resp = decodePkg respPkg+                       evt = newResolvedEvent $ getField $ psseaEvt resp in+                   Loop <$ writeChan subM (Submit evt)+                | otherwise+                -> pure Loop ----------------------------------------------------------------------------------handleDropped :: SubscriptionDropped -> Code SubAction ()-handleDropped d = do-  let reason = fromMaybe D_Unsubscribed (getField $ dropReason d)-  yield (Dropped $ toSubDropReason reason)+       pure (theSubId, var, subM)
Database/EventStore/Internal/Operation/PersistOperations.hs view
@@ -20,6 +20,9 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Subscription.Message@@ -28,55 +31,76 @@ import Database.EventStore.Internal.Types  ---------------------------------------------------------------------------------persistOperation :: Text-                 -> Text-                 -> Maybe Credentials-                 -> PersistAction-                 -> Operation (Maybe PersistActionException)-persistOperation grp stream cred tpe = construct go-  where-    go =-      case tpe of-        PersistCreate ss -> do-          let req = _createPersistentSubscription grp stream ss-          resp <- send createPersistentSubscriptionCmd-                       createPersistentSubscriptionCompletedCmd cred req-          let result = createRException $ getField $ cpscResult resp-          yield result-        PersistUpdate ss -> do-          let req = _updatePersistentSubscription grp stream ss-          resp <- send updatePersistentSubscriptionCmd-                       updatePersistentSubscriptionCompletedCmd cred req-          let result = updateRException $ getField $ upscResult resp-          yield result-        PersistDelete -> do-          let req = _deletePersistentSubscription grp stream-          resp <- send deletePersistentSubscriptionCmd-                       deletePersistentSubscriptionCompletedCmd cred req-          let result = deleteRException $ getField $ dpscResult resp-          yield result+persistOperation+  :: Exec+  -> Text+  -> Text+  -> Maybe Credentials+  -> PersistAction+  -> IO (Async (Maybe PersistActionException))+persistOperation exec grp stream cred tpe+  = do m <- mailboxNew+       async $+         case tpe of+           PersistCreate ss+             -> do let req = _createPersistentSubscription grp stream ss+                   pkg <- createPkg createPersistentSubscriptionCmd cred req+                   publishWith exec (Transmit m OneTime pkg)+                   outcome <- mailboxReadDecoded m+                   case outcome of+                     Left e+                       -> throw e+                     Right resp+                       -> pure $ createRException $ getField $ cpscResult resp+           PersistUpdate ss+             -> do let req = _updatePersistentSubscription grp stream ss+                   pkg <- createPkg updatePersistentSubscriptionCmd cred req+                   publishWith exec (Transmit m OneTime pkg)+                   outcome <- mailboxReadDecoded m+                   case outcome of+                     Left e+                       -> throw e+                     Right resp+                       -> pure $ updateRException $ getField $ upscResult resp+           PersistDelete+             -> do let req = _deletePersistentSubscription grp stream+                   pkg <- createPkg deletePersistentSubscriptionCmd cred req+                   publishWith exec (Transmit m OneTime pkg)+                   outcome <- mailboxReadDecoded m+                   case outcome of+                     Left e+                       -> throw e+                     Right resp+                       -> pure $ deleteRException $ getField $ dpscResult resp  ---------------------------------------------------------------------------------createPersist :: Text-              -> Text-              -> PersistentSubscriptionSettings-              -> Maybe Credentials-              -> Operation (Maybe PersistActionException)-createPersist grp stream ss cred =-  persistOperation grp stream cred (PersistCreate ss)+createPersist+  :: Exec+  -> Text+  -> Text+  -> PersistentSubscriptionSettings+  -> Maybe Credentials+  -> IO (Async (Maybe PersistActionException))+createPersist exec grp stream ss cred+  = persistOperation exec grp stream cred (PersistCreate ss)  ---------------------------------------------------------------------------------updatePersist :: Text-              -> Text-              -> PersistentSubscriptionSettings-              -> Maybe Credentials-              -> Operation (Maybe PersistActionException)-updatePersist grp stream ss cred =-  persistOperation grp stream cred (PersistUpdate ss)+updatePersist+  :: Exec+  -> Text+  -> Text+  -> PersistentSubscriptionSettings+  -> Maybe Credentials+  -> IO (Async (Maybe PersistActionException))+updatePersist exec grp stream ss cred+  = persistOperation exec grp stream cred (PersistUpdate ss)  ---------------------------------------------------------------------------------deletePersist :: Text-              -> Text-              -> Maybe Credentials-              -> Operation (Maybe PersistActionException)-deletePersist grp stream cred = persistOperation grp stream cred PersistDelete+deletePersist+  :: Exec+  -> Text+  -> Text+  -> Maybe Credentials+  -> IO (Async (Maybe PersistActionException))+deletePersist exec grp stream cred+  = persistOperation exec grp stream cred PersistDelete
Database/EventStore/Internal/Operation/ReadAllEvents.hs view
@@ -22,6 +22,9 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Read.Common import Database.EventStore.Internal.Operation.ReadAllEvents.Message@@ -32,35 +35,43 @@  -------------------------------------------------------------------------------- -- | Batch read on $all stream operation.-readAllEvents :: Settings-              -> Int64-              -> Int64-              -> Int32-              -> Bool-              -> ReadDirection-              -> Maybe Credentials-              -> Operation AllSlice-readAllEvents Settings{..} c_pos p_pos max_c tos dir cred = construct $ do-    let msg = newRequest c_pos p_pos max_c tos s_requireMaster-        cmd = case dir of-            Forward  -> readAllEventsForwardCmd-            Backward -> readAllEventsBackwardCmd+readAllEvents+  :: Settings+  -> Exec+  -> Int64+  -> Int64+  -> Int32+  -> Bool+  -> ReadDirection+  -> Maybe Credentials+  -> IO (Async AllSlice)+readAllEvents Settings{..} exec c_pos p_pos max_c tos dir cred+  = do m <- mailboxNew+       async $+         do let req = newRequest c_pos p_pos max_c tos s_requireMaster+                cmd =+                  case dir of+                    Forward  -> readAllEventsForwardCmd+                    Backward -> readAllEventsBackwardCmd -        resp_cmd = case dir of-            Forward  -> readAllEventsForwardCompletedCmd-            Backward -> readAllEventsBackwardCompletedCmd-    resp <- send cmd resp_cmd cred msg-    let r      = getField $ _Result resp-        err    = getField $ _Error resp-        nc_pos = getField $ _NextCommitPosition resp-        np_pos = getField $ _NextPreparePosition resp-        es     = getField $ _Events resp-        evts   = fmap newResolvedEventFromBuf es-        eos    = null evts-        n_pos  = Position nc_pos np_pos-        slice  =-            if eos then SliceEndOfStream else Slice evts (Just n_pos)-    case fromMaybe SUCCESS r of-        ERROR         -> serverError err-        ACCESS_DENIED -> accessDenied All-        _             -> yield slice+            pkg <- createPkg cmd cred req+            publishWith exec (Transmit m OneTime pkg)+            outcome <- mailboxReadDecoded m+            case outcome of+              Left e+                -> throw e+              Right resp+                -> let r = getField $ _Result resp+                       err = getField $ _Error resp+                       nc_pos = getField $ _NextCommitPosition resp+                       np_pos = getField $ _NextPreparePosition resp+                       es = getField $ _Events resp+                       evts = fmap newResolvedEventFromBuf es+                       eos = null evts+                       n_pos = Position nc_pos np_pos+                       slice =+                           if eos then SliceEndOfStream else Slice evts (Just n_pos) in+                   case fromMaybe SUCCESS r of+                     ERROR -> throw $ ServerError err+                     ACCESS_DENIED -> throw $ AccessDenied All+                     _ -> pure slice
Database/EventStore/Internal/Operation/ReadEvent.hs view
@@ -24,6 +24,9 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.ReadEvent.Message import Database.EventStore.Internal.Operation.Read.Common@@ -47,24 +50,36 @@  -------------------------------------------------------------------------------- -- | Read a specific event given event number operation.-readEvent :: Settings-          -> Text-          -> Int64-          -> Bool-          -> Maybe Credentials-          -> Operation (ReadResult EventNumber ReadEvent)-readEvent Settings{..} s evtn tos cred = construct $ do-    let msg = newRequest s evtn tos s_requireMaster-    resp <- send readEventCmd readEventCompletedCmd cred msg-    let r         = getField $ _result resp-        evt       = newResolvedEvent $ getField $ _indexedEvent resp-        err       = getField $ _error resp-        not_found = ReadSuccess $ ReadEventNotFound s evtn-        found     = ReadSuccess $ ReadEvent s evtn evt-    case r of-        NOT_FOUND      -> yield not_found-        NO_STREAM      -> yield ReadNoStream-        STREAM_DELETED -> yield $ ReadStreamDeleted $ StreamName s-        ERROR          -> yield (ReadError err)-        ACCESS_DENIED  -> yield $ ReadAccessDenied $ StreamName s-        SUCCESS        -> yield found+readEvent+  :: Settings+  -> Exec+  -> Text+  -> Int64+  -> Bool+  -> Maybe Credentials+  -> IO (Async (ReadResult EventNumber ReadEvent))+readEvent Settings{..} exec stream evtn tos creds+  = do m <- mailboxNew+       async $+         do let req = newRequest stream evtn tos s_requireMaster+            pkg <- createPkg readEventCmd creds req+            publishWith exec (Transmit m OneTime pkg)+            outcome <- mailboxReadDecoded m+            case outcome of+              Left e+                -> throw e+              Right resp+                -> let r = getField $ _result resp+                       evt = newResolvedEvent $ getField $ _indexedEvent resp+                       err = getField $ _error resp+                       notFound = ReadSuccess $ ReadEventNotFound stream evtn+                       found = ReadSuccess $ ReadEvent stream evtn evt in+                   case r of+                     NOT_FOUND      -> pure notFound+                     NO_STREAM      -> pure ReadNoStream+                     STREAM_DELETED -> pure $ ReadStreamDeleted $ StreamName stream+                     ERROR          -> pure (ReadError err)+                     ACCESS_DENIED  -> pure $ ReadAccessDenied $ StreamName stream+                     SUCCESS        -> pure found++
Database/EventStore/Internal/Operation/ReadStreamEvents.hs view
@@ -22,6 +22,9 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Read.Common import Database.EventStore.Internal.Operation.ReadStreamEvents.Message@@ -32,40 +35,46 @@  -------------------------------------------------------------------------------- -- | Batch read from a regular stream operation.-readStreamEvents :: Settings-                 -> ReadDirection-                 -> Text-                 -> Int64-                 -> Int32-                 -> Bool-                 -> Maybe Credentials-                 -> Operation (ReadResult EventNumber StreamSlice)-readStreamEvents Settings{..} dir s st cnt tos cred = construct $ do-    let req_cmd =-            case dir of-                Forward  -> readStreamEventsForwardCmd-                Backward -> readStreamEventsBackwardCmd-        resp_cmd =-            case dir of-                Forward  -> readStreamEventsForwardCompletedCmd-                Backward -> readStreamEventsBackwardCompletedCmd+readStreamEvents+  :: Settings+  -> Exec+  -> ReadDirection+  -> Text+  -> Int64+  -> Int32+  -> Bool+  -> Maybe Credentials+  -> IO (Async (ReadResult EventNumber StreamSlice))+readStreamEvents Settings{..} exec dir stream st cnt tos cred+  = do m <- mailboxNew+       async $+         do let reqCmd =+                  case dir of+                    Forward  -> readStreamEventsForwardCmd+                    Backward -> readStreamEventsBackwardCmd -        msg = newRequest s st cnt tos s_requireMaster-    resp <- send req_cmd resp_cmd cred msg-    let r     = getField $ _result resp-        es    = getField $ _events resp-        evts  = fmap newResolvedEvent es-        err   = getField $ _error resp-        eos   = getField $ _endOfStream resp-        nxt   = getField $ _nextNumber resp-        found =-            if null evts && eos-            then SliceEndOfStream-            else Slice evts (if eos then Nothing else Just $ EventNumber nxt)-    case r of-        NO_STREAM      -> yield ReadNoStream-        STREAM_DELETED -> yield $ ReadStreamDeleted $ StreamName s-        NOT_MODIFIED   -> yield ReadNotModified-        ERROR          -> yield (ReadError err)-        ACCESS_DENIED  -> yield $ ReadAccessDenied $ StreamName s-        SUCCESS        -> yield (ReadSuccess found)+                req = newRequest stream st cnt tos s_requireMaster+            pkg <- createPkg reqCmd cred req+            publishWith exec (Transmit m OneTime pkg)+            outcome <- mailboxReadDecoded m+            case outcome of+              Left e+                -> throw e+              Right resp+                -> let r     = getField $ _result resp+                       es    = getField $ _events resp+                       evts  = fmap newResolvedEvent es+                       err   = getField $ _error resp+                       eos   = getField $ _endOfStream resp+                       nxt   = getField $ _nextNumber resp+                       found =+                           if null evts && eos+                           then SliceEndOfStream+                           else Slice evts (if eos then Nothing else Just $ EventNumber nxt) in+                   case r of+                     NO_STREAM -> pure ReadNoStream+                     STREAM_DELETED -> pure $ ReadStreamDeleted $ StreamName stream+                     NOT_MODIFIED -> pure ReadNotModified+                     ERROR -> pure (ReadError err)+                     ACCESS_DENIED -> pure $ ReadAccessDenied $ StreamName stream+                     SUCCESS -> pure (ReadSuccess found)
Database/EventStore/Internal/Operation/StreamMetadata.hs view
@@ -26,6 +26,7 @@ import Data.Aeson (decode)  --------------------------------------------------------------------------------+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Read.Common import Database.EventStore.Internal.Operation.ReadEvent@@ -41,33 +42,37 @@  -------------------------------------------------------------------------------- -- | Read stream metadata operation.-readMetaStream :: Settings-               -> Text-               -> Maybe Credentials-               -> Operation StreamMetadataResult-readMetaStream setts s cred = construct $ do-    traversing go <~ readEvent setts (metaStream s) (-1) False cred-  where-    go tmp =-        onReadResult tmp $ \n e_num evt -> do-            let bytes = recordedEventData $ resolvedEventOriginal evt-            case decode $ fromStrict bytes of-                Just pv -> pure $ StreamMetadataResult n e_num pv-                Nothing -> Failed invalidFormat+readMetaStream+  :: Settings+  -> Exec+  -> Text+  -> Maybe Credentials+  -> IO (Async StreamMetadataResult)+readMetaStream setts exec s cred+  = async $+      do as <- readEvent setts exec (metaStream s) (-1) False cred+         tmp <- wait as+         onReadResult tmp $ \n evtNum evt ->+           do let bytes = recordedEventData $ resolvedEventOriginal evt+              case decode $ fromStrict bytes of+                Just pv -> pure $ StreamMetadataResult n evtNum pv+                Nothing -> throw invalidFormat  -------------------------------------------------------------------------------- -- | Set stream metadata operation.-setMetaStream :: Settings-              -> Text-              -> ExpectedVersion-              -> Maybe Credentials-              -> StreamMetadata-              -> Operation WriteResult-setMetaStream setts s v cred meta =-    let stream = metaStream s+setMetaStream+  :: Settings+  -> Exec+  -> Text+  -> ExpectedVersion+  -> Maybe Credentials+  -> StreamMetadata+  -> IO (Async WriteResult)+setMetaStream setts exec s v cred meta+  = let stream = metaStream s         json   = streamMetadataJSON meta         evt    = createEvent StreamMetadataType Nothing (withJson json) in-     writeEvents setts stream v cred [evt]+    writeEvents setts exec stream v cred [evt]  -------------------------------------------------------------------------------- invalidFormat :: OperationError@@ -79,14 +84,15 @@  -------------------------------------------------------------------------------- onReadResult :: ReadResult EventNumber ReadEvent-             -> (Text -> Int64 -> ResolvedEvent -> Execution a)-             -> Execution a+             -> (Text -> Int64 -> ResolvedEvent -> IO a)+             -> IO a onReadResult (ReadSuccess r) k =     case r of       ReadEvent s n e -> k s n e-      _               -> Failed streamNotFound-onReadResult ReadNoStream _          = Failed streamNotFound-onReadResult (ReadStreamDeleted s) _ = Failed $ StreamDeleted s-onReadResult ReadNotModified _       = Failed $ ServerError Nothing-onReadResult (ReadError e) _         = Failed $ ServerError e-onReadResult (ReadAccessDenied s) _  = Failed $ AccessDenied s+      _ -> throw streamNotFound++onReadResult ReadNoStream _          = throw streamNotFound+onReadResult (ReadStreamDeleted s) _ = throw $ StreamDeleted s+onReadResult ReadNotModified _       = throw $ ServerError Nothing+onReadResult (ReadError e) _         = throw $ ServerError e+onReadResult (ReadAccessDenied s) _  = throw $ AccessDenied s
Database/EventStore/Internal/Operation/Transaction.hs view
@@ -29,6 +29,10 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Exec (Exec)+import Database.EventStore.Internal.Operation (OpResult(..)) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Transaction.Message import Database.EventStore.Internal.Operation.Write.Common@@ -39,75 +43,109 @@  -------------------------------------------------------------------------------- -- | Start transaction operation.-transactionStart :: Settings-                 -> Text-                 -> ExpectedVersion-                 -> Maybe Credentials-                 -> Operation Int64-transactionStart Settings{..} stream exp_v cred = construct $ do-    let msg = newStart stream (expVersionInt64 exp_v) s_requireMaster-    resp <- send transactionStartCmd transactionStartCompletedCmd cred msg-    let tid = getField $ _transId resp-        r   = getField $ _result resp-    case r of-        OP_PREPARE_TIMEOUT        -> retry-        OP_FORWARD_TIMEOUT        -> retry-        OP_COMMIT_TIMEOUT         -> retry-        OP_WRONG_EXPECTED_VERSION -> wrongVersion stream exp_v-        OP_STREAM_DELETED         -> streamDeleted $ StreamName stream-        OP_INVALID_TRANSACTION    -> invalidTransaction-        OP_ACCESS_DENIED          -> accessDenied $ StreamName stream-        OP_SUCCESS                -> yield tid+transactionStart+  :: Settings+  -> Exec+  -> Text+  -> ExpectedVersion+  -> Maybe Credentials+  -> IO (Async Int64)+transactionStart Settings{..} exec stream exp_v cred+  = do m <- mailboxNew+       async $+         do let req = newStart stream (expVersionInt64 exp_v) s_requireMaster+            pkg <- createPkg transactionStartCmd cred req +            keepLooping $+              do publishWith exec (Transmit m OneTime pkg)+                 outcome <- mailboxReadDecoded m+                 case outcome of+                   Left e+                     -> throw e+                   Right resp+                     -> let tid = getField $ _transId resp+                            r   = getField $ _result resp in+                        case r of+                          OP_PREPARE_TIMEOUT -> pure Loop+                          OP_FORWARD_TIMEOUT -> pure Loop+                          OP_COMMIT_TIMEOUT -> pure Loop+                          OP_WRONG_EXPECTED_VERSION -> throw $ WrongExpectedVersion stream exp_v+                          OP_STREAM_DELETED -> throw $ StreamDeleted $ StreamName stream+                          OP_INVALID_TRANSACTION -> throw InvalidTransaction+                          OP_ACCESS_DENIED -> throw $ AccessDenied $ StreamName stream+                          OP_SUCCESS -> pure $ Break tid+ -------------------------------------------------------------------------------- -- | Transactional write operation.-transactionWrite :: Settings-                 -> Text-                 -> ExpectedVersion-                 -> Int64-                 -> [Event]-                 -> Maybe Credentials-                 -> Operation ()-transactionWrite Settings{..} stream exp_v trans_id evts cred = construct $ do-    nevts <- traverse eventToNewEvent evts-    let msg = newWrite trans_id nevts s_requireMaster-    resp <- send transactionWriteCmd transactionWriteCompletedCmd cred msg-    let r = getField $ _wwResult resp-    case r of-        OP_PREPARE_TIMEOUT        -> retry-        OP_FORWARD_TIMEOUT        -> retry-        OP_COMMIT_TIMEOUT         -> retry-        OP_WRONG_EXPECTED_VERSION -> wrongVersion stream exp_v-        OP_STREAM_DELETED         -> streamDeleted $ StreamName stream-        OP_INVALID_TRANSACTION    -> invalidTransaction-        OP_ACCESS_DENIED          -> accessDenied $ StreamName stream-        OP_SUCCESS                -> yield ()+transactionWrite+  :: Settings+  -> Exec+  -> Text+  -> ExpectedVersion+  -> Int64+  -> [Event]+  -> Maybe Credentials+  -> IO (Async ())+transactionWrite Settings{..} exec stream exp_v trans_id evts cred+  = do m <- mailboxNew+       async $+         do nevts <- traverse eventToNewEventIO evts+            let req = newWrite trans_id nevts s_requireMaster+            pkg <- createPkg transactionWriteCmd cred req+            keepLooping $+              do publishWith exec (Transmit m OneTime pkg)+                 outcome <- mailboxReadDecoded m+                 case outcome of+                   Left e+                     -> throw e+                   Right resp+                     -> let r = getField $ _wwResult resp in+                        case r of+                          OP_PREPARE_TIMEOUT -> pure Loop+                          OP_FORWARD_TIMEOUT -> pure Loop+                          OP_COMMIT_TIMEOUT -> pure Loop+                          OP_WRONG_EXPECTED_VERSION -> throw $ WrongExpectedVersion stream exp_v+                          OP_STREAM_DELETED -> throw $ StreamDeleted $ StreamName stream+                          OP_INVALID_TRANSACTION -> throw InvalidTransaction+                          OP_ACCESS_DENIED -> throw $ AccessDenied $ StreamName stream+                          OP_SUCCESS -> pure $ Break ()  -------------------------------------------------------------------------------- -- | Transactional commit operation.-transactionCommit :: Settings-                  -> Text-                  -> ExpectedVersion-                  -> Int64-                  -> Maybe Credentials-                  -> Operation WriteResult-transactionCommit Settings{..} stream exp_v trans_id cred = construct $ do-    let msg = newCommit trans_id s_requireMaster-    resp <- send transactionCommitCmd transactionCommitCompletedCmd cred msg-    let r = getField $ _ccResult resp-        com_pos = getField $ _commitPosition resp-        pre_pos = getField $ _preparePosition resp-        lst_num = getField $ _lastNumber resp-        p_int   = fromMaybe (-1) pre_pos-        c_int   = fromMaybe (-1) com_pos-        pos     = Position c_int p_int-        res     = WriteResult lst_num pos-    case r of-        OP_PREPARE_TIMEOUT        -> retry-        OP_FORWARD_TIMEOUT        -> retry-        OP_COMMIT_TIMEOUT         -> retry-        OP_WRONG_EXPECTED_VERSION -> wrongVersion stream exp_v-        OP_STREAM_DELETED         -> streamDeleted $ StreamName stream-        OP_INVALID_TRANSACTION    -> invalidTransaction-        OP_ACCESS_DENIED          -> accessDenied $ StreamName stream-        OP_SUCCESS                -> yield res+transactionCommit+  :: Settings+  -> Exec+  -> Text+  -> ExpectedVersion+  -> Int64+  -> Maybe Credentials+  -> IO (Async WriteResult)+transactionCommit Settings{..} exec stream exp_v trans_id cred+  = do m <- mailboxNew+       async $+         do let req = newCommit trans_id s_requireMaster+            pkg <- createPkg transactionCommitCmd cred req+            keepLooping $+              do publishWith exec (Transmit m OneTime pkg)+                 outcome <- mailboxReadDecoded m+                 case outcome of+                   Left e+                     -> throw e+                   Right resp+                     -> let r = getField $ _ccResult resp+                            com_pos = getField $ _commitPosition resp+                            pre_pos = getField $ _preparePosition resp+                            lst_num = getField $ _lastNumber resp+                            p_int = fromMaybe (-1) pre_pos+                            c_int = fromMaybe (-1) com_pos+                            pos = Position c_int p_int+                            res = WriteResult lst_num pos in+                        case r of+                          OP_PREPARE_TIMEOUT -> pure Loop+                          OP_FORWARD_TIMEOUT -> pure Loop+                          OP_COMMIT_TIMEOUT -> pure Loop+                          OP_WRONG_EXPECTED_VERSION -> throw $ WrongExpectedVersion stream exp_v+                          OP_STREAM_DELETED -> throw $ StreamDeleted $ StreamName stream+                          OP_INVALID_TRANSACTION -> throw InvalidTransaction+                          OP_ACCESS_DENIED -> throw $ AccessDenied $ StreamName stream+                          OP_SUCCESS -> pure $ Break res
Database/EventStore/Internal/Operation/Volatile.hs view
@@ -17,6 +17,9 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Exec (Exec) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Settings@@ -26,57 +29,49 @@ import Database.EventStore.Internal.Types  ---------------------------------------------------------------------------------volatile :: StreamId t -> Bool -> Maybe Credentials -> Operation SubAction-volatile streamId tos cred = construct (issueRequest stream tos cred)-  where-    stream = streamIdRaw streamId-----------------------------------------------------------------------------------issueRequest :: Text -> Bool -> Maybe Credentials -> Code SubAction ()-issueRequest stream tos cred = do-  let req = subscribeToStream stream tos-  request subscribeToStreamCmd cred req-    [ Expect subscriptionDroppedCmd $ \_ d ->-        handleDropped d-    , Expect subscriptionConfirmationCmd $ \sid c -> do-        let lcp     = getField $ subscribeLastCommitPos c-            len     = getField $ subscribeLastEventNumber c-            details =-              SubDetails-              { subId           = sid-              , subCommitPos    = lcp-              , subLastEventNum = len-              , subSubId        = Nothing-              }-        yield (Confirmed details)-        live sid-    ]-----------------------------------------------------------------------------------eventAppeared :: StreamEventAppeared -> Code SubAction ()-eventAppeared e = do-  let evt = newResolvedEventFromBuf $ getField $ streamResolvedEvent e-  yield (Submit evt)+volatile+  :: Exec+  -> StreamId t+  -> Bool+  -> Maybe Credentials+  -> IO (UUID, Chan SubAction)+volatile exec streamId tos cred+  = do m <- mailboxNew+       subM <- newChan+       let req = subscribeToStream stream tos+       pkg <- createPkg subscribeToStreamCmd cred req+       let theSubId = packageCorrelation pkg+       publishWith exec (Transmit m (KeepAlive subscriptionDroppedCmd) pkg)+       _ <- async $ keepLooping $+         do outcome <- mailboxRead m+            case outcome of+              Left _+                -> Break () <$ writeChan subM (Dropped SubAborted)+              Right respPkg+                | packageCmd respPkg == subscriptionDroppedCmd+                -> let Right resp = decodePkg respPkg+                       reason = fromMaybe D_Unsubscribed (getField $ dropReason resp)+                       subReason = toSubDropReason reason in+                   Break () <$ writeChan subM (Dropped subReason)+                | packageCmd respPkg == subscriptionConfirmationCmd+                -> let Right resp = decodePkg respPkg+                       lcp = getField $ subscribeLastCommitPos resp+                       len = getField $ subscribeLastEventNumber resp+                       details =+                         SubDetails+                         { subId = theSubId+                         , subCommitPos = lcp+                         , subLastEventNum = len+                         , subSubId = Nothing+                         } in+                   Loop <$ writeChan subM (Confirmed details)+                | packageCmd respPkg == streamEventAppearedCmd+                -> let Right resp = decodePkg respPkg+                       evt = newResolvedEventFromBuf $ getField $ streamResolvedEvent resp in+                   Loop <$ writeChan subM (Submit evt)+                | otherwise+                -> pure Loop ----------------------------------------------------------------------------------live :: UUID -> Code SubAction ()-live subscriptionId = loop+       pure (theSubId, subM)   where-    loop =-      waitForOr subscriptionId connectionReset-        [ Expect subscriptionDroppedCmd $ \_ d ->-            handleDropped d-        , Expect streamEventAppearedCmd $ \_ e -> do-            eventAppeared e-            loop-        ]-----------------------------------------------------------------------------------connectionReset :: Code SubAction ()-connectionReset = yield ConnectionReset-----------------------------------------------------------------------------------handleDropped :: SubscriptionDropped -> Code SubAction ()-handleDropped d = do-  let reason = fromMaybe D_Unsubscribed (getField $ dropReason d)-  yield (Dropped $ toSubDropReason reason)+    stream = streamIdRaw streamId
Database/EventStore/Internal/Operation/Write/Common.hs view
@@ -15,7 +15,7 @@ import Data.Int  ---------------------------------------------------------------------------------import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Control (freshUUID) import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types @@ -32,9 +32,9 @@  -------------------------------------------------------------------------------- -- | Constructs a 'NewEvent' from an 'Event'.-eventToNewEvent :: Event -> Code o NewEvent-eventToNewEvent evt = do-    uuid <- maybe freshId return evt_id+eventToNewEventIO :: Event -> IO NewEvent+eventToNewEventIO evt = do+    uuid <- maybe freshUUID pure evt_id     return $ newEvent evt_type                       uuid                       evt_data_type
Database/EventStore/Internal/Operation/WriteEvents.hs view
@@ -21,6 +21,10 @@  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Communication (Transmit(..))+import Database.EventStore.Internal.Control (publishWith)+import Database.EventStore.Internal.Exec+import Database.EventStore.Internal.Operation (OpResult(..)) import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Write.Common import Database.EventStore.Internal.Operation.WriteEvents.Message@@ -29,32 +33,45 @@ import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types ------------------------------------------------------------------------------------ | Write events operation.-writeEvents :: Settings-            -> Text-            -> ExpectedVersion-            -> Maybe Credentials-            -> [Event]-            -> Operation WriteResult-writeEvents Settings{..} s v cred evts = construct $ do-    nevts <- traverse eventToNewEvent evts-    let msg = newRequest s (expVersionInt64 v) nevts s_requireMaster-    resp <- send writeEventsCmd writeEventsCompletedCmd cred msg-    let r            = getField $ _result resp-        com_pos      = getField $ _commitPosition resp-        prep_pos     = getField $ _preparePosition resp-        lst_num      = getField $ _lastNumber resp-        com_pos_int  = fromMaybe (-1) com_pos-        prep_pos_int = fromMaybe (-1) prep_pos-        pos          = Position com_pos_int prep_pos_int-        res          = WriteResult lst_num pos-    case r of-        OP_SUCCESS                -> yield res-        OP_PREPARE_TIMEOUT        -> retry-        OP_FORWARD_TIMEOUT        -> retry-        OP_COMMIT_TIMEOUT         -> retry-        OP_WRONG_EXPECTED_VERSION -> wrongVersion s v-        OP_STREAM_DELETED         -> streamDeleted $ StreamName s-        OP_INVALID_TRANSACTION    -> invalidTransaction-        OP_ACCESS_DENIED          -> accessDenied (StreamName s)+-------------------------------------------------------------------------------+writeEvents+  :: Settings+  -> Exec+  -> Text+  -> ExpectedVersion+  -> Maybe Credentials+  -> [Event]+  -> IO (Async WriteResult)+writeEvents setts exec stream version creds evts+  = do m <- mailboxNew+       async $+         do nevts <- traverse eventToNewEventIO evts+            let req = newRequest stream (expVersionInt64 version) nevts (s_requireMaster setts)++            pkg <- createPkg writeEventsCmd creds req++            keepLooping $ do+              publishWith exec (Transmit m OneTime pkg)+              outcome <- mailboxReadDecoded m+              case outcome of+                Left e+                  -> throw e+                Right resp+                  -> let r = getField $ _result resp+                         com_pos = getField $ _commitPosition resp+                         prep_pos = getField $ _preparePosition resp+                         lst_num = getField $ _lastNumber resp+                         com_pos_int = fromMaybe (-1) com_pos+                         prep_pos_int = fromMaybe (-1) prep_pos+                         pos = Position com_pos_int prep_pos_int+                         res = WriteResult lst_num pos in+                     case r of+                         OP_SUCCESS -> pure $ Break res+                         OP_PREPARE_TIMEOUT -> pure Loop+                         OP_FORWARD_TIMEOUT -> pure Loop+                         OP_COMMIT_TIMEOUT -> pure Loop+                         OP_WRONG_EXPECTED_VERSION -> throw $ WrongExpectedVersion stream version+                         OP_STREAM_DELETED -> throw $ StreamDeleted $ StreamName stream+                         OP_INVALID_TRANSACTION -> throw InvalidTransaction+                         OP_ACCESS_DENIED -> throw $ AccessDenied (StreamName stream)+
− Database/EventStore/Internal/OperationManager.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.OperationManager--- Copyright : (C) 2017 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable-------------------------------------------------------------------------------------module Database.EventStore.Internal.OperationManager-  ( Manager-  , Decision(..)-  , new-  , submit-  , handle-  , cleanup-  , check-  ) where-----------------------------------------------------------------------------------import Database.EventStore.Internal.Callback-import Database.EventStore.Internal.Connection-import Database.EventStore.Internal.Control-import Database.EventStore.Internal.Logger-import Database.EventStore.Internal.Manager.Operation.Registry-import Database.EventStore.Internal.Operation-import Database.EventStore.Internal.Prelude-import Database.EventStore.Internal.Types-----------------------------------------------------------------------------------data Manager = Manager { _reg :: Registry }-----------------------------------------------------------------------------------new :: ConnectionRef -> IO Manager-new = fmap Manager . newRegistry-----------------------------------------------------------------------------------submit :: Manager -> Operation a -> Callback a -> EventStore ()-submit Manager{..} op cb = register _reg op cb-----------------------------------------------------------------------------------handle :: Manager -> Package -> EventStore (Maybe Decision)-handle Manager{..} pkg = handlePackage _reg pkg-----------------------------------------------------------------------------------cleanup :: Manager -> EventStore ()-cleanup Manager{..} = do-  $(logInfo) "Cleaning up pending requests..."-  abortPendingRequests _reg-  $(logInfo) "Cleanup done successfully."-----------------------------------------------------------------------------------check :: Manager -> EventStore ()-check Manager{..} = do-  checkAndRetry _reg-  startAwaitings _reg
Database/EventStore/Internal/Settings.hs view
@@ -13,7 +13,6 @@  -------------------------------------------------------------------------------- import Network.Connection (TLSSettings)-import System.Metrics (Store)  -------------------------------------------------------------------------------- import Database.EventStore.Internal.Logger@@ -99,8 +98,10 @@         -- ^ Delay in which an operation will be retried if no response arrived.       , s_operationRetry :: Retry         -- ^ Retry strategy when an operation timeout.-      , s_monitoring :: Maybe Store-        -- ^ EKG metric store.+      , s_monitoring :: MonitoringBackend+        -- ^ Monitoring backend abstraction. You could implement one targetting+        --   `ekg-core` for example. We will expose an `ekg-core` implementation+        --   as soon as `ekg-core` supports GHC 8.8.*.       , s_defaultConnectionName :: Maybe Text         -- ^ Default connection name.       , s_defaultUserCredentials :: Maybe Credentials@@ -122,7 +123,7 @@ --   * 's_loggerDetailed'         = 'False' --   * 's_operationTimeout'       = 10 seconds --   * 's_operationRetry'         = 'atMost' 3---   * 's_monitoring'             = 'Nothing'+--   * 's_monitoring'             = 'noopMonitoringBackend' --   * 's_defaultConnectionName'  = 'Nothing' --   * 's_defaultUserCredentials' = 'Nothing' defaultSettings :: Settings@@ -138,7 +139,7 @@                    , s_loggerDetailed         = False                    , s_operationTimeout       = 10 -- secs                    , s_operationRetry         = atMost 3-                   , s_monitoring             = Nothing+                   , s_monitoring             = noopMonitoringBackend                    , s_defaultConnectionName  = Nothing                    , s_defaultUserCredentials = Nothing                    }@@ -152,3 +153,40 @@ -- | Millisecond timespan msDiffTime :: Float -> NominalDiffTime msDiffTime n = fromRational $ toRational (n / 1000)++--------------------------------------------------------------------------------+-- | Monitoring backend abstraction. Gathers all the metrics currently tracked+--   by the client. Used only by the TCP interface. Be careful as+--   'MonitoringBackend' is used in a very tight loop. Each+--   function must not throw any exception or the client will end in a broken+--   state.+data MonitoringBackend =+    MonitoringBackend+    { monitoringBackendIncrPkgCount :: IO ()+      -- ^ Called every time a TCP package is sent. We mean high-level TCP+      --   package, used in EventStore TCP protocol.+    , monitoringBackendIncrConnectionDrop :: IO ()+      -- ^ Called every time the client has lost the connection.+    , monitoringBackendAddDataTransmitted :: Int -> IO ()+      -- ^ When the client sends a TCP package, it calls that function by+      --   passing the size of the payload. The goal is to have a distrubtion+      --   of the amount of data exchanged with the server.+    , monitoringBackendIncrForceReconnect :: IO ()+      -- ^ Called every time the client is asked by a node to connect to+      --   another node. It happens only in cluster connection setting.+    , monitoringBackendIncrHeartbeatTimeouts :: IO ()+      -- ^ Called every time the client detects a heartbeat timeout from the+      --   server.+    }++--------------------------------------------------------------------------------+-- | A 'MonitoringBackend' that does nothing.+noopMonitoringBackend :: MonitoringBackend+noopMonitoringBackend =+    MonitoringBackend+    { monitoringBackendIncrPkgCount = pure ()+    , monitoringBackendIncrConnectionDrop = pure ()+    , monitoringBackendAddDataTransmitted = const (pure ())+    , monitoringBackendIncrForceReconnect = pure ()+    , monitoringBackendIncrHeartbeatTimeouts = pure ()+    }
Database/EventStore/Internal/Subscription/Api.hs view
@@ -16,38 +16,23 @@ module Database.EventStore.Internal.Subscription.Api where  ---------------------------------------------------------------------------------import Database.EventStore.Internal.Callback-import Database.EventStore.Internal.Communication-import Database.EventStore.Internal.Control+import           Streaming+import qualified Streaming.Prelude as Streaming++-------------------------------------------------------------------------------- import Database.EventStore.Internal.Types import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Stream-import Database.EventStore.Internal.Subscription.Packages import Database.EventStore.Internal.Subscription.Types  ---------------------------------------------------------------------------------submit :: Callback SubAction -> ResolvedEvent -> IO ()-submit s xs = fulfill s (Submit xs)-----------------------------------------------------------------------------------dropped :: Callback SubAction -> SubDropReason -> IO ()-dropped s r = fulfill s (Dropped r)-----------------------------------------------------------------------------------confirmed :: Callback SubAction -> SubDetails -> IO ()-confirmed s d = fulfill s (Confirmed d)---------------------------------------------------------------------------------- -- | Common operations supported by a subscription. class Subscription s where-  -- | Asks for the next incoming event like 'nextEventMaybe' while still being-  --   in the the 'STM'.-  nextEventMaybeSTM :: s -> STM (Maybe ResolvedEvent)--  -- | Returns the runtime details of a subscription.-  getSubscriptionDetailsSTM :: s -> STM SubDetails+  -- | Asks for the next subcription event. If that function is called after+  --   a SubDropped event, expect it to hang indefinitely.+  nextSubEvent :: s -> IO SubAction -  -- | Asynchronously unsubscribe from the the stream.+  -- | Asynchronously unsubscribe from a subscription.   unsubscribe :: s -> IO ()  --------------------------------------------------------------------------------@@ -56,62 +41,22 @@     subscriptionStream :: s -> StreamId t  ----------------------------------------------------------------------------------- | Awaits for the next event.-nextEvent :: Subscription s => s -> IO ResolvedEvent-nextEvent s = atomically $ do-  outcome <- nextEventMaybeSTM s-  case outcome of-    Just e  -> return e-    Nothing -> retrySTM------------------------------------------------------------------------------------- | Non blocking version of 'nextEvent'.-nextEventMaybe :: Subscription s => s -> IO (Maybe ResolvedEvent)-nextEventMaybe = atomically . nextEventMaybeSTM------------------------------------------------------------------------------------- | Waits until the `Subscription` has been confirmed.-waitConfirmation :: Subscription s => s -> IO ()-waitConfirmation s = atomically $ do-    _ <- getSubscriptionDetailsSTM s-    return ()------------------------------------------------------------------------------------- | Like 'unsubscribeConfirmed' but lives in 'STM' monad.-unsubscribeConfirmedSTM :: Subscription s => s -> STM Bool-unsubscribeConfirmedSTM s = do-  let action = do-        _ <- getSubscriptionDetailsSTM s-        return False-  catchSTM action $ \(_ :: SomeException) -> return True------------------------------------------------------------------------------------- | Non blocking version of `waitUnsubscribeConfirmed`.-unsubscribeConfirmed :: Subscription s => s -> IO Bool-unsubscribeConfirmed = atomically . unsubscribeConfirmedSTM------------------------------------------------------------------------------------- | Wait until unsubscription has been confirmed by the server.-waitUnsubscribeConfirmed :: Subscription s => s -> IO ()-waitUnsubscribeConfirmed s = atomically $-    unlessM (unsubscribeConfirmedSTM s) retrySTM+-- | Streams a subscription events. The stream will end when hitting `Dropped`+--   event but will still emit it.+streamSubEvents :: Subscription s => s -> Stream (Of SubAction) IO ()+streamSubEvents s+  = do rest <- Streaming.span predicate $ Streaming.repeatM (nextSubEvent s)+       outcome <- lift $ Streaming.uncons rest+       for_ outcome $ \(dropped, _) -> Streaming.yield dropped+  where+    predicate (Dropped _) = False+    predicate _ = True  ---------------------------------------------------------------------------------subUnsubscribe :: (Pub pub, Subscription s) => pub -> s -> IO ()-subUnsubscribe pub s = do-  outcome <- atomically $ do-    unsubscribed <- unsubscribeConfirmedSTM s-    if unsubscribed-      then return Nothing-      else Just <$> getSubscriptionDetailsSTM s--  for_ outcome $ \details -> do-    let pkg = createUnsubscribePackage (subId details)-    publishWith pub (SendPackage pkg)+-- | Like `streamSubEvent` but will only emit `ResolvedEvent`.+streamSubResolvedEvents :: Subscription s => s -> Stream (Of ResolvedEvent) IO ()+streamSubResolvedEvents = Streaming.mapMaybe go . streamSubEvents+  where+    go (Submit e) = Just e+    go _ = Nothing ------------------------------------------------------------------------------------ | Gets the ID of the subscription.-getSubscriptionId :: Subscription s => s -> IO SubscriptionId-getSubscriptionId s = atomically $ do-  details <- getSubscriptionDetailsSTM s-  return (SubscriptionId $ subId details)
Database/EventStore/Internal/Subscription/Catchup.hs view
@@ -18,30 +18,21 @@ module Database.EventStore.Internal.Subscription.Catchup where  ---------------------------------------------------------------------------------import Control.Monad.Fix import Safe (fromJustNote)  ---------------------------------------------------------------------------------import Database.EventStore.Internal.Callback import Database.EventStore.Internal.Communication import Database.EventStore.Internal.Control import Database.EventStore.Internal.Exec-import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Catchup-import Database.EventStore.Internal.Operation.Volatile import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Subscription.Api import Database.EventStore.Internal.Subscription.Types+import Database.EventStore.Internal.Subscription.Packages import Database.EventStore.Internal.Types  ---------------------------------------------------------------------------------data Phase-  = CatchingUp-  | Running SubDetails-  | Closed (Either SomeException SubDropReason)---------------------------------------------------------------------------------- receivedAlready :: StreamId t -> t -> ResolvedEvent -> Bool receivedAlready StreamName{} old e =     EventNumber (resolvedEventOriginalEventNumber e) < old@@ -72,108 +63,48 @@ --   then any events subsequently written until such time as the subscription is --   dropped or closed. data CatchupSubscription t =-  CatchupSubscription { _catchupExec   :: Exec-                      , _catchupStream :: StreamId t-                      , _catchupPhase  :: TVar Phase-                      , _catchupTrack  :: TVar t-                      , _catchupNext   :: STM (Maybe ResolvedEvent)-                      }+  CatchupSubscription+    { _catchupExec :: Exec+    , _catchupStream :: StreamId t+    , _catchupSub :: TVar (Maybe UUID)+    , _catchupChan :: Chan SubAction+    }  ---------------------------------------------------------------------------------instance Subscription (CatchupSubscription t) where-  nextEventMaybeSTM = _catchupNext+instance Subscription (CatchupSubscription s) where+  nextSubEvent s = readChan (_catchupChan s) -  getSubscriptionDetailsSTM s = do-    p <- readTVar (_catchupPhase s)-    case p of-      Running details -> return details-      Closed r        -> throwClosed r-      _               -> retrySTM+  unsubscribe s+    = do subId <- atomically $+           do idMay <- readTVar (_catchupSub s)+              case idMay of+                Nothing -> retrySTM+                Just sid -> pure sid -  unsubscribe s = subUnsubscribe (_catchupExec s) s+         let pkg = createUnsubscribePackage subId+         publishWith (_catchupExec s) (SendPackage pkg)  -------------------------------------------------------------------------------- instance SubscriptionStream (CatchupSubscription t) t where     subscriptionStream = _catchupStream  ---------------------------------------------------------------------------------newCatchupSubscription :: Exec-                       -> Bool-                       -> Maybe Int32-                       -> Maybe Credentials-                       -> StreamId t-                       -> t-                       -> IO (CatchupSubscription t)-newCatchupSubscription exec tos batch cred streamId seed = do-  phaseVar <- newTVarIO CatchingUp-  queue    <- newTQueueIO-  track    <- newTVarIO seed--  let sub = CatchupSubscription exec streamId phaseVar track $ do-        p       <- readTVar phaseVar-        isEmpty <- isEmptyTQueue queue-        if isEmpty-          then-            case p of-              Closed r -> throwClosed r-              _        -> return Nothing-          else Just <$> readTQueue queue--      callback cb (Left e) =-        case fromException e of-          Just opE ->-            case opE of-              StreamNotFound{} -> do-                let op = volatile streamId tos cred-                publishWith exec (SubmitOperation cb op)-              _ -> atomically $ writeTVar phaseVar (Closed $ Left e)-          _ -> atomically $ writeTVar phaseVar (Closed $ Left e)-      callback _ (Right action) =-        case action of-          Confirmed details -> atomically $ writeTVar phaseVar (Running details)-          Dropped r ->-            atomically $ writeTVar phaseVar (Closed $ Right r)-          Submit e -> atomically $ do-            tracker <- readTVar track-            unless (receivedAlready streamId tracker e) $ do-              writeTVar track (nextTarget streamId e)-              writeTQueue queue e-          ConnectionReset -> do-            chk <- readTVarIO track-            let newOp = catchup (execSettings exec) streamId chk tos batch cred--            newCb <- mfix $ \self -> newCallback (callback self)-            publishWith exec (SubmitOperation newCb newOp)--  cb <- mfix $ \self -> newCallback (callback self)-  let op = catchup (execSettings exec) streamId seed tos batch cred-  publishWith exec (SubmitOperation cb op)-  return sub-----------------------------------------------------------------------------------throwClosed :: Either SomeException SubDropReason -> STM a-throwClosed (Left e)  = throwSTM e-throwClosed (Right r) = throwSTM (SubscriptionClosed $ Just r)------------------------------------------------------------------------------------- | Non blocking version of `waitTillCatchup`.-hasCaughtUp :: CatchupSubscription t -> IO Bool-hasCaughtUp sub = atomically $ hasCaughtUpSTM sub------------------------------------------------------------------------------------- | Waits until 'CatchupSubscription' subscription catch-up its stream.-waitTillCatchup :: CatchupSubscription t -> IO ()-waitTillCatchup sub = atomically $ unlessM (hasCaughtUpSTM sub) retrySTM+newCatchupSubscription+  :: Exec+  -> Bool+  -> Maybe Int32+  -> Maybe Credentials+  -> StreamId t+  -> t+  -> IO (CatchupSubscription t)+newCatchupSubscription exec tos batch cred streamId seed+  = do (var, chan) <- catchup (execSettings exec) exec streamId seed tos batch cred+       let sub =+             CatchupSubscription+             { _catchupExec = exec+             , _catchupStream = streamId+             , _catchupSub = var+             , _catchupChan = chan+             } ------------------------------------------------------------------------------------ | Like 'hasCaughtUp' but lives in 'STM' monad.-hasCaughtUpSTM :: CatchupSubscription t -> STM Bool-hasCaughtUpSTM CatchupSubscription{..} = do-  p <- readTVar _catchupPhase-  case p of-    CatchingUp -> return False-    Running{}  -> return True-    Closed tpe ->-      case tpe of-        Left e  -> throwSTM e-        Right r -> throwSTM (SubscriptionClosed $ Just r)+       pure sub
Database/EventStore/Internal/Subscription/Persistent.hs view
@@ -18,7 +18,6 @@ import Data.UUID  ---------------------------------------------------------------------------------import Database.EventStore.Internal.Callback import Database.EventStore.Internal.Communication import Database.EventStore.Internal.Control import Database.EventStore.Internal.Exec@@ -32,106 +31,78 @@ import Database.EventStore.Internal.Types  ---------------------------------------------------------------------------------data Phase-  = Pending-  | Running SubDetails-  | Closed (Either SomeException SubDropReason)---------------------------------------------------------------------------------- -- | The server remembers the state of the subscription. This allows for many --   different modes of operations compared to a regular or catchup subscription --   where the client holds the subscription state. --   (Need EventStore >= v3.1.0). data PersistentSubscription =-  PersistentSubscription { _perExec   :: Exec-                         , _perStream :: StreamName-                         , _perCred   :: Maybe Credentials-                         , _perPhase  :: TVar Phase-                         , _perNext   :: STM (Maybe ResolvedEvent)-                         }+  PersistentSubscription+  { _perExec :: Exec+  , _perSubId :: UUID+  , _perStream :: StreamName+  , _perCred :: Maybe Credentials+  , _perSubKey :: TVar (Maybe Text)+  , _perChan :: Chan SubAction+  }  -------------------------------------------------------------------------------- instance Subscription PersistentSubscription where-  nextEventMaybeSTM = _perNext--  getSubscriptionDetailsSTM s = do-    p <- readTVar (_perPhase s)-    case p of-      Pending         -> retrySTM-      Running details -> return details-      Closed outcome  ->-        case outcome of-          Right r -> throwSTM (SubscriptionClosed $ Just r)-          Left e  -> throwSTM e+  nextSubEvent s = readChan (_perChan s) -  unsubscribe s = subUnsubscribe (_perExec s) s+  unsubscribe s = publishWith (_perExec s) (SendPackage pkg)+    where+      pkg = createUnsubscribePackage (_perSubId s)  -------------------------------------------------------------------------------- instance SubscriptionStream PersistentSubscription EventNumber where     subscriptionStream = _perStream  ---------------------------------------------------------------------------------newPersistentSubscription :: Exec-                          -> Text-                          -> StreamName-                          -> Int32-                          -> Maybe Credentials-                          -> IO PersistentSubscription-newPersistentSubscription exec grp stream bufSize cred = do-  phaseVar <- newTVarIO Pending-  queue    <- newTQueueIO--  let name = streamIdRaw stream-      sub  = PersistentSubscription exec stream cred phaseVar $ do-        p       <- readTVar phaseVar-        isEmpty <- isEmptyTQueue queue-        if isEmpty-          then-            case p of-              Closed outcome ->-                case outcome of-                  Right r -> throwSTM (SubscriptionClosed $ Just r)-                  Left e  -> throwSTM e-              _ -> return Nothing-          else Just <$> readTQueue queue+newPersistentSubscription+  :: Exec+  -> Text+  -> StreamName+  -> Int32+  -> Maybe Credentials+  -> IO PersistentSubscription+newPersistentSubscription exec grp (StreamName stream) bufSize cred+  = do (subId, varSubKey, chan) <- persist exec grp stream bufSize cred+       let sub =+             PersistentSubscription+             { _perExec = exec+             , _perSubId = subId+             , _perCred = cred+             , _perSubKey = varSubKey+             , _perChan = chan+             , _perStream = StreamName stream+             } -      callback (Left e) = atomically $-          writeTVar phaseVar (Closed $ Left e)-      callback (Right action) =-        case action of-          Confirmed details -> atomically $-            writeTVar phaseVar (Running details)-          Dropped r -> atomically $-            writeTVar phaseVar (Closed $ Right r)-          Submit e -> atomically $ do-            readTVar phaseVar >>= \case-              Running{} -> writeTQueue queue e-              _         -> return ()-          ConnectionReset -> atomically $-            writeTVar phaseVar (Closed $ Right SubAborted)+       pure sub -  cb <- newCallback callback-  publishWith exec (SubmitOperation cb (persist grp name bufSize cred))-  return sub+--------------------------------------------------------------------------------+persistentGetSubKey+  :: PersistentSubscription+  -> IO Text+persistentGetSubKey sub+  = atomically $+      do subKeyMay <- readTVar (_perSubKey sub)+         case subKeyMay of+           Just key+             -> pure key+           Nothing+             -> retrySTM  -------------------------------------------------------------------------------- -- | Acknowledges those event ids have been successfully processed.-notifyEventsProcessed :: PersistentSubscription -> [UUID] -> IO ()-notifyEventsProcessed PersistentSubscription{..} evts = do-  details <- atomically $ do-    p <- readTVar _perPhase-    case p of-      Closed outcome  ->-        case outcome of-          Right r -> throwSTM (SubscriptionClosed $ Just r)-          Left e  -> throwSTM e-      Pending   -> retrySTM-      Running d -> return d--  let uuid     = subId details-      Just sid = subSubId details-      pkg      = createAckPackage _perCred uuid sid evts-  publishWith _perExec (SendPackage pkg)+notifyEventsProcessed+  :: PersistentSubscription+  -> [UUID]+  -> IO ()+notifyEventsProcessed sub evts+  = do subKey <- persistentGetSubKey sub+       let uuid = _perSubId sub+           pkg = createAckPackage (_perCred sub) uuid subKey evts+       publishWith (_perExec sub) (SendPackage pkg)  -------------------------------------------------------------------------------- -- | Acknowledges that 'ResolvedEvent' has been successfully processed.@@ -166,23 +137,14 @@  -------------------------------------------------------------------------------- -- | Acknowledges those event ids have failed to be processed successfully.-notifyEventsFailed :: PersistentSubscription-                   -> NakAction-                   -> Maybe Text-                   -> [UUID]-                   -> IO ()-notifyEventsFailed PersistentSubscription{..} act res evts = do-  details <- atomically $ do-    p <- readTVar _perPhase-    case p of-      Closed outcome  ->-        case outcome of-          Right r -> throwSTM (SubscriptionClosed $ Just r)-          Left e  -> throwSTM e-      Pending   -> retrySTM-      Running d -> return d--  let uuid     = subId details-      Just sid = subSubId details-      pkg      = createNakPackage _perCred uuid sid act res evts-  publishWith _perExec (SendPackage pkg)+notifyEventsFailed+  :: PersistentSubscription+  -> NakAction+  -> Maybe Text -- Reason+  -> [UUID]+  -> IO ()+notifyEventsFailed sub act res evts+  = do subKey <- persistentGetSubKey sub+       let uuid = _perSubId sub+           pkg = createNakPackage (_perCred sub) uuid subKey act res evts+       publishWith (_perExec sub) (SendPackage pkg)
Database/EventStore/Internal/Subscription/Regular.hs view
@@ -14,7 +14,6 @@ module Database.EventStore.Internal.Subscription.Regular where  ---------------------------------------------------------------------------------import Database.EventStore.Internal.Callback import Database.EventStore.Internal.Communication import Database.EventStore.Internal.Control import Database.EventStore.Internal.Exec@@ -23,13 +22,7 @@ import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Subscription.Api import Database.EventStore.Internal.Subscription.Types-import Database.EventStore.Internal.Types-----------------------------------------------------------------------------------data Phase-  = Pending-  | Running SubDetails-  | Closed (Either SomeException SubDropReason)+import Database.EventStore.Internal.Subscription.Packages  -------------------------------------------------------------------------------- -- | Also referred as volatile subscription. For example, if a stream has 100@@ -37,70 +30,41 @@ --   event number 101 onwards until the time the subscription is closed or --   dropped. data RegularSubscription t =-  RegularSubscription { _regExec   :: Exec-                      , _regStream :: StreamId t-                      , _regPhase  :: TVar Phase-                      , _regNext   :: STM (Maybe ResolvedEvent)-                      }+  RegularSubscription+  { _regExec :: Exec+  , _regSubId :: UUID+  , _regStream :: StreamId t+  , _regChan :: Chan SubAction+  }  ---------------------------------------------------------------------------------instance Subscription (RegularSubscription t) where-  nextEventMaybeSTM = _regNext--  getSubscriptionDetailsSTM s = do-    p <- readTVar (_regPhase s)-    case p of-      Pending         -> retrySTM-      Running details -> return details-      Closed outcome  ->-        case outcome of-          Right r -> throwSTM (SubscriptionClosed $ Just r)-          Left e  -> throwSTM e+instance Subscription (RegularSubscription s) where+  nextSubEvent s = readChan (_regChan s) -  unsubscribe s = subUnsubscribe (_regExec s) s+  unsubscribe s = publishWith (_regExec s) (SendPackage pkg)+    where+      pkg = createUnsubscribePackage (_regSubId s)  -------------------------------------------------------------------------------- instance SubscriptionStream (RegularSubscription t) t where     subscriptionStream = _regStream  ---------------------------------------------------------------------------------newRegularSubscription :: Exec-                       -> StreamId t-                       -> Bool-                       -> Maybe Credentials-                       -> IO (RegularSubscription t)-newRegularSubscription exec streamId tos cred = do-  phaseVar <- newTVarIO Pending-  queue    <- newTQueueIO--  let sub = RegularSubscription exec streamId phaseVar $ do-        p       <- readTVar phaseVar-        isEmpty <- isEmptyTQueue queue-        if isEmpty-          then-            case p of-              Closed outcome ->-                case outcome of-                  Right r -> throwSTM (SubscriptionClosed $ Just r)-                  Left e  -> throwSTM e-              _ -> return Nothing-          else Just <$> readTQueue queue+newRegularSubscription+  :: Exec+  -> StreamId t+  -> Bool+  -> Maybe Credentials+  -> IO (RegularSubscription t)+newRegularSubscription exec streamId tos cred+  = do (subId, chan) <- volatile exec streamId tos cred+       let sub =+             RegularSubscription+             { _regExec = exec+             , _regSubId = subId+             , _regStream = streamId+             , _regChan = chan+             } -      callback (Left e) = atomically $-          writeTVar phaseVar (Closed $ Left e)-      callback (Right action) =-        case action of-          Confirmed details -> atomically $-            writeTVar phaseVar (Running details)-          Dropped r -> atomically $-            writeTVar phaseVar (Closed $ Right r)-          Submit e -> atomically $ do-            readTVar phaseVar >>= \case-              Running{} -> writeTQueue queue e-              _         -> return ()-          ConnectionReset -> atomically $-            writeTVar phaseVar (Closed $ Right SubAborted)+       pure sub -  cb <- newCallback callback-  publishWith exec (SubmitOperation cb (volatile streamId tos cred))-  return sub
Database/EventStore/Internal/Subscription/Types.hs view
@@ -135,4 +135,3 @@   = Submit ResolvedEvent   | Dropped SubDropReason   | Confirmed SubDetails-  | ConnectionReset
Database/EventStore/Internal/Test.hs view
@@ -12,8 +12,7 @@ -- Re-exports several modules to ease internal testing. -------------------------------------------------------------------------------- module Database.EventStore.Internal.Test-  ( module Database.EventStore.Internal.Callback-  , module Database.EventStore.Internal.Command+  ( module Database.EventStore.Internal.Command   , module Database.EventStore.Internal.Communication   , module Database.EventStore.Internal.Connection   , module Database.EventStore.Internal.Control@@ -27,7 +26,6 @@   , module Database.EventStore.Internal.Types   ) where -import Database.EventStore.Internal.Callback import Database.EventStore.Internal.Command import Database.EventStore.Internal.Communication import Database.EventStore.Internal.Connection
README.md view
@@ -11,7 +11,7 @@   * 64bits system   * GHC        >= 8.0.3   * Cabal      >= 1.18-  * EventStore >= 4+  * EventStore >= 4 (Doesn't support EventStore 2020 Preview yet, previously named version 6).  *Note: If you use this client version >= to `1.1`, it will only supports EventStore >= 4.0.0.* @@ -26,10 +26,8 @@  * From source ```-$ git clone https://gitlab.com/YoEight/eventstore-hs.git+$ git clone https://github.com/YoEight/eventstore.git $ cd eventstore-$ cabal install --only-dependencies-$ cabal configure $ cabal install ``` @@ -37,8 +35,6 @@ =========== Tests are available. Those assume a server is running on `127.0.0.1` and `1113` port. ```-$ cabal install --only-dependencies --enable-tests-$ cabal configure --enable-tests $ cabal test ``` 
eventstore.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: bf11e2eb3f56e21967cbcbcdbafbb405a8955a6942e4b7f12ddaae7597d57737+-- hash: ddd7a1bceafaa54e7b8bf636d520fca1eaa6569f66fe19640603f77e065c9469  name:           eventstore-version:        1.3.3+version:        1.4.0 synopsis:       EventStore TCP Client description:    EventStore TCP Client <https://eventstore.org> category:       Database@@ -17,7 +17,7 @@ copyright:      Yorick Laupa license:        BSD3 license-file:   LICENSE-tested-with:    GHC >= 7.8 && <= 8.6+tested-with:    GHC >= 8.0 && <= 8.8 build-type:     Simple extra-source-files:     README.md@@ -34,7 +34,6 @@       Database.EventStore.Internal.Test   other-modules:       Database.EventStore.Internal-      Database.EventStore.Internal.Callback       Database.EventStore.Internal.Command       Database.EventStore.Internal.Communication       Database.EventStore.Internal.Connection@@ -67,7 +66,6 @@       Database.EventStore.Internal.Operation.Write.Common       Database.EventStore.Internal.Operation.WriteEvents       Database.EventStore.Internal.Operation.WriteEvents.Message-      Database.EventStore.Internal.OperationManager       Database.EventStore.Internal.Operations       Database.EventStore.Internal.Prelude       Database.EventStore.Internal.Settings@@ -100,7 +98,6 @@     , containers     , dns >=3.0.1     , dotnet-timespan-    , ekg-core     , exceptions     , fast-logger     , hashable
tests/Test/Integration/Tests.hs view
@@ -17,6 +17,7 @@ module Test.Integration.Tests (spec) where  --------------------------------------------------------------------------------+import Prelude (fail) import Control.Concurrent.Async (wait) import Data.Aeson import Data.DotNet.TimeSpan@@ -38,22 +39,20 @@     , Slice(..)     , ConnectionType(..)     , Connection+    , SubAction(..)+    , acknowledge     , getStreamMetadata     , sendEvent     , sendEvents     , setStreamMetadata-    , nextEvent-    , notifyEventsProcessed-    , waitConfirmation+    , nextSubEvent+    , streamSubEvents     , connectToPersistentSubscription     , unsubscribe     , createPersistentSubscription     , deletePersistentSubscription     , updatePersistentSubscription-    , nextEventMaybe-    , waitUnsubscribeConfirmed     , subscribeFrom-    , waitTillCatchup     , readEventsBackward     , readEventsForward     , subscribe@@ -78,7 +77,9 @@     , transactionCommit     , i     )+import Data.Maybe (fromJust) import Database.EventStore.Streaming+import qualified Streaming.Prelude as Streaming import Test.Common  --------------------------------------------------------------------------------@@ -260,22 +261,35 @@               ]         evts = fmap (createEvent "foo" Nothing . withJson) jss     sub  <- subscribe conn stream NoResolveLink Nothing-    _    <- waitConfirmation sub-    _    <- sendEvents conn stream anyVersion evts Nothing >>= wait-    let loop 3 = return []-        loop i = do-            e <- nextEvent sub-            fmap (resolvedEventDataAsJson e:) $ loop (i+1)+    confirmChan <- newChan+    resultChan <- newChan -    nxt_js <- loop (0 :: Int)-    assertEqual "Events should be equal" jss (catMaybes nxt_js)-    unsubscribe sub-    let action = do-            _ <- nextEvent sub-            return False-    res <- catch action $ \(_ :: SubscriptionClosed) -> return True-    assertBool "Should have raised an exception" res +    fork $+      do let loop state@(acc, cnt)+               | cnt == 3+               = writeChan resultChan (reverse acc)++               | otherwise+               = do tpe <- nextSubEvent sub+                    case tpe of+                      Confirmed _+                        -> do writeChan confirmChan ()+                              loop state++                      Submit evt+                        -> loop (evt:acc, cnt+1)++                      Dropped _+                        -> writeChan resultChan (reverse acc)+         loop ([],0)++    readChan confirmChan+    _ <- sendEvents conn stream anyVersion evts Nothing >>= wait+    result <- fmap (fromJust . resolvedEventDataAsJson) <$> readChan resultChan++    assertEqual "Events should match" jss result+ -------------------------------------------------------------------------------- subscribeFromTest :: Connection -> IO () subscribeFromTest conn = do@@ -294,74 +308,76 @@         evts2  = fmap (createEvent "foo" Nothing . withJson) jss2     _   <- sendEvents conn stream anyVersion evts Nothing >>= wait     sub <- subscribeFrom conn stream NoResolveLink Nothing (Just 1) Nothing-    _   <- waitConfirmation sub-    _   <- sendEvents conn stream anyVersion evts2 Nothing >>= wait+    confirmChan <- newChan+    resultChan <- newChan -    let loop [] = do-            m <- nextEventMaybe sub-            case m of-                Just _  -> fail "should not have more events at the point."-                Nothing -> return ()-        loop (x:xs) = do-            evt <- nextEvent sub-            case recordedEventDataAsJson $ resolvedEventOriginal evt of-                Just e | e == x    -> loop xs-                       | otherwise -> fail "Out of order event's appeared."-                _ -> fail "Can't deserialized event" -    loop alljss-    unsubscribe sub-    waitUnsubscribeConfirmed sub-    let action = do-            _ <- nextEvent sub-            return False-    res <- catch action $ \(_ :: SubscriptionClosed) -> return True-    assertBool "Should have raised an exception" res+    fork $+      do let loop state@(acc, cnt)+               | cnt == 6+               = writeChan resultChan (reverse acc) ----------------------------------------------------------------------------------data SubNoStreamTest-  = SubNoStreamTestSuccess-  | SubNoStreamTestTimeout-  deriving (Eq, Show)+               | otherwise+               = do tpe <- nextSubEvent sub+                    case tpe of+                      Confirmed _+                        -> do writeChan confirmChan ()+                              loop state +                      Submit evt+                        -> do loop (evt:acc, cnt+1)++                      Dropped _+                        -> writeChan resultChan (reverse acc)+         loop ([],0)++    readChan confirmChan+    _ <- sendEvents conn stream anyVersion evts2 Nothing >>= wait+    result <- fmap (fromJust . resolvedEventDataAsJson) <$> readChan resultChan++    assertEqual "Events should match" alljss result+ -------------------------------------------------------------------------------- subscribeFromNoStreamTest :: Connection -> IO () subscribeFromNoStreamTest conn = do   stream <- freshStreamId   sub <- subscribeFrom conn stream NoResolveLink Nothing Nothing Nothing-  let loop [] = do-          m <- nextEventMaybe sub-          case m of-              Just _  -> fail "should not have more events at the point."-              Nothing -> return ()-      loop (x:xs) = do-          evt <- nextEvent sub-          case recordedEventDataAsJson $ resolvedEventOriginal evt of-              Just e | e == x    -> loop xs-                     | otherwise -> fail "Out of order event's appeared."-              _ -> fail "Can't deserialized event"+  confirmChan <- newChan+  resultChan <- newChan -      subAction = do-          waitTillCatchup sub-          let jss = [ object [ "1" .= (1 :: Int)]-                    , object [ "2" .= (2 :: Int)]-                    , object [ "3" .= (3 :: Int)]-                    ] -              evts = fmap (createEvent "foo" Nothing . withJson) jss+  fork $+    do let loop state@(acc, cnt)+             | cnt == 3+             = writeChan resultChan (reverse acc) -          _ <- sendEvents conn stream anyVersion evts Nothing >>= wait-          loop jss-          return SubNoStreamTestSuccess-      timeout = do-          threadDelay (12 * secs)-          return SubNoStreamTestTimeout+             | otherwise+             = do tpe <- nextSubEvent sub+                  case tpe of+                    Confirmed _+                      -> do writeChan confirmChan ()+                            loop state -  res <- race subAction timeout-  case res of-    Left r -> assertEqual "Wrong test result" SubNoStreamTestSuccess r-    Right r -> assertEqual "Wrong test result" SubNoStreamTestSuccess r+                    Submit evt+                      -> loop (evt:acc, cnt+1) +                    Dropped _+                      -> writeChan resultChan (reverse acc)+       loop ([],0)++  readChan confirmChan+  let jss = [ object [ "1" .= (1 :: Int)]+            , object [ "2" .= (2 :: Int)]+            , object [ "3" .= (3 :: Int)]+            ]++      evts = fmap (createEvent "foo" Nothing . withJson) jss++  _ <- sendEvents conn stream anyVersion evts Nothing >>= wait+  result <- fmap (fromJust . resolvedEventDataAsJson) <$> readChan resultChan++  assertEqual "Events should match" jss result+ -------------------------------------------------------------------------------- setStreamMetadataTest :: Connection -> IO () setStreamMetadataTest conn = do@@ -483,29 +499,35 @@         evts = fmap (createEvent "foo" Nothing . withJson) jss     stream <- freshStreamId     _   <- createPersistentSubscription conn "group" stream def Nothing >>= wait-    _   <- sendEvents conn stream anyVersion evts Nothing >>= wait     sub <- connectToPersistentSubscription conn "group" stream 1 Nothing-    _   <- waitConfirmation sub-    r   <- nextEvent sub-    case resolvedEventDataAsJson r of-        Just js_evt -> assertEqual "event 1 should match" js1 js_evt-        _           -> fail "Deserialization error"+    confirmChan <- newChan+    resultChan <- newChan -    notifyEventsProcessed sub [resolvedEventOriginalId r]+    fork $+      do let loop state@(acc, cnt)+               | cnt == 2+               = writeChan resultChan (reverse acc) -    r2 <- nextEvent sub-    case resolvedEventDataAsJson r2 of-        Just js_evt -> assertEqual "event 2 should match" js2 js_evt-        _           -> fail "Deserialization error"+               | otherwise+               = do tpe <- nextSubEvent sub+                    case tpe of+                      Confirmed _+                        -> do writeChan confirmChan ()+                              loop state -    notifyEventsProcessed sub [resolvedEventOriginalId r2]+                      Submit evt+                        -> do acknowledge sub evt+                              loop (evt:acc, cnt+1) -    unsubscribe sub-    let action = do-            _ <- nextEvent sub-            return False-    res <- catch action $ \(_ :: SubscriptionClosed) -> return True-    assertBool "Should have raised an exception" res+                      Dropped _+                        -> writeChan resultChan (reverse acc)+         loop ([],0)++    readChan confirmChan+    _ <- sendEvents conn stream anyVersion evts Nothing >>= wait+    result <- fmap (fromJust . resolvedEventDataAsJson) <$> readChan resultChan++    assertEqual "Both events should match" [js1,js2] result  -------------------------------------------------------------------------------- maxAgeTest :: Connection -> IO ()
tests/Test/Operation.hs view
@@ -57,10 +57,7 @@      exec <- newExec testSettings bus builder testDisc -    p <- newPromise-    let op = readEvent testSettings "foo" 1 True Nothing-    publishWith exec (SubmitOperation p op)-+    readEvent testSettings exec "foo" 1 True Nothing     res <- takeMVar var      publishWith exec SystemShutdown