eventstore 0.14.0.2 → 0.15.0.0
raw patch · 79 files changed
+6296/−4972 lines, 79 filesdep +asyncdep +bifunctorsdep +bytestringdep −classy-preludedep ~aesondep ~basedep ~cerealPVP ok
version bump matches the API change (PVP)
Dependencies added: async, bifunctors, bytestring, clock, ekg-core, exceptions, fast-logger, hashable, interpolate, lifted-async, lifted-base, machines, monad-control, monad-logger, mono-traversable, safe-exceptions, stm-chans, tasty-hspec, text-format, transformers-base
Dependencies removed: classy-prelude
Dependency ranges changed: aeson, base, cereal, connection, containers, protobuf, semigroups, time
API changes (from Hackage documentation)
- Database.EventStore: ClosedConnection :: ConnectionException
- Database.EventStore: MaxAttemptConnectionReached :: ConnectionException
- Database.EventStore: PackageParsingError :: String -> ConnectionException
- Database.EventStore: SubscriptionUnsubscribedByUser :: SubscriptionClosed
- Database.EventStore: WrongPackageFraming :: ConnectionException
- Database.EventStore: [s_logger] :: Settings -> Maybe (Log -> IO ())
- Database.EventStore: [s_reconnect_delay_secs] :: Settings -> Int
- Database.EventStore: data Catchup
- Database.EventStore: data ConnectionException
- Database.EventStore: data Persistent
- Database.EventStore: data Regular
- Database.EventStore: data Subscription t
- Database.EventStore: getSubId :: Subscription a -> IO SubscriptionId
- Database.EventStore: getSubLastCommitPos :: Subscription a -> IO Int64
- Database.EventStore: getSubLastEventNumber :: Subscription a -> IO (Maybe Int32)
- Database.EventStore: getSubResolveLinkTos :: Subscription Regular -> Bool
- Database.EventStore: getSubStream :: Subscription a -> Text
- Database.EventStore: waitAsync :: MonadIO m => Async a -> m a
- Database.EventStore.Logging: Connected :: UUID -> InfoMessage
- Database.EventStore.Logging: Connecting :: Int -> InfoMessage
- Database.EventStore.Logging: ConnectionClosed :: UUID -> InfoMessage
- Database.EventStore.Logging: Disconnected :: UUID -> InfoMessage
- Database.EventStore.Logging: Error :: ErrorMessage -> Log
- Database.EventStore.Logging: Info :: InfoMessage -> Log
- Database.EventStore.Logging: PackageReceived :: Command -> UUID -> InfoMessage
- Database.EventStore.Logging: PackageSent :: Command -> UUID -> InfoMessage
- Database.EventStore.Logging: UnexpectedException :: SomeException -> ErrorMessage
- Database.EventStore.Logging: data ErrorMessage
- Database.EventStore.Logging: data InfoMessage
- Database.EventStore.Logging: data Log
- Database.EventStore.Logging: instance GHC.Show.Show Database.EventStore.Logging.ErrorMessage
- Database.EventStore.Logging: instance GHC.Show.Show Database.EventStore.Logging.InfoMessage
- Database.EventStore.Logging: instance GHC.Show.Show Database.EventStore.Logging.Log
+ Database.EventStore: LevelDebug :: LogLevel
+ Database.EventStore: LevelError :: LogLevel
+ Database.EventStore: LevelInfo :: LogLevel
+ Database.EventStore: LevelOther :: Text -> LogLevel
+ Database.EventStore: LevelWarn :: LogLevel
+ Database.EventStore: LogCallback :: (LogStr -> IO ()) -> IO () -> LogType
+ Database.EventStore: LogFile :: FileLogSpec -> BufSize -> LogType
+ Database.EventStore: LogFileNoRotate :: FilePath -> BufSize -> LogType
+ Database.EventStore: LogNone :: LogType
+ Database.EventStore: LogStderr :: BufSize -> LogType
+ Database.EventStore: LogStdout :: BufSize -> LogType
+ Database.EventStore: LoggerFilter :: (LogSource -> LogLevel -> Bool) -> LoggerFilter
+ Database.EventStore: LoggerLevel :: LogLevel -> LoggerFilter
+ Database.EventStore: OperationMaxAttemptReached :: UUID -> Command -> OperationMaxAttemptReached
+ Database.EventStore: StreamNotFound :: Text -> OperationError
+ Database.EventStore: [s_loggerDetailed] :: Settings -> Bool
+ Database.EventStore: [s_loggerFilter] :: Settings -> LoggerFilter
+ Database.EventStore: [s_loggerType] :: Settings -> LogType
+ Database.EventStore: [s_monitoring] :: Settings -> Maybe Store
+ Database.EventStore: [s_operationRetry] :: Settings -> Retry
+ Database.EventStore: [s_operationTimeout] :: Settings -> NominalDiffTime
+ Database.EventStore: [s_reconnect_delay] :: Settings -> NominalDiffTime
+ Database.EventStore: class Subscription s
+ Database.EventStore: data CatchupSubscription
+ Database.EventStore: data LogLevel :: *
+ Database.EventStore: data LogType :: *
+ Database.EventStore: data LoggerFilter
+ Database.EventStore: data NominalDiffTime :: *
+ Database.EventStore: data OperationMaxAttemptReached
+ Database.EventStore: data PersistentSubscription
+ Database.EventStore: data RegularSubscription
+ Database.EventStore: data SubDetails
+ Database.EventStore: getSubscriptionDetailsSTM :: Subscription s => s -> STM SubDetails
+ Database.EventStore: getSubscriptionId :: Subscription s => s -> IO SubscriptionId
+ Database.EventStore: msDiffTime :: Float -> NominalDiffTime
+ Database.EventStore: nextEventMaybeSTM :: Subscription s => s -> STM (Maybe ResolvedEvent)
+ Database.EventStore: resolvedEventOriginalEventNumber :: ResolvedEvent -> Int32
+ Database.EventStore: subscriptionStream :: Subscription s => s -> StreamName
+ Database.EventStore: toSlice :: Slice a => a -> SomeSlice
+ Database.EventStore.Internal.Test: (<>) :: Semigroup a => a -> a -> a
+ Database.EventStore.Internal.Test: (<|>) :: Alternative f => f a -> f a -> f a
+ Database.EventStore.Internal.Test: AllCatchup :: Position -> CatchupState
+ Database.EventStore.Internal.Test: Any :: ExpectedVersion
+ Database.EventStore.Internal.Test: AtMost :: Int -> Retry
+ Database.EventStore.Internal.Test: Authenticated :: Flag
+ Database.EventStore.Internal.Test: Backward :: ReadDirection
+ Database.EventStore.Internal.Test: Binary :: ByteString -> (Maybe ByteString) -> EventData
+ Database.EventStore.Internal.Test: CatchupOpResult :: ![ResolvedEvent] -> !Bool -> !Checkpoint -> CatchupOpResult
+ Database.EventStore.Internal.Test: CheckpointNumber :: Int32 -> Checkpoint
+ Database.EventStore.Internal.Test: CheckpointPosition :: Position -> Checkpoint
+ Database.EventStore.Internal.Test: ClusterSettings :: !ByteString -> !Int -> !Int -> (Maybe (NonEmpty GossipSeed)) -> !TimeSpan -> !(Maybe DnsServer) -> ClusterSettings
+ Database.EventStore.Internal.Test: Command :: !Word8 -> !Text -> Command
+ Database.EventStore.Internal.Test: Connection :: ConnectionId -> EndPoint -> (Package -> EventStore ()) -> EventStore () -> Connection
+ Database.EventStore.Internal.Test: ConnectionBuilder :: (EndPoint -> EventStore Connection) -> ConnectionBuilder
+ Database.EventStore.Internal.Test: ConnectionClosed :: Connection -> SomeException -> ConnectionClosed
+ Database.EventStore.Internal.Test: ConnectionClosedByServer :: InternalException
+ Database.EventStore.Internal.Test: ConnectionError :: Connection -> SomeException -> ConnectionError
+ Database.EventStore.Internal.Test: ConnectionEstablished :: Connection -> ConnectionEstablished
+ Database.EventStore.Internal.Test: ConnectionManager :: Service
+ Database.EventStore.Internal.Test: ConnectionRef :: EventStore (Maybe Connection) -> ConnectionRef
+ Database.EventStore.Internal.Test: Credentials :: !ByteString -> !ByteString -> Credentials
+ Database.EventStore.Internal.Test: DNSDiscoveryError :: DNSError -> DnsDiscoveryException
+ Database.EventStore.Internal.Test: DeleteResult :: Position -> DeleteResult
+ Database.EventStore.Internal.Test: DeletedStreamMetadataResult :: !Text -> StreamMetadataResult
+ Database.EventStore.Internal.Test: Discovery :: (Maybe EndPoint -> IO (Maybe EndPoint)) -> Discovery
+ Database.EventStore.Internal.Test: DispatchToSingle :: SystemConsumerStrategy
+ Database.EventStore.Internal.Test: DnsFilePath :: String -> DnsServer
+ Database.EventStore.Internal.Test: DnsHostName :: String -> DnsServer
+ Database.EventStore.Internal.Test: DnsHostPort :: String -> Int -> DnsServer
+ Database.EventStore.Internal.Test: Down :: a -> Down a
+ Database.EventStore.Internal.Test: Duration :: Int64 -> Duration
+ Database.EventStore.Internal.Test: EmptyStream :: ExpectedVersion
+ Database.EventStore.Internal.Test: EndPoint :: !String -> !Int -> EndPoint
+ Database.EventStore.Internal.Test: Event :: !EventType -> !(Maybe UUID) -> !EventData -> Event
+ Database.EventStore.Internal.Test: EventRecord :: Required 1 (Value Text) -> Required 2 (Value Int32) -> Required 3 (Value ByteString) -> Required 4 (Value Text) -> Required 5 (Value Int32) -> Required 6 (Value Int32) -> Required 7 (Value ByteString) -> Optional 8 (Value ByteString) -> Optional 9 (Value Int64) -> Optional 10 (Value Int64) -> EventRecord
+ Database.EventStore.Internal.Test: Exact :: Int32 -> ExpectedVersion
+ Database.EventStore.Internal.Test: FatalCondition :: Text -> FatalException
+ Database.EventStore.Internal.Test: FatalException :: e -> FatalException
+ Database.EventStore.Internal.Test: Forward :: ReadDirection
+ Database.EventStore.Internal.Test: InitFailed :: Service -> InitFailed
+ Database.EventStore.Internal.Test: Initialized :: Service -> Initialized
+ Database.EventStore.Internal.Test: Json :: Value -> (Maybe Value) -> EventData
+ Database.EventStore.Internal.Test: KeepRetrying :: Retry
+ Database.EventStore.Internal.Test: LinkToType :: EventType
+ Database.EventStore.Internal.Test: LoggerFilter :: (LogSource -> LogLevel -> Bool) -> LoggerFilter
+ Database.EventStore.Internal.Test: LoggerLevel :: LogLevel -> LoggerFilter
+ Database.EventStore.Internal.Test: MasterInfo :: !String -> !Int -> !String -> !Int -> !(Maybe String) -> !(Maybe Int) -> MasterInfo
+ Database.EventStore.Internal.Test: MasterInfoBuf :: Required 1 (Value Text) -> Required 2 (Value Int32) -> Required 3 (Value Text) -> Required 4 (Value Int32) -> Optional 5 (Value Text) -> Optional 6 (Value Int32) -> MasterInfoBuf
+ Database.EventStore.Internal.Test: MaxDiscoveryAttemptReached :: ByteString -> DnsDiscoveryException
+ Database.EventStore.Internal.Test: N_NotMaster :: NotHandledReason
+ Database.EventStore.Internal.Test: N_NotReady :: NotHandledReason
+ Database.EventStore.Internal.Test: N_TooBusy :: NotHandledReason
+ Database.EventStore.Internal.Test: NewEvent :: Required 1 (Value ByteString) -> Required 2 (Value Text) -> Required 3 (Value Int32) -> Required 4 (Value Int32) -> Required 5 (Value ByteString) -> Optional 6 (Value ByteString) -> NewEvent
+ Database.EventStore.Internal.Test: NewTimer :: e -> Duration -> Bool -> NewTimer
+ Database.EventStore.Internal.Test: NoStream :: ExpectedVersion
+ Database.EventStore.Internal.Test: NodeEndPoints :: !EndPoint -> !(Maybe EndPoint) -> NodeEndPoints
+ Database.EventStore.Internal.Test: None :: Flag
+ Database.EventStore.Internal.Test: NotFoundStreamMetadataResult :: !Text -> StreamMetadataResult
+ Database.EventStore.Internal.Test: NotHandledBuf :: Required 1 (Enumeration NotHandledReason) -> Optional 2 (Message MasterInfoBuf) -> NotHandledBuf
+ Database.EventStore.Internal.Test: Package :: !Command -> !UUID -> !ByteString -> !(Maybe Credentials) -> Package
+ Database.EventStore.Internal.Test: PackageArrived :: Connection -> Package -> PackageArrived
+ Database.EventStore.Internal.Test: ParsingError :: RecvOutcome
+ Database.EventStore.Internal.Test: PersistentSubscriptionSettings :: !Bool -> !Int32 -> !Bool -> !TimeSpan -> !Int32 -> !Int32 -> !Int32 -> !Int32 -> !TimeSpan -> !Int32 -> !Int32 -> !Int32 -> !SystemConsumerStrategy -> PersistentSubscriptionSettings
+ Database.EventStore.Internal.Test: Position :: !Int64 -> !Int64 -> Position
+ Database.EventStore.Internal.Test: ReadEvent :: !Text -> !Int32 -> !ResolvedEvent -> ReadEvent
+ Database.EventStore.Internal.Test: ReadEventNotFound :: !Text -> !Int32 -> ReadEvent
+ Database.EventStore.Internal.Test: RecordedEvent :: !Text -> !UUID -> !Int32 -> !Text -> !ByteString -> !(Maybe ByteString) -> !Bool -> !(Maybe UTCTime) -> RecordedEvent
+ Database.EventStore.Internal.Test: Recv :: Package -> RecvOutcome
+ Database.EventStore.Internal.Test: RegularCatchup :: Text -> Int32 -> CatchupState
+ Database.EventStore.Internal.Test: ResetByPeer :: RecvOutcome
+ Database.EventStore.Internal.Test: ResolvedEvent :: !(Maybe RecordedEvent) -> !(Maybe RecordedEvent) -> !(Maybe Position) -> ResolvedEvent
+ Database.EventStore.Internal.Test: ResolvedEventBuf :: Required 1 (Message EventRecord) -> Optional 2 (Message EventRecord) -> Required 3 (Value Int64) -> Required 4 (Value Int64) -> ResolvedEventBuf
+ Database.EventStore.Internal.Test: ResolvedIndexedEvent :: Optional 1 (Message EventRecord) -> Optional 2 (Message EventRecord) -> ResolvedIndexedEvent
+ Database.EventStore.Internal.Test: RoundRobin :: SystemConsumerStrategy
+ Database.EventStore.Internal.Test: SendPackage :: Package -> SendPackage
+ Database.EventStore.Internal.Test: ServiceTerminated :: Service -> ServiceTerminated
+ Database.EventStore.Internal.Test: Settings :: NominalDiffTime -> NominalDiffTime -> Bool -> Maybe Credentials -> Retry -> NominalDiffTime -> Maybe TLSSettings -> LogType -> LoggerFilter -> Bool -> NominalDiffTime -> Retry -> Maybe Store -> Settings
+ Database.EventStore.Internal.Test: SettingsType :: EventType
+ Database.EventStore.Internal.Test: StatsCollectionType :: EventType
+ Database.EventStore.Internal.Test: StreamACL :: ![Text] -> ![Text] -> ![Text] -> ![Text] -> ![Text] -> StreamACL
+ Database.EventStore.Internal.Test: StreamDeletedType :: EventType
+ Database.EventStore.Internal.Test: StreamExists :: ExpectedVersion
+ Database.EventStore.Internal.Test: StreamMetadata :: !(Maybe Int32) -> !(Maybe TimeSpan) -> !(Maybe Int32) -> !(Maybe TimeSpan) -> !StreamACL -> !Object -> StreamMetadata
+ Database.EventStore.Internal.Test: StreamMetadataResult :: !Text -> !Int32 -> !StreamMetadata -> StreamMetadataResult
+ Database.EventStore.Internal.Test: StreamMetadataType :: EventType
+ Database.EventStore.Internal.Test: SubmitOperation :: (Callback a) -> (Operation a) -> SubmitOperation
+ Database.EventStore.Internal.Test: SystemInit :: SystemInit
+ Database.EventStore.Internal.Test: SystemShutdown :: SystemShutdown
+ Database.EventStore.Internal.Test: Terminated :: String -> Terminated
+ Database.EventStore.Internal.Test: TimerService :: Service
+ Database.EventStore.Internal.Test: UserDefined :: Text -> EventType
+ Database.EventStore.Internal.Test: WrongFraming :: RecvOutcome
+ Database.EventStore.Internal.Test: [bufMasterExternalHttpAddr] :: MasterInfoBuf -> Required 3 (Value Text)
+ Database.EventStore.Internal.Test: [bufMasterExternalHttpPort] :: MasterInfoBuf -> Required 4 (Value Int32)
+ Database.EventStore.Internal.Test: [bufMasterExternalSecureTcpAddr] :: MasterInfoBuf -> Optional 5 (Value Text)
+ Database.EventStore.Internal.Test: [bufMasterExternalSecureTcpPort] :: MasterInfoBuf -> Optional 6 (Value Int32)
+ Database.EventStore.Internal.Test: [bufMasterExternalTcpAddr] :: MasterInfoBuf -> Required 1 (Value Text)
+ Database.EventStore.Internal.Test: [bufMasterExternalTcpPort] :: MasterInfoBuf -> Required 2 (Value Int32)
+ Database.EventStore.Internal.Test: [catchupCheckpoint] :: CatchupOpResult -> !Checkpoint
+ Database.EventStore.Internal.Test: [catchupEndOfStream] :: CatchupOpResult -> !Bool
+ Database.EventStore.Internal.Test: [catchupReadEvents] :: CatchupOpResult -> ![ResolvedEvent]
+ Database.EventStore.Internal.Test: [clusterDnsServer] :: ClusterSettings -> !(Maybe DnsServer)
+ Database.EventStore.Internal.Test: [clusterDns] :: ClusterSettings -> !ByteString
+ Database.EventStore.Internal.Test: [clusterExternalGossipPort] :: ClusterSettings -> !Int
+ Database.EventStore.Internal.Test: [clusterGossipSeeds] :: ClusterSettings -> (Maybe (NonEmpty GossipSeed))
+ Database.EventStore.Internal.Test: [clusterGossipTimeout] :: ClusterSettings -> !TimeSpan
+ Database.EventStore.Internal.Test: [clusterMaxDiscoverAttempts] :: ClusterSettings -> !Int
+ Database.EventStore.Internal.Test: [cmdDesc] :: Command -> !Text
+ Database.EventStore.Internal.Test: [cmdWord8] :: Command -> !Word8
+ Database.EventStore.Internal.Test: [connect] :: ConnectionBuilder -> EndPoint -> EventStore Connection
+ Database.EventStore.Internal.Test: [connectionEndPoint] :: Connection -> EndPoint
+ Database.EventStore.Internal.Test: [connectionId] :: Connection -> ConnectionId
+ Database.EventStore.Internal.Test: [credLogin] :: Credentials -> !ByteString
+ Database.EventStore.Internal.Test: [credPassword] :: Credentials -> !ByteString
+ Database.EventStore.Internal.Test: [dispose] :: Connection -> EventStore ()
+ Database.EventStore.Internal.Test: [endPointIp] :: EndPoint -> !String
+ Database.EventStore.Internal.Test: [endPointPort] :: EndPoint -> !Int
+ Database.EventStore.Internal.Test: [enqueuePackage] :: Connection -> Package -> EventStore ()
+ Database.EventStore.Internal.Test: [eventData] :: Event -> !EventData
+ Database.EventStore.Internal.Test: [eventId] :: Event -> !(Maybe UUID)
+ Database.EventStore.Internal.Test: [eventRecordCreatedEpoch] :: EventRecord -> Optional 10 (Value Int64)
+ Database.EventStore.Internal.Test: [eventRecordCreated] :: EventRecord -> Optional 9 (Value Int64)
+ Database.EventStore.Internal.Test: [eventRecordDataType] :: EventRecord -> Required 5 (Value Int32)
+ Database.EventStore.Internal.Test: [eventRecordData] :: EventRecord -> Required 7 (Value ByteString)
+ Database.EventStore.Internal.Test: [eventRecordId] :: EventRecord -> Required 3 (Value ByteString)
+ Database.EventStore.Internal.Test: [eventRecordMetadataType] :: EventRecord -> Required 6 (Value Int32)
+ Database.EventStore.Internal.Test: [eventRecordMetadata] :: EventRecord -> Optional 8 (Value ByteString)
+ Database.EventStore.Internal.Test: [eventRecordNumber] :: EventRecord -> Required 2 (Value Int32)
+ Database.EventStore.Internal.Test: [eventRecordStreamId] :: EventRecord -> Required 1 (Value Text)
+ Database.EventStore.Internal.Test: [eventRecordType] :: EventRecord -> Required 4 (Value Text)
+ Database.EventStore.Internal.Test: [eventType] :: Event -> !EventType
+ Database.EventStore.Internal.Test: [masterExternalHttpAddr] :: MasterInfo -> !String
+ Database.EventStore.Internal.Test: [masterExternalHttpPort] :: MasterInfo -> !Int
+ Database.EventStore.Internal.Test: [masterExternalSecureTcpAddr] :: MasterInfo -> !(Maybe String)
+ Database.EventStore.Internal.Test: [masterExternalSecureTcpPort] :: MasterInfo -> !(Maybe Int)
+ Database.EventStore.Internal.Test: [masterExternalTcpAddr] :: MasterInfo -> !String
+ Database.EventStore.Internal.Test: [masterExternalTcpPort] :: MasterInfo -> !Int
+ Database.EventStore.Internal.Test: [maybeConnection] :: ConnectionRef -> EventStore (Maybe Connection)
+ Database.EventStore.Internal.Test: [newEventDataType] :: NewEvent -> Required 3 (Value Int32)
+ Database.EventStore.Internal.Test: [newEventData] :: NewEvent -> Required 5 (Value ByteString)
+ Database.EventStore.Internal.Test: [newEventId] :: NewEvent -> Required 1 (Value ByteString)
+ Database.EventStore.Internal.Test: [newEventMetadataType] :: NewEvent -> Required 4 (Value Int32)
+ Database.EventStore.Internal.Test: [newEventMetadata] :: NewEvent -> Optional 6 (Value ByteString)
+ Database.EventStore.Internal.Test: [newEventType] :: NewEvent -> Required 2 (Value Text)
+ Database.EventStore.Internal.Test: [notHandledAdditionalInfo] :: NotHandledBuf -> Optional 2 (Message MasterInfoBuf)
+ Database.EventStore.Internal.Test: [notHandledReason] :: NotHandledBuf -> Required 1 (Enumeration NotHandledReason)
+ Database.EventStore.Internal.Test: [packageCmd] :: Package -> !Command
+ Database.EventStore.Internal.Test: [packageCorrelation] :: Package -> !UUID
+ Database.EventStore.Internal.Test: [packageCred] :: Package -> !(Maybe Credentials)
+ Database.EventStore.Internal.Test: [packageData] :: Package -> !ByteString
+ Database.EventStore.Internal.Test: [positionCommit] :: Position -> !Int64
+ Database.EventStore.Internal.Test: [positionPrepare] :: Position -> !Int64
+ Database.EventStore.Internal.Test: [psSettingsCheckPointAfter] :: PersistentSubscriptionSettings -> !TimeSpan
+ Database.EventStore.Internal.Test: [psSettingsExtraStats] :: PersistentSubscriptionSettings -> !Bool
+ Database.EventStore.Internal.Test: [psSettingsHistoryBufSize] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [psSettingsLiveBufSize] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [psSettingsMaxCheckPointCount] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [psSettingsMaxRetryCount] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [psSettingsMaxSubsCount] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [psSettingsMinCheckPointCount] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [psSettingsMsgTimeout] :: PersistentSubscriptionSettings -> !TimeSpan
+ Database.EventStore.Internal.Test: [psSettingsNamedConsumerStrategy] :: PersistentSubscriptionSettings -> !SystemConsumerStrategy
+ Database.EventStore.Internal.Test: [psSettingsReadBatchSize] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [psSettingsResolveLinkTos] :: PersistentSubscriptionSettings -> !Bool
+ Database.EventStore.Internal.Test: [psSettingsStartFrom] :: PersistentSubscriptionSettings -> !Int32
+ Database.EventStore.Internal.Test: [readEventNumber] :: ReadEvent -> !Int32
+ Database.EventStore.Internal.Test: [readEventResolved] :: ReadEvent -> !ResolvedEvent
+ Database.EventStore.Internal.Test: [readEventStream] :: ReadEvent -> !Text
+ Database.EventStore.Internal.Test: [recordedEventCreated] :: RecordedEvent -> !(Maybe UTCTime)
+ Database.EventStore.Internal.Test: [recordedEventData] :: RecordedEvent -> !ByteString
+ Database.EventStore.Internal.Test: [recordedEventId] :: RecordedEvent -> !UUID
+ Database.EventStore.Internal.Test: [recordedEventIsJson] :: RecordedEvent -> !Bool
+ Database.EventStore.Internal.Test: [recordedEventMetadata] :: RecordedEvent -> !(Maybe ByteString)
+ Database.EventStore.Internal.Test: [recordedEventNumber] :: RecordedEvent -> !Int32
+ Database.EventStore.Internal.Test: [recordedEventStreamId] :: RecordedEvent -> !Text
+ Database.EventStore.Internal.Test: [recordedEventType] :: RecordedEvent -> !Text
+ Database.EventStore.Internal.Test: [resolvedEventBufCommitPosition] :: ResolvedEventBuf -> Required 3 (Value Int64)
+ Database.EventStore.Internal.Test: [resolvedEventBufEvent] :: ResolvedEventBuf -> Required 1 (Message EventRecord)
+ Database.EventStore.Internal.Test: [resolvedEventBufLink] :: ResolvedEventBuf -> Optional 2 (Message EventRecord)
+ Database.EventStore.Internal.Test: [resolvedEventBufPreparePosition] :: ResolvedEventBuf -> Required 4 (Value Int64)
+ Database.EventStore.Internal.Test: [resolvedEventLink] :: ResolvedEvent -> !(Maybe RecordedEvent)
+ Database.EventStore.Internal.Test: [resolvedEventPosition] :: ResolvedEvent -> !(Maybe Position)
+ Database.EventStore.Internal.Test: [resolvedEventRecord] :: ResolvedEvent -> !(Maybe RecordedEvent)
+ Database.EventStore.Internal.Test: [resolvedIndexedLink] :: ResolvedIndexedEvent -> Optional 2 (Message EventRecord)
+ Database.EventStore.Internal.Test: [resolvedIndexedRecord] :: ResolvedIndexedEvent -> Optional 1 (Message EventRecord)
+ Database.EventStore.Internal.Test: [runDiscovery] :: Discovery -> Maybe EndPoint -> IO (Maybe EndPoint)
+ Database.EventStore.Internal.Test: [s_credentials] :: Settings -> Maybe Credentials
+ Database.EventStore.Internal.Test: [s_heartbeatInterval] :: Settings -> NominalDiffTime
+ Database.EventStore.Internal.Test: [s_heartbeatTimeout] :: Settings -> NominalDiffTime
+ Database.EventStore.Internal.Test: [s_loggerDetailed] :: Settings -> Bool
+ Database.EventStore.Internal.Test: [s_loggerFilter] :: Settings -> LoggerFilter
+ Database.EventStore.Internal.Test: [s_loggerType] :: Settings -> LogType
+ Database.EventStore.Internal.Test: [s_monitoring] :: Settings -> Maybe Store
+ Database.EventStore.Internal.Test: [s_operationRetry] :: Settings -> Retry
+ Database.EventStore.Internal.Test: [s_operationTimeout] :: Settings -> NominalDiffTime
+ Database.EventStore.Internal.Test: [s_reconnect_delay] :: Settings -> NominalDiffTime
+ Database.EventStore.Internal.Test: [s_requireMaster] :: Settings -> Bool
+ Database.EventStore.Internal.Test: [s_retry] :: Settings -> Retry
+ Database.EventStore.Internal.Test: [s_ssl] :: Settings -> Maybe TLSSettings
+ Database.EventStore.Internal.Test: [secureEndPoint] :: NodeEndPoints -> !(Maybe EndPoint)
+ Database.EventStore.Internal.Test: [streamACLDeleteRoles] :: StreamACL -> ![Text]
+ Database.EventStore.Internal.Test: [streamACLMetaReadRoles] :: StreamACL -> ![Text]
+ Database.EventStore.Internal.Test: [streamACLMetaWriteRoles] :: StreamACL -> ![Text]
+ Database.EventStore.Internal.Test: [streamACLReadRoles] :: StreamACL -> ![Text]
+ Database.EventStore.Internal.Test: [streamACLWriteRoles] :: StreamACL -> ![Text]
+ Database.EventStore.Internal.Test: [streamMetaResultData] :: StreamMetadataResult -> !StreamMetadata
+ Database.EventStore.Internal.Test: [streamMetaResultStream] :: StreamMetadataResult -> !Text
+ Database.EventStore.Internal.Test: [streamMetaResultVersion] :: StreamMetadataResult -> !Int32
+ Database.EventStore.Internal.Test: [streamMetadataACL] :: StreamMetadata -> !StreamACL
+ Database.EventStore.Internal.Test: [streamMetadataCacheControl] :: StreamMetadata -> !(Maybe TimeSpan)
+ Database.EventStore.Internal.Test: [streamMetadataCustom] :: StreamMetadata -> !Object
+ Database.EventStore.Internal.Test: [streamMetadataMaxAge] :: StreamMetadata -> !(Maybe TimeSpan)
+ Database.EventStore.Internal.Test: [streamMetadataMaxCount] :: StreamMetadata -> !(Maybe Int32)
+ Database.EventStore.Internal.Test: [streamMetadataTruncateBefore] :: StreamMetadata -> !(Maybe Int32)
+ Database.EventStore.Internal.Test: [tcpEndPoint] :: NodeEndPoints -> !EndPoint
+ Database.EventStore.Internal.Test: _cmdDict :: HashMap Word8 Command
+ Database.EventStore.Internal.Test: anyVersion :: ExpectedVersion
+ Database.EventStore.Internal.Test: asHub :: (Sub h, Pub h) => h -> Hub
+ Database.EventStore.Internal.Test: asPub :: Pub p => p -> Publish
+ Database.EventStore.Internal.Test: asSub :: Sub s => s -> Subscribe
+ Database.EventStore.Internal.Test: atMost :: Int -> Retry
+ Database.EventStore.Internal.Test: atomically :: MonadIO m => STM a -> m a
+ Database.EventStore.Internal.Test: authenticateCmd :: Command
+ Database.EventStore.Internal.Test: authenticatedCmd :: Command
+ Database.EventStore.Internal.Test: badRequestCmd :: Command
+ Database.EventStore.Internal.Test: build :: a -> Builder a -> a
+ Database.EventStore.Internal.Test: buildStreamACL :: StreamACLBuilder -> StreamACL
+ Database.EventStore.Internal.Test: buildStreamMetadata :: StreamMetadataBuilder -> StreamMetadata
+ Database.EventStore.Internal.Test: busProcessedEverything :: Bus -> IO ()
+ Database.EventStore.Internal.Test: busStop :: MonadIO m => Bus -> m ()
+ Database.EventStore.Internal.Test: catchup :: Settings -> CatchupState -> Bool -> Maybe Int32 -> Operation SubAction
+ Database.EventStore.Internal.Test: class Applicative f => Alternative (f :: * -> *)
+ Database.EventStore.Internal.Test: class Generic a
+ Database.EventStore.Internal.Test: class Hashable a
+ Database.EventStore.Internal.Test: class IsString a
+ Database.EventStore.Internal.Test: class MonadBase b m => MonadBaseControl (b :: * -> *) (m :: * -> *) | m -> b where type StM (m :: * -> *) a :: * where {
+ Database.EventStore.Internal.Test: class Monad m => MonadIO (m :: * -> *)
+ Database.EventStore.Internal.Test: class Monoid a
+ Database.EventStore.Internal.Test: class Pub p
+ Database.EventStore.Internal.Test: class Semigroup a
+ Database.EventStore.Internal.Test: class Sub s
+ Database.EventStore.Internal.Test: clientIdentifiedCmd :: Command
+ Database.EventStore.Internal.Test: clusterDnsEndPointDiscovery :: ClusterSettings -> IO Discovery
+ Database.EventStore.Internal.Test: connectToPersistentSubscriptionCmd :: Command
+ Database.EventStore.Internal.Test: connectionBuilder :: Settings -> IO ConnectionBuilder
+ Database.EventStore.Internal.Test: connectionError :: Exception e => Connection -> e -> ConnectionError
+ Database.EventStore.Internal.Test: createEvent :: EventType -> Maybe UUID -> EventData -> Event
+ Database.EventStore.Internal.Test: createPersist :: Text -> Text -> PersistentSubscriptionSettings -> Operation (Maybe PersistActionException)
+ Database.EventStore.Internal.Test: createPersistentSubscriptionCmd :: Command
+ Database.EventStore.Internal.Test: createPersistentSubscriptionCompletedCmd :: Command
+ Database.EventStore.Internal.Test: credentials :: ByteString -> ByteString -> Credentials
+ Database.EventStore.Internal.Test: customMetaToPairs :: Object -> [Pair]
+ Database.EventStore.Internal.Test: data Bus
+ Database.EventStore.Internal.Test: data ByteString :: *
+ Database.EventStore.Internal.Test: data Callback a
+ Database.EventStore.Internal.Test: data CatchupOpResult
+ Database.EventStore.Internal.Test: data CatchupState
+ Database.EventStore.Internal.Test: data Checkpoint
+ Database.EventStore.Internal.Test: data ClusterSettings
+ Database.EventStore.Internal.Test: data Command
+ Database.EventStore.Internal.Test: data Connection
+ Database.EventStore.Internal.Test: data ConnectionClosed
+ Database.EventStore.Internal.Test: data ConnectionError
+ Database.EventStore.Internal.Test: data ConnectionEstablished
+ Database.EventStore.Internal.Test: data Credentials
+ Database.EventStore.Internal.Test: data DnsDiscoveryException
+ Database.EventStore.Internal.Test: data DnsServer
+ Database.EventStore.Internal.Test: data EndPoint
+ Database.EventStore.Internal.Test: data Event
+ Database.EventStore.Internal.Test: data EventData
+ Database.EventStore.Internal.Test: data EventRecord
+ Database.EventStore.Internal.Test: data EventStore a
+ Database.EventStore.Internal.Test: data EventType
+ Database.EventStore.Internal.Test: data Exec
+ Database.EventStore.Internal.Test: data ExpectedVersion
+ Database.EventStore.Internal.Test: data FatalException
+ Database.EventStore.Internal.Test: data Flag
+ Database.EventStore.Internal.Test: data GossipSeed
+ Database.EventStore.Internal.Test: data HashMap k v :: * -> * -> *
+ Database.EventStore.Internal.Test: data Hub
+ Database.EventStore.Internal.Test: data InitFailed
+ Database.EventStore.Internal.Test: data Initialized
+ Database.EventStore.Internal.Test: data InternalException
+ Database.EventStore.Internal.Test: data LoggerFilter
+ Database.EventStore.Internal.Test: data LoggerRef
+ Database.EventStore.Internal.Test: data MasterInfo
+ Database.EventStore.Internal.Test: data MasterInfoBuf
+ Database.EventStore.Internal.Test: data Message
+ Database.EventStore.Internal.Test: data NewEvent
+ Database.EventStore.Internal.Test: data NewTimer
+ Database.EventStore.Internal.Test: data NodeEndPoints
+ Database.EventStore.Internal.Test: data NotHandledBuf
+ Database.EventStore.Internal.Test: data NotHandledReason
+ Database.EventStore.Internal.Test: data Package
+ Database.EventStore.Internal.Test: data PackageArrived
+ Database.EventStore.Internal.Test: data PersistentSubscriptionSettings
+ Database.EventStore.Internal.Test: data Position
+ Database.EventStore.Internal.Test: data Publish
+ Database.EventStore.Internal.Test: data ReadDirection
+ Database.EventStore.Internal.Test: data ReadEvent
+ Database.EventStore.Internal.Test: data RecordedEvent
+ Database.EventStore.Internal.Test: data RecvOutcome
+ Database.EventStore.Internal.Test: data ResolvedEvent
+ Database.EventStore.Internal.Test: data ResolvedEventBuf
+ Database.EventStore.Internal.Test: data ResolvedIndexedEvent
+ Database.EventStore.Internal.Test: data Retry
+ Database.EventStore.Internal.Test: data Seq a :: * -> *
+ Database.EventStore.Internal.Test: data Service
+ Database.EventStore.Internal.Test: data ServiceTerminated
+ Database.EventStore.Internal.Test: data Set a :: * -> *
+ Database.EventStore.Internal.Test: data Settings
+ Database.EventStore.Internal.Test: data StreamACL
+ Database.EventStore.Internal.Test: data StreamMetadata
+ Database.EventStore.Internal.Test: data StreamMetadataResult
+ Database.EventStore.Internal.Test: data SubmitOperation
+ Database.EventStore.Internal.Test: data Subscribe
+ Database.EventStore.Internal.Test: data SystemConsumerStrategy
+ Database.EventStore.Internal.Test: data SystemInit
+ Database.EventStore.Internal.Test: data SystemShutdown
+ Database.EventStore.Internal.Test: data Text :: *
+ Database.EventStore.Internal.Test: defaultPersistentSubscriptionSettings :: PersistentSubscriptionSettings
+ Database.EventStore.Internal.Test: defaultSSLSettings :: TLSSettings -> Settings
+ Database.EventStore.Internal.Test: defaultSettings :: Settings
+ Database.EventStore.Internal.Test: deletePersist :: Text -> Text -> Operation (Maybe PersistActionException)
+ Database.EventStore.Internal.Test: deletePersistentSubscriptionCmd :: Command
+ Database.EventStore.Internal.Test: deletePersistentSubscriptionCompletedCmd :: Command
+ Database.EventStore.Internal.Test: deleteStream :: Settings -> Text -> ExpectedVersion -> Maybe Bool -> Operation DeleteResult
+ Database.EventStore.Internal.Test: deleteStreamCmd :: Command
+ Database.EventStore.Internal.Test: deleteStreamCompletedCmd :: Command
+ Database.EventStore.Internal.Test: dnsClusterSettings :: ByteString -> ClusterSettings
+ Database.EventStore.Internal.Test: empty :: Alternative f => f a
+ Database.EventStore.Internal.Test: emptyEndPoint :: EndPoint
+ Database.EventStore.Internal.Test: emptyStreamACL :: StreamACL
+ Database.EventStore.Internal.Test: emptyStreamMetadata :: StreamMetadata
+ Database.EventStore.Internal.Test: emptyStreamVersion :: ExpectedVersion
+ Database.EventStore.Internal.Test: eventDataBytes :: EventData -> ByteString
+ Database.EventStore.Internal.Test: eventDataType :: EventData -> Int32
+ Database.EventStore.Internal.Test: eventMetadataBytes :: EventData -> Maybe ByteString
+ Database.EventStore.Internal.Test: eventMetadataType :: EventData -> Int32
+ Database.EventStore.Internal.Test: eventTypeFromStr :: String -> EventType
+ Database.EventStore.Internal.Test: eventTypeText :: EventType -> Text
+ Database.EventStore.Internal.Test: exactEventVersion :: Int32 -> ExpectedVersion
+ Database.EventStore.Internal.Test: execSettings :: Exec -> Settings
+ Database.EventStore.Internal.Test: execWaitTillClosed :: Exec -> IO ()
+ Database.EventStore.Internal.Test: expVersionInt32 :: ExpectedVersion -> Int32
+ Database.EventStore.Internal.Test: flagWord8 :: Flag -> Word8
+ Database.EventStore.Internal.Test: freshUUID :: EventStore UUID
+ Database.EventStore.Internal.Test: fromEither :: Exception e => Callback a -> Either e a -> IO ()
+ Database.EventStore.Internal.Test: fromMaybe :: a -> Maybe a -> a
+ Database.EventStore.Internal.Test: fromMsg :: Typeable a => Message -> Maybe a
+ Database.EventStore.Internal.Test: fromString :: IsString a => String -> a
+ Database.EventStore.Internal.Test: fulfill :: MonadIO m => Callback a -> a -> m ()
+ Database.EventStore.Internal.Test: getCommand :: Word8 -> Command
+ Database.EventStore.Internal.Test: getConnection :: ConnectionRef -> EventStore Connection
+ Database.EventStore.Internal.Test: getCustomProperty :: FromJSON a => StreamMetadata -> Text -> Maybe a
+ Database.EventStore.Internal.Test: getCustomPropertyValue :: StreamMetadata -> Text -> Maybe Value
+ Database.EventStore.Internal.Test: getSettings :: EventStore Settings
+ Database.EventStore.Internal.Test: gossipSeed :: String -> Int -> GossipSeed
+ Database.EventStore.Internal.Test: gossipSeedClusterSettings :: NonEmpty GossipSeed -> ClusterSettings
+ Database.EventStore.Internal.Test: gossipSeedHeader :: GossipSeed -> String
+ Database.EventStore.Internal.Test: gossipSeedHost :: GossipSeed -> String
+ Database.EventStore.Internal.Test: gossipSeedPort :: GossipSeed -> Int
+ Database.EventStore.Internal.Test: gossipSeedWithHeader :: String -> Int -> String -> GossipSeed
+ Database.EventStore.Internal.Test: hash :: Hashable a => a -> Int
+ Database.EventStore.Internal.Test: hashWithSalt :: Hashable a => Int -> a -> Int
+ Database.EventStore.Internal.Test: heartbeatRequestCmd :: Command
+ Database.EventStore.Internal.Test: heartbeatRequestPackage :: UUID -> Package
+ Database.EventStore.Internal.Test: heartbeatResponseCmd :: Command
+ Database.EventStore.Internal.Test: heartbeatResponsePackage :: UUID -> Package
+ Database.EventStore.Internal.Test: identifyClientCmd :: Command
+ Database.EventStore.Internal.Test: internalMetaProperties :: Set Text
+ Database.EventStore.Internal.Test: isEventResolvedLink :: ResolvedEvent -> Bool
+ Database.EventStore.Internal.Test: isJust :: Maybe a -> Bool
+ Database.EventStore.Internal.Test: keepRetrying :: Retry
+ Database.EventStore.Internal.Test: keepUserProperties :: Object -> Object
+ Database.EventStore.Internal.Test: length :: MonoFoldable mono => mono -> Int
+ Database.EventStore.Internal.Test: lift :: MonadTrans t => forall (m :: * -> *) a. Monad m => m a -> t m a
+ Database.EventStore.Internal.Test: liftBaseWith :: MonadBaseControl b m => (RunInBase m b -> b a) -> m a
+ Database.EventStore.Internal.Test: liftIO :: MonadIO m => IO a -> m a
+ Database.EventStore.Internal.Test: loggerCallback :: LoggerRef -> (Loc -> LogSource -> LogLevel -> LogStr -> IO ())
+ Database.EventStore.Internal.Test: many :: Alternative f => f a -> f [a]
+ Database.EventStore.Internal.Test: mappend :: Monoid a => a -> a -> a
+ Database.EventStore.Internal.Test: masterInfo :: MasterInfoBuf -> MasterInfo
+ Database.EventStore.Internal.Test: masterInfoNodeEndPoints :: MasterInfo -> NodeEndPoints
+ Database.EventStore.Internal.Test: mconcat :: Monoid a => [a] -> a
+ Database.EventStore.Internal.Test: mempty :: Monoid a => a
+ Database.EventStore.Internal.Test: modifyACL :: StreamACLBuilder -> StreamMetadataBuilder
+ Database.EventStore.Internal.Test: modifyStreamACL :: StreamACLBuilder -> StreamACL -> StreamACL
+ Database.EventStore.Internal.Test: modifyStreamMetadata :: StreamMetadataBuilder -> StreamMetadata -> StreamMetadata
+ Database.EventStore.Internal.Test: monitorAddDataTransmitted :: Int -> EventStore ()
+ Database.EventStore.Internal.Test: monitorIncrConnectionDrop :: EventStore ()
+ Database.EventStore.Internal.Test: monitorIncrForceReconnect :: EventStore ()
+ Database.EventStore.Internal.Test: monitorIncrHeartbeatTimeouts :: EventStore ()
+ Database.EventStore.Internal.Test: monitorIncrPkgCount :: EventStore ()
+ Database.EventStore.Internal.Test: msDiffTime :: Float -> NominalDiffTime
+ Database.EventStore.Internal.Test: msDuration :: Int64 -> Duration
+ Database.EventStore.Internal.Test: newBus :: LoggerRef -> Settings -> IO Bus
+ Database.EventStore.Internal.Test: newCallback :: (Either SomeException a -> IO ()) -> IO (Callback a)
+ Database.EventStore.Internal.Test: newEvent :: Text -> UUID -> Int32 -> Int32 -> ByteString -> Maybe ByteString -> NewEvent
+ Database.EventStore.Internal.Test: newExec :: Settings -> Bus -> ConnectionBuilder -> Discovery -> IO Exec
+ Database.EventStore.Internal.Test: newLoggerRef :: LogType -> LoggerFilter -> Bool -> IO LoggerRef
+ Database.EventStore.Internal.Test: newPromise :: IO (Callback a)
+ Database.EventStore.Internal.Test: newRecordedEvent :: EventRecord -> RecordedEvent
+ Database.EventStore.Internal.Test: newResolvedEvent :: ResolvedIndexedEvent -> ResolvedEvent
+ Database.EventStore.Internal.Test: newResolvedEventFromBuf :: ResolvedEventBuf -> ResolvedEvent
+ Database.EventStore.Internal.Test: newtype ConnectionBuilder
+ Database.EventStore.Internal.Test: newtype ConnectionRef
+ Database.EventStore.Internal.Test: newtype DeleteResult
+ Database.EventStore.Internal.Test: newtype Discovery
+ Database.EventStore.Internal.Test: newtype Down a :: * -> *
+ Database.EventStore.Internal.Test: newtype Duration
+ Database.EventStore.Internal.Test: newtype SendPackage
+ Database.EventStore.Internal.Test: newtype Terminated
+ Database.EventStore.Internal.Test: noStreamVersion :: ExpectedVersion
+ Database.EventStore.Internal.Test: notAuthenticatedCmd :: Command
+ Database.EventStore.Internal.Test: notHandledCmd :: Command
+ Database.EventStore.Internal.Test: null :: MonoFoldable mono => mono -> Bool
+ Database.EventStore.Internal.Test: p_acl :: Text
+ Database.EventStore.Internal.Test: p_cacheControl :: Text
+ Database.EventStore.Internal.Test: p_deleteRoles :: Text
+ Database.EventStore.Internal.Test: p_maxAge :: Text
+ Database.EventStore.Internal.Test: p_maxCount :: Text
+ Database.EventStore.Internal.Test: p_metaReadRoles :: Text
+ Database.EventStore.Internal.Test: p_metaWriteRoles :: Text
+ Database.EventStore.Internal.Test: p_readRoles :: Text
+ Database.EventStore.Internal.Test: p_truncateBefore :: Text
+ Database.EventStore.Internal.Test: p_writeRoles :: Text
+ Database.EventStore.Internal.Test: packageDataAsText :: Package -> Maybe Text
+ Database.EventStore.Internal.Test: parseNominalDiffTime :: Text -> Object -> Parser (Maybe NominalDiffTime)
+ Database.EventStore.Internal.Test: parseStreamACL :: Value -> Parser StreamACL
+ Database.EventStore.Internal.Test: parseStreamMetadata :: Value -> Parser StreamMetadata
+ Database.EventStore.Internal.Test: persistentSubscriptionAckEventsCmd :: Command
+ Database.EventStore.Internal.Test: persistentSubscriptionConfirmationCmd :: Command
+ Database.EventStore.Internal.Test: persistentSubscriptionNakEventsCmd :: Command
+ Database.EventStore.Internal.Test: persistentSubscriptionStreamEventAppearedCmd :: Command
+ Database.EventStore.Internal.Test: positionEnd :: Position
+ Database.EventStore.Internal.Test: positionStart :: Position
+ Database.EventStore.Internal.Test: publish :: Typeable a => a -> EventStore ()
+ Database.EventStore.Internal.Test: publishSTM :: (Pub p, Typeable a) => p -> a -> STM Bool
+ Database.EventStore.Internal.Test: publishWith :: (Pub p, Typeable a, MonadIO m) => p -> a -> m ()
+ Database.EventStore.Internal.Test: publisher :: EventStore Publish
+ Database.EventStore.Internal.Test: readAllEvents :: Settings -> Int64 -> Int64 -> Int32 -> Bool -> ReadDirection -> Operation AllSlice
+ Database.EventStore.Internal.Test: readAllEventsBackwardCmd :: Command
+ Database.EventStore.Internal.Test: readAllEventsBackwardCompletedCmd :: Command
+ Database.EventStore.Internal.Test: readAllEventsForwardCmd :: Command
+ Database.EventStore.Internal.Test: readAllEventsForwardCompletedCmd :: Command
+ Database.EventStore.Internal.Test: readEvent :: Settings -> Text -> Int32 -> Bool -> Operation (ReadResult RegularStream ReadEvent)
+ Database.EventStore.Internal.Test: readEventCmd :: Command
+ Database.EventStore.Internal.Test: readEventCompletedCmd :: Command
+ Database.EventStore.Internal.Test: readMetaStream :: Settings -> Text -> Operation StreamMetadataResult
+ Database.EventStore.Internal.Test: readStreamEvents :: Settings -> ReadDirection -> Text -> Int32 -> Int32 -> Bool -> Operation (ReadResult RegularStream StreamSlice)
+ Database.EventStore.Internal.Test: readStreamEventsBackwardCmd :: Command
+ Database.EventStore.Internal.Test: readStreamEventsBackwardCompletedCmd :: Command
+ Database.EventStore.Internal.Test: readStreamEventsForwardCmd :: Command
+ Database.EventStore.Internal.Test: readStreamEventsForwardCompletedCmd :: Command
+ Database.EventStore.Internal.Test: recordedEventDataAsJson :: FromJSON a => RecordedEvent -> Maybe a
+ Database.EventStore.Internal.Test: reject :: (Exception e, MonadIO m) => Callback a -> e -> m ()
+ Database.EventStore.Internal.Test: resolvedEventDataAsJson :: FromJSON a => ResolvedEvent -> Maybe a
+ Database.EventStore.Internal.Test: resolvedEventOriginal :: ResolvedEvent -> RecordedEvent
+ Database.EventStore.Internal.Test: resolvedEventOriginalEventNumber :: ResolvedEvent -> Int32
+ Database.EventStore.Internal.Test: resolvedEventOriginalId :: ResolvedEvent -> UUID
+ Database.EventStore.Internal.Test: resolvedEventOriginalStreamId :: ResolvedEvent -> Text
+ Database.EventStore.Internal.Test: restoreM :: MonadBaseControl b m => StM m a -> m a
+ Database.EventStore.Internal.Test: retrieve :: Callback a -> IO a
+ Database.EventStore.Internal.Test: retrySTM :: STM a
+ Database.EventStore.Internal.Test: runEventStore :: LoggerRef -> Settings -> Bus -> EventStore a -> IO a
+ Database.EventStore.Internal.Test: sconcat :: Semigroup a => NonEmpty a -> a
+ Database.EventStore.Internal.Test: secsDuration :: Int64 -> Duration
+ Database.EventStore.Internal.Test: setACL :: StreamACL -> StreamMetadataBuilder
+ Database.EventStore.Internal.Test: setCacheControl :: TimeSpan -> StreamMetadataBuilder
+ Database.EventStore.Internal.Test: setCustomProperty :: ToJSON a => Text -> a -> StreamMetadataBuilder
+ Database.EventStore.Internal.Test: setDeleteRole :: Text -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setDeleteRoles :: [Text] -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setMaxAge :: TimeSpan -> StreamMetadataBuilder
+ Database.EventStore.Internal.Test: setMaxCount :: Int32 -> StreamMetadataBuilder
+ Database.EventStore.Internal.Test: setMetaReadRole :: Text -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setMetaReadRoles :: [Text] -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setMetaStream :: Settings -> Text -> ExpectedVersion -> StreamMetadata -> Operation WriteResult
+ Database.EventStore.Internal.Test: setMetaWriteRole :: Text -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setMetaWriteRoles :: [Text] -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setReadRole :: Text -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setReadRoles :: [Text] -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setTruncateBefore :: Int32 -> StreamMetadataBuilder
+ Database.EventStore.Internal.Test: setWriteRole :: Text -> StreamACLBuilder
+ Database.EventStore.Internal.Test: setWriteRoles :: [Text] -> StreamACLBuilder
+ Database.EventStore.Internal.Test: simpleDnsEndPointDiscovery :: ByteString -> Maybe DnsServer -> Int -> Discovery
+ Database.EventStore.Internal.Test: some :: Alternative f => f a -> f [a]
+ Database.EventStore.Internal.Test: staticEndPointDiscovery :: String -> Int -> Discovery
+ Database.EventStore.Internal.Test: stimes :: (Semigroup a, Integral b) => b -> a -> a
+ Database.EventStore.Internal.Test: stopBus :: EventStore ()
+ Database.EventStore.Internal.Test: strategyFromText :: Text -> Maybe SystemConsumerStrategy
+ Database.EventStore.Internal.Test: strategyText :: SystemConsumerStrategy -> Text
+ Database.EventStore.Internal.Test: streamACLJSON :: StreamACL -> Value
+ Database.EventStore.Internal.Test: streamEventAppearedCmd :: Command
+ Database.EventStore.Internal.Test: streamExists :: ExpectedVersion
+ Database.EventStore.Internal.Test: streamMetadataJSON :: StreamMetadata -> Value
+ Database.EventStore.Internal.Test: subscribe :: (Sub s, Typeable a) => s -> (a -> EventStore ()) -> IO ()
+ Database.EventStore.Internal.Test: subscribeEventHandler :: Sub s => s -> EventHandler -> IO ()
+ Database.EventStore.Internal.Test: subscribeToStreamCmd :: Command
+ Database.EventStore.Internal.Test: subscriptionConfirmationCmd :: Command
+ Database.EventStore.Internal.Test: subscriptionDroppedCmd :: Command
+ Database.EventStore.Internal.Test: toMsg :: Typeable a => a -> Message
+ Database.EventStore.Internal.Test: toUTC :: Int64 -> UTCTime
+ Database.EventStore.Internal.Test: transactionCommit :: Settings -> Text -> ExpectedVersion -> Int64 -> Operation WriteResult
+ Database.EventStore.Internal.Test: transactionCommitCmd :: Command
+ Database.EventStore.Internal.Test: transactionCommitCompletedCmd :: Command
+ Database.EventStore.Internal.Test: transactionStart :: Settings -> Text -> ExpectedVersion -> Operation Int64
+ Database.EventStore.Internal.Test: transactionStartCmd :: Command
+ Database.EventStore.Internal.Test: transactionStartCompletedCmd :: Command
+ Database.EventStore.Internal.Test: transactionWrite :: Settings -> Text -> ExpectedVersion -> Int64 -> [Event] -> Operation ()
+ Database.EventStore.Internal.Test: transactionWriteCmd :: Command
+ Database.EventStore.Internal.Test: transactionWriteCompletedCmd :: Command
+ Database.EventStore.Internal.Test: tryRetrieve :: Callback a -> IO (Either SomeException a)
+ Database.EventStore.Internal.Test: tshow :: Show a => a -> Text
+ Database.EventStore.Internal.Test: type Builder a = Endo a
+ Database.EventStore.Internal.Test: type StreamACLBuilder = Builder StreamACL
+ Database.EventStore.Internal.Test: type StreamMetadataBuilder = Builder StreamMetadata
+ Database.EventStore.Internal.Test: type family StM (m :: * -> *) a :: *;
+ Database.EventStore.Internal.Test: unknownCmd :: Word8 -> Command
+ Database.EventStore.Internal.Test: unlessM :: Monad m => m Bool -> m () -> m ()
+ Database.EventStore.Internal.Test: unsubscribeFromStreamCmd :: Command
+ Database.EventStore.Internal.Test: updatePersist :: Text -> Text -> PersistentSubscriptionSettings -> Operation (Maybe PersistActionException)
+ Database.EventStore.Internal.Test: updatePersistentSubscriptionCmd :: Command
+ Database.EventStore.Internal.Test: updatePersistentSubscriptionCompletedCmd :: Command
+ Database.EventStore.Internal.Test: whenM :: Monad m => m Bool -> m () -> m ()
+ Database.EventStore.Internal.Test: withBinary :: ByteString -> EventData
+ Database.EventStore.Internal.Test: withBinaryAndMetadata :: ByteString -> ByteString -> EventData
+ Database.EventStore.Internal.Test: withJson :: ToJSON a => a -> EventData
+ Database.EventStore.Internal.Test: withJsonAndMetadata :: (ToJSON a, ToJSON b) => a -> b -> EventData
+ Database.EventStore.Internal.Test: writeEvents :: Settings -> Text -> ExpectedVersion -> [Event] -> Operation WriteResult
+ Database.EventStore.Internal.Test: writeEventsCmd :: Command
+ Database.EventStore.Internal.Test: writeEventsCompletedCmd :: Command
+ Database.EventStore.Internal.Test: zip :: [a] -> [b] -> [(a, b)]
+ Database.EventStore.Internal.Test: zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
+ Database.EventStore.Internal.Test: }
- Database.EventStore: Settings :: NominalDiffTime -> NominalDiffTime -> Bool -> Maybe Credentials -> Retry -> Int -> Maybe (Log -> IO ()) -> Maybe TLSSettings -> Settings
+ Database.EventStore: Settings :: NominalDiffTime -> NominalDiffTime -> Bool -> Maybe Credentials -> Retry -> NominalDiffTime -> Maybe TLSSettings -> LogType -> LoggerFilter -> Bool -> NominalDiffTime -> Retry -> Maybe Store -> Settings
- Database.EventStore: SubscriptionClosed :: SubDropReason -> SubscriptionClosed
+ Database.EventStore: SubscriptionClosed :: (Maybe SubDropReason) -> SubscriptionClosed
- Database.EventStore: acknowledge :: Subscription Persistent -> ResolvedEvent -> IO ()
+ Database.EventStore: acknowledge :: PersistentSubscription -> ResolvedEvent -> IO ()
- Database.EventStore: acknowledgeEvents :: Subscription Persistent -> [ResolvedEvent] -> IO ()
+ Database.EventStore: acknowledgeEvents :: PersistentSubscription -> [ResolvedEvent] -> IO ()
- Database.EventStore: connectToPersistentSubscription :: Connection -> Text -> Text -> Int32 -> IO (Subscription Persistent)
+ Database.EventStore: connectToPersistentSubscription :: Connection -> Text -> StreamName -> Int32 -> IO PersistentSubscription
- Database.EventStore: createPersistentSubscription :: Connection -> Text -> Text -> PersistentSubscriptionSettings -> IO (Async (Maybe PersistActionException))
+ Database.EventStore: createPersistentSubscription :: Connection -> Text -> StreamName -> PersistentSubscriptionSettings -> IO (Async (Maybe PersistActionException))
- Database.EventStore: deletePersistentSubscription :: Connection -> Text -> Text -> IO (Async (Maybe PersistActionException))
+ Database.EventStore: deletePersistentSubscription :: Connection -> Text -> StreamName -> IO (Async (Maybe PersistActionException))
- Database.EventStore: deleteStream :: Connection -> Text -> ExpectedVersion -> Maybe Bool -> IO (Async DeleteResult)
+ Database.EventStore: deleteStream :: Connection -> StreamName -> ExpectedVersion -> Maybe Bool -> IO (Async DeleteResult)
- Database.EventStore: eventsFailed :: Subscription Persistent -> [ResolvedEvent] -> NakAction -> Maybe Text -> IO ()
+ Database.EventStore: eventsFailed :: PersistentSubscription -> [ResolvedEvent] -> NakAction -> Maybe Text -> IO ()
- Database.EventStore: failed :: Subscription Persistent -> ResolvedEvent -> NakAction -> Maybe Text -> IO ()
+ Database.EventStore: failed :: PersistentSubscription -> ResolvedEvent -> NakAction -> Maybe Text -> IO ()
- Database.EventStore: getStreamMetadata :: Connection -> Text -> IO (Async StreamMetadataResult)
+ Database.EventStore: getStreamMetadata :: Connection -> StreamName -> IO (Async StreamMetadataResult)
- Database.EventStore: hasCaughtUp :: Subscription Catchup -> IO Bool
+ Database.EventStore: hasCaughtUp :: CatchupSubscription -> IO Bool
- Database.EventStore: hasCaughtUpSTM :: Subscription Catchup -> STM Bool
+ Database.EventStore: hasCaughtUpSTM :: CatchupSubscription -> STM Bool
- Database.EventStore: isSubscribedToAll :: Subscription a -> Bool
+ Database.EventStore: isSubscribedToAll :: Subscription s => s -> Bool
- Database.EventStore: nextEvent :: Subscription a -> IO ResolvedEvent
+ Database.EventStore: nextEvent :: Subscription s => s -> IO ResolvedEvent
- Database.EventStore: nextEventMaybe :: Subscription a -> IO (Maybe ResolvedEvent)
+ Database.EventStore: nextEventMaybe :: Subscription s => s -> IO (Maybe ResolvedEvent)
- Database.EventStore: notifyEventsFailed :: Subscription Persistent -> NakAction -> Maybe Text -> [UUID] -> IO ()
+ Database.EventStore: notifyEventsFailed :: PersistentSubscription -> NakAction -> Maybe Text -> [UUID] -> IO ()
- Database.EventStore: notifyEventsProcessed :: Subscription Persistent -> [UUID] -> IO ()
+ Database.EventStore: notifyEventsProcessed :: PersistentSubscription -> [UUID] -> IO ()
- Database.EventStore: readEvent :: Connection -> Text -> Int32 -> Bool -> IO (Async (ReadResult RegularStream ReadEvent))
+ Database.EventStore: readEvent :: Connection -> StreamName -> Int32 -> Bool -> IO (Async (ReadResult RegularStream ReadEvent))
- Database.EventStore: readStreamEventsBackward :: Connection -> Text -> Int32 -> Int32 -> Bool -> IO (Async (ReadResult RegularStream StreamSlice))
+ Database.EventStore: readStreamEventsBackward :: Connection -> StreamName -> Int32 -> Int32 -> Bool -> IO (Async (ReadResult RegularStream StreamSlice))
- Database.EventStore: readStreamEventsForward :: Connection -> Text -> Int32 -> Int32 -> Bool -> IO (Async (ReadResult RegularStream StreamSlice))
+ Database.EventStore: readStreamEventsForward :: Connection -> StreamName -> Int32 -> Int32 -> Bool -> IO (Async (ReadResult RegularStream StreamSlice))
- Database.EventStore: sendEvent :: Connection -> Text -> ExpectedVersion -> Event -> IO (Async WriteResult)
+ Database.EventStore: sendEvent :: Connection -> StreamName -> ExpectedVersion -> Event -> IO (Async WriteResult)
- Database.EventStore: sendEvents :: Connection -> Text -> ExpectedVersion -> [Event] -> IO (Async WriteResult)
+ Database.EventStore: sendEvents :: Connection -> StreamName -> ExpectedVersion -> [Event] -> IO (Async WriteResult)
- Database.EventStore: setStreamMetadata :: Connection -> Text -> ExpectedVersion -> StreamMetadata -> IO (Async WriteResult)
+ Database.EventStore: setStreamMetadata :: Connection -> StreamName -> ExpectedVersion -> StreamMetadata -> IO (Async WriteResult)
- Database.EventStore: startTransaction :: Connection -> Text -> ExpectedVersion -> IO (Async Transaction)
+ Database.EventStore: startTransaction :: Connection -> StreamName -> ExpectedVersion -> IO (Async Transaction)
- Database.EventStore: subscribe :: Connection -> Text -> Bool -> IO (Subscription Regular)
+ Database.EventStore: subscribe :: Connection -> StreamName -> Bool -> IO RegularSubscription
- Database.EventStore: subscribeFrom :: Connection -> Text -> Bool -> Maybe Int32 -> Maybe Int32 -> IO (Subscription Catchup)
+ Database.EventStore: subscribeFrom :: Connection -> StreamName -> Bool -> Maybe Int32 -> Maybe Int32 -> IO CatchupSubscription
- Database.EventStore: subscribeToAll :: Connection -> Bool -> IO (Subscription Regular)
+ Database.EventStore: subscribeToAll :: Connection -> Bool -> IO RegularSubscription
- Database.EventStore: subscribeToAllFrom :: Connection -> Bool -> Maybe Position -> Maybe Int32 -> IO (Subscription Catchup)
+ Database.EventStore: subscribeToAllFrom :: Connection -> Bool -> Maybe Position -> Maybe Int32 -> IO CatchupSubscription
- Database.EventStore: unsubscribe :: Subscription a -> IO ()
+ Database.EventStore: unsubscribe :: Subscription s => s -> IO ()
- Database.EventStore: unsubscribeConfirmed :: Subscription a -> IO Bool
+ Database.EventStore: unsubscribeConfirmed :: Subscription s => s -> IO Bool
- Database.EventStore: unsubscribeConfirmedSTM :: Subscription a -> STM Bool
+ Database.EventStore: unsubscribeConfirmedSTM :: Subscription s => s -> STM Bool
- Database.EventStore: updatePersistentSubscription :: Connection -> Text -> Text -> PersistentSubscriptionSettings -> IO (Async (Maybe PersistActionException))
+ Database.EventStore: updatePersistentSubscription :: Connection -> Text -> StreamName -> PersistentSubscriptionSettings -> IO (Async (Maybe PersistActionException))
- Database.EventStore: waitConfirmation :: Subscription a -> IO ()
+ Database.EventStore: waitConfirmation :: Subscription s => s -> IO ()
- Database.EventStore: waitTillCatchup :: Subscription Catchup -> IO ()
+ Database.EventStore: waitTillCatchup :: CatchupSubscription -> IO ()
- Database.EventStore: waitUnsubscribeConfirmed :: Subscription a -> IO ()
+ Database.EventStore: waitUnsubscribeConfirmed :: Subscription s => s -> IO ()
Files
- .gitignore +0/−0
- .travis.yml +159/−74
- CHANGELOG.markdown +9/−2
- Database/EventStore.hs +141/−156
- Database/EventStore/Internal/Callback.hs +111/−0
- Database/EventStore/Internal/Command.hs +395/−6
- Database/EventStore/Internal/Communication.hs +67/−0
- Database/EventStore/Internal/Connection.hs +181/−220
- Database/EventStore/Internal/ConnectionManager.hs +499/−0
- Database/EventStore/Internal/Control.hs +419/−0
- Database/EventStore/Internal/Discovery.hs +4/−3
- Database/EventStore/Internal/EndPoint.hs +9/−2
- Database/EventStore/Internal/Exec.hs +176/−0
- Database/EventStore/Internal/Execution/Production.hs +0/−587
- Database/EventStore/Internal/Generator.hs +0/−43
- Database/EventStore/Internal/Logger.hs +73/−0
- Database/EventStore/Internal/Manager/Operation/Model.hs +0/−204
- Database/EventStore/Internal/Manager/Operation/Registry.hs +427/−0
- Database/EventStore/Internal/Manager/Subscription/Command.hs +0/−103
- Database/EventStore/Internal/Manager/Subscription/Driver.hs +0/−550
- Database/EventStore/Internal/Manager/Subscription/Message.hs +0/−408
- Database/EventStore/Internal/Manager/Subscription/Model.hs +0/−317
- Database/EventStore/Internal/Manager/Subscription/Packages.hs +0/−133
- Database/EventStore/Internal/Manager/Subscription/Types.hs +0/−40
- Database/EventStore/Internal/Operation.hs +138/−75
- Database/EventStore/Internal/Operation/Catchup.hs +51/−41
- Database/EventStore/Internal/Operation/DeleteStream.hs +5/−3
- Database/EventStore/Internal/Operation/DeleteStream/Message.hs +1/−1
- Database/EventStore/Internal/Operation/Persist.hs +79/−0
- Database/EventStore/Internal/Operation/PersistOperations.hs +75/−0
- Database/EventStore/Internal/Operation/Read/Common.hs +55/−4
- Database/EventStore/Internal/Operation/ReadAllEvents.hs +8/−6
- Database/EventStore/Internal/Operation/ReadAllEvents/Message.hs +1/−1
- Database/EventStore/Internal/Operation/ReadEvent.hs +5/−3
- Database/EventStore/Internal/Operation/ReadEvent/Message.hs +9/−3
- Database/EventStore/Internal/Operation/ReadStreamEvents.hs +8/−6
- Database/EventStore/Internal/Operation/ReadStreamEvents/Message.hs +1/−1
- Database/EventStore/Internal/Operation/StreamMetadata.hs +14/−13
- Database/EventStore/Internal/Operation/Transaction.hs +9/−7
- Database/EventStore/Internal/Operation/Transaction/Message.hs +1/−1
- Database/EventStore/Internal/Operation/Volatile.hs +78/−0
- Database/EventStore/Internal/Operation/Write/Common.hs +2/−4
- Database/EventStore/Internal/Operation/WriteEvents.hs +5/−3
- Database/EventStore/Internal/Operation/WriteEvents/Message.hs +1/−1
- Database/EventStore/Internal/OperationManager.hs +60/−0
- Database/EventStore/Internal/Operations.hs +2/−0
- Database/EventStore/Internal/Prelude.hs +185/−0
- Database/EventStore/Internal/Processor.hs +0/−328
- Database/EventStore/Internal/Settings.hs +149/−0
- Database/EventStore/Internal/Stopwatch.hs +54/−0
- Database/EventStore/Internal/Stream.hs +15/−4
- Database/EventStore/Internal/Subscription.hs +0/−810
- Database/EventStore/Internal/Subscription/Api.hs +122/−0
- Database/EventStore/Internal/Subscription/Catchup.hs +197/−0
- Database/EventStore/Internal/Subscription/Command.hs +113/−0
- Database/EventStore/Internal/Subscription/Message.hs +413/−0
- Database/EventStore/Internal/Subscription/Packages.hs +135/−0
- Database/EventStore/Internal/Subscription/Persistent.hs +184/−0
- Database/EventStore/Internal/Subscription/Regular.hs +102/−0
- Database/EventStore/Internal/Subscription/Types.hs +138/−0
- Database/EventStore/Internal/Test.hs +41/−0
- Database/EventStore/Internal/TimerService.hs +74/−0
- Database/EventStore/Internal/Types.hs +66/−131
- Database/EventStore/Internal/Utils.hs +3/−2
- Database/EventStore/Logging.hs +0/−68
- README.md +5/−5
- eventstore-test-setup-osx.sh +5/−0
- eventstore-test-setup.sh +3/−3
- eventstore.cabal +174/−150
- tests/Main.hs +40/−0
- tests/Test/Bogus/Connection.hs +79/−0
- tests/Test/Bus.hs +55/−0
- tests/Test/Common.hs +65/−0
- tests/Test/Connection.hs +52/−0
- tests/Test/Integration.hs +26/−0
- tests/Test/Integration/Tests.hs +454/−0
- tests/Test/Operation.hs +69/−0
- tests/Tests.hs +0/−397
- tests/integration.hs +0/−53
.gitignore view
.travis.yml view
@@ -1,91 +1,176 @@-# This file has been generated -- see https://github.com/hvr/multi-ghc-travis-language: c+# This is the complex Travis configuration, which is intended for use+# on open source libraries which need compatibility across multiple GHC+# versions, must work with cabal-install, and should be+# cross-platform. For more information and other options, see:+#+# https://docs.haskellstack.org/en/stable/travis_ci/+#+# Copy these contents into the root directory of your Github project in a file+# named .travis.yml++# Use new container infrastructure to enable caching sudo: false +# Do not choose a language; we provide our own build tools.+language: generic++# Caching so the next build will be fast too. cache: directories:- - $HOME/.cabsnap- - $HOME/.cabal/packages--before_cache:- - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log- - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar+ - $HOME/.ghc+ - $HOME/.cabal+ - $HOME/.stack +# The different configurations we want to test. We have BUILD=cabal which uses+# cabal-install, and BUILD=stack which uses Stack. More documentation on each+# of those below.+#+# We set the compiler values here to tell Travis to use a different+# cache file per set of arguments.+#+# If you need to have different apt packages for each combination in the+# matrix, you can use a line such as:+# addons: {apt: {packages: [libfcgi-dev,libgmp-dev]}} matrix: include:- - env: CABALVER=1.18 GHCVER=7.8.3- compiler: ": #GHC 7.8.3"- addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.3], sources: [hvr-ghc]}}- - env: CABALVER=1.18 GHCVER=7.8.4- compiler: ": #GHC 7.8.4"- addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}- - env: CABALVER=1.22 GHCVER=7.10.1- compiler: ": #GHC 7.10.1"- addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.1], sources: [hvr-ghc]}}- - env: CABALVER=1.22 GHCVER=7.10.2- compiler: ": #GHC 7.10.2"- addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.2], sources: [hvr-ghc]}}- - env: CABALVER=1.22 GHCVER=7.10.3- compiler: ": #GHC 7.10.3"- addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.3], sources: [hvr-ghc]}}- - env: CABALVER=1.24 GHCVER=8.0.1- compiler: ": #GHC 8.0.1"- addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.1], sources: [hvr-ghc]}}+ # We grab the appropriate GHC and cabal-install versions from hvr's PPA. See:+ # https://github.com/hvr/multi-ghc-travis+ - env: BUILD=cabal GHCVER=7.8.4 CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7+ compiler: ": #GHC 7.8.4"+ addons: {apt: {packages: [cabal-install-head,ghc-7.8.4,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}}+ - env: BUILD=cabal GHCVER=7.10.3 CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7+ compiler: ": #GHC 7.10.3"+ addons: {apt: {packages: [cabal-install-head,ghc-7.10.3,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}}+ - env: BUILD=cabal GHCVER=8.0.2 CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7+ compiler: ": #GHC 8.0.2"+ addons: {apt: {packages: [cabal-install-head,ghc-8.0.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}}++ # Build with the newest GHC and cabal-install. This is an accepted failure,+ # see below.+ - env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7+ compiler: ": #GHC HEAD"+ addons: {apt: {packages: [cabal-install-head,ghc-head,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}}++ # The Stack builds. We can pass in arbitrary Stack arguments via the ARGS+ # variable, such as using --stack-yaml to point to a different file.+ - env: BUILD=stack ARGS=""+ compiler: ": #stack default"+ addons: {apt: {packages: [libgmp-dev]}}++ - env: BUILD=stack ARGS="--resolver lts-8"+ compiler: ": #stack 8.0.2"+ addons: {apt: {packages: [libgmp-dev]}}++ # Nightly builds are allowed to fail+ - env: BUILD=stack ARGS="--resolver nightly"+ compiler: ": #stack nightly"+ addons: {apt: {packages: [libgmp-dev]}}++ # Build on OS X in addition to Linux+ - env: BUILD=stack ARGS=""+ compiler: ": #stack default osx"+ os: osx++ # Travis includes an OS X which is incompatible with GHC 7.8.4++ - env: BUILD=stack ARGS="--resolver lts-8"+ compiler: ": #stack 8.0.2 osx"+ os: osx++ - env: BUILD=stack ARGS="--resolver nightly"+ compiler: ": #stack nightly osx"+ os: osx++ allow_failures:+ - env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7+ - env: BUILD=stack ARGS="--resolver nightly"+ before_install:- - unset CC- - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH- - bash eventstore-test-setup.sh+# Using compiler above sets CC to an invalid value, so unset it+- unset CC +# Setting up GetEventStore database.+- |+ if [ `uname` = "Darwin" ]+ then+ bash eventstore-test-setup-osx.sh+ else+ bash eventstore-test-setup.sh+ fi++# We want to always allow newer versions of packages when building on GHC HEAD+- CABALARGS=""+- if [ "x$GHCVER" = "xhead" ]; then CABALARGS=--allow-newer; fi++# Download and unpack the stack executable+- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.local/bin:/opt/alex/$ALEXVER/bin:/opt/happy/$HAPPYVER/bin:$HOME/.cabal/bin:$PATH+- mkdir -p ~/.local/bin+- |+ if [ `uname` = "Darwin" ]+ then+ travis_retry curl --insecure -L https://www.stackage.org/stack/osx-x86_64 | tar xz --strip-components=1 --include '*/stack' -C ~/.local/bin+ else+ travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'+ fi++ # Use the more reliable S3 mirror of Hackage+ mkdir -p $HOME/.cabal+ echo 'remote-repo: hackage.haskell.org:http://hackage.fpcomplete.com/' > $HOME/.cabal/config+ echo 'remote-repo-cache: $HOME/.cabal/packages' >> $HOME/.cabal/config++ if [ "$CABALVER" != "1.16" ]+ then+ echo 'jobs: $ncpus' >> $HOME/.cabal/config+ fi+ install:- - cabal --version- - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"- - if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ];- then- zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz >- $HOME/.cabal/packages/hackage.haskell.org/00-index.tar;- fi- - travis_retry cabal update -v- - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config- - cabal install --only-dependencies --enable-tests --enable-benchmarks --dry -v > installplan.txt- - sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt+- echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"+- if [ -f configure.ac ]; then autoreconf -i; fi+- |+ set -ex+ case "$BUILD" in+ stack)+ stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies+ ;;+ cabal)+ cabal --version+ travis_retry cabal update -# check whether current requested install-plan matches cached package-db snapshot- - if diff -u installplan.txt $HOME/.cabsnap/installplan.txt;- then- echo "cabal build-cache HIT";- rm -rfv .ghc;- cp -a $HOME/.cabsnap/ghc $HOME/.ghc;- cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/;- else- echo "cabal build-cache MISS";- rm -rf $HOME/.cabsnap;- mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;- cabal install --only-dependencies --enable-tests --enable-benchmarks;- fi+ # Get the list of packages from the stack.yaml file+ PACKAGES=$(stack --install-ghc query locals | grep '^ *path' | sed 's@^ *path:@@') -# snapshot package-db on cache miss- - if [ ! -d $HOME/.cabsnap ];- then- echo "snapshotting package-db to build-cache";- mkdir $HOME/.cabsnap;- cp -a $HOME/.ghc $HOME/.cabsnap/ghc;- cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;- fi+ cabal install --only-dependencies --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES+ ;;+ esac+ set +ex -# Here starts the actual work to be performed for the package under test;-# any command which exits with a non-zero exit code causes the build to fail. script:- - if [ -f configure.ac ]; then autoreconf -i; fi- - cabal configure --enable-tests --enable-benchmarks -v2 # -v2 provides useful information for debugging- - cabal build # this builds all libraries and executables (including tests/benchmarks)- - cabal test- - cabal check- - cabal sdist # tests that a source-distribution can be generated+- |+ set -ex+ case "$BUILD" in+ stack)+ stack --no-terminal $ARGS test --bench --no-run-benchmarks --haddock --no-haddock-deps+ ;;+ cabal)+ cabal install --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES -# Check that the resulting source distribution can be built & installed.-# If there are no other `.tar.gz` files in `dist`, this can be even simpler:-# `cabal install --force-reinstalls dist/*-*.tar.gz`- - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz &&- (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+ ORIGDIR=$(pwd)+ for dir in $PACKAGES+ do+ cd $dir+ cabal check || [ "$CABALVER" == "1.16" ]+ cabal sdist+ PKGVER=$(cabal info . | awk '{print $2;exit}')+ SRC_TGZ=$PKGVER.tar.gz+ cd dist+ tar zxfv "$SRC_TGZ"+ cd "$PKGVER"+ cabal configure --enable-tests+ cabal build+ cd $ORIGDIR+ done+ ;;+ esac+ set +ex # EOF
CHANGELOG.markdown view
@@ -1,6 +1,13 @@-0.14.0.2+0.15.0.0 ---------* Bump aeson version.+* Overall internal components refactoring.+* Decrease memory pressure by 10 folds.+* Improve general performance.+* Provide proper logging support using `fast-logger` library.+* Support operation timeout detection.+* Detects if the server is overwhelmed and act accordingly.+* Improve connection management code.+* Expose EKG monitoring metrics. 0.14.0.1 ----------
Database/EventStore.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore@@ -18,7 +19,6 @@ ( -- * Connection Connection , ConnectionType(..)- , ConnectionException(..) , Credentials , Settings(..) , Retry@@ -51,6 +51,8 @@ , withJsonAndMetadata , withBinary , withBinaryAndMetadata+ -- * Common Operation types+ , OperationMaxAttemptReached(..) -- * Read Operations , StreamMetadataResult(..) , readEvent@@ -110,32 +112,32 @@ , SubscriptionId , Subscription , SubDropReason(..)+ , SubDetails , waitConfirmation , unsubscribeConfirmed , unsubscribeConfirmedSTM , waitUnsubscribeConfirmed+ , nextEventMaybeSTM+ , getSubscriptionDetailsSTM+ , unsubscribe+ , subscriptionStream -- * Volatile Subscription- , Regular+ , RegularSubscription , subscribe , subscribeToAll- , getSubId- , getSubStream+ , getSubscriptionId , isSubscribedToAll- , unsubscribe , nextEvent , nextEventMaybe- , getSubResolveLinkTos- , getSubLastCommitPos- , getSubLastEventNumber -- * Catch-up Subscription- , Catchup+ , CatchupSubscription , subscribeFrom , subscribeToAllFrom , waitTillCatchup , hasCaughtUp , hasCaughtUpSTM -- * Persistent Subscription- , Persistent+ , PersistentSubscription , PersistentSubscriptionSettings(..) , SystemConsumerStrategy(..) , NakAction(..)@@ -171,9 +173,14 @@ , resolvedEventDataAsJson , resolvedEventOriginalStreamId , resolvedEventOriginalId+ , resolvedEventOriginalEventNumber , recordedEventDataAsJson , positionStart , positionEnd+ -- * Logging+ , LogLevel(..)+ , LogType(..)+ , LoggerFilter(..) -- * Misc , Command , DropReason(..)@@ -183,37 +190,48 @@ , emptyStreamVersion , exactEventVersion , streamExists+ , msDiffTime -- * Re-export- , waitAsync , (<>) , NonEmpty(..) , nonEmpty , TLSSettings+ , NominalDiffTime ) where --------------------------------------------------------------------------------+import Prelude (String) import Data.Int import Data.Maybe+import Data.Time (NominalDiffTime) ---------------------------------------------------------------------------------import ClassyPrelude hiding (Builder, group) import Data.List.NonEmpty(NonEmpty(..), nonEmpty) import Network.Connection (TLSSettings) --------------------------------------------------------------------------------+import Database.EventStore.Internal.Callback import Database.EventStore.Internal.Command-import Database.EventStore.Internal.Connection+import Database.EventStore.Internal.Communication+import Database.EventStore.Internal.Connection (connectionBuilder)+import Database.EventStore.Internal.Control hiding (subscribe) import Database.EventStore.Internal.Discovery-import Database.EventStore.Internal.Subscription-import Database.EventStore.Internal.Manager.Subscription.Driver hiding (unsubscribe)-import Database.EventStore.Internal.Manager.Subscription.Message+import Database.EventStore.Internal.Exec+import Database.EventStore.Internal.Subscription.Api+import Database.EventStore.Internal.Subscription.Catchup+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Subscription.Persistent+import Database.EventStore.Internal.Subscription.Types+import Database.EventStore.Internal.Subscription.Regular+import Database.EventStore.Internal.Logger+import Database.EventStore.Internal.Manager.Operation.Registry import Database.EventStore.Internal.Operation (OperationError(..)) import qualified Database.EventStore.Internal.Operations as Op import Database.EventStore.Internal.Operation.Read.Common import Database.EventStore.Internal.Operation.Write.Common+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types-import Database.EventStore.Internal.Execution.Production -------------------------------------------------------------------------------- -- Connection@@ -230,7 +248,7 @@ -- | Represents a connection to a single EventStore node. data Connection = Connection- { _prod :: Production+ { _exec :: Exec , _settings :: Settings , _type :: ConnectionType }@@ -249,33 +267,37 @@ -- performance out of the connection it is generally recommended to use it in -- this way. connect :: Settings -> ConnectionType -> IO Connection-connect settings tpe = do+connect settings@Settings{..} tpe = do disc <- case tpe of Static host port -> return $ staticEndPointDiscovery host port Cluster setts -> clusterDnsEndPointDiscovery setts Dns dom srv port -> return $ simpleDnsEndPointDiscovery dom srv port- prod <- newExecutionModel settings disc- return $ Connection prod settings tpe + logRef <- newLoggerRef s_loggerType s_loggerFilter s_loggerDetailed+ mainBus <- newBus logRef settings+ builder <- connectionBuilder settings+ exec <- newExec settings mainBus builder disc+ return $ Connection exec settings tpe+ -------------------------------------------------------------------------------- -- | Waits the 'Connection' to be closed. waitTillClosed :: Connection -> IO ()-waitTillClosed Connection{..} = prodWaitTillClosed _prod+waitTillClosed Connection{..} = execWaitTillClosed _exec ----------------------------------------------------------------------------------- | Returns a 'Connection''s 'Settings'.+-- | Returns a 'Connection' 's 'Settings'. connectionSettings :: Connection -> Settings connectionSettings = _settings -------------------------------------------------------------------------------- -- | Asynchronously closes the 'Connection'. shutdown :: Connection -> IO ()-shutdown Connection{..} = shutdownExecutionModel _prod+shutdown Connection{..} = publishWith _exec SystemShutdown -------------------------------------------------------------------------------- -- | Sends a single 'Event' to given stream. sendEvent :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> ExpectedVersion -> Event -> IO (Async WriteResult)@@ -285,28 +307,28 @@ -------------------------------------------------------------------------------- -- | Sends a list of 'Event' to given stream. sendEvents :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> ExpectedVersion -> [Event] -> IO (Async WriteResult) sendEvents Connection{..} evt_stream exp_ver evts = do- (k, as) <- createOpAsync- let op = Op.writeEvents _settings evt_stream exp_ver evts- pushOperation _prod k op- return as+ p <- newPromise+ let op = Op.writeEvents _settings (streamNameRaw evt_stream) exp_ver evts+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | Deletes given stream. deleteStream :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> ExpectedVersion -> Maybe Bool -- ^ Hard delete -> IO (Async Op.DeleteResult) deleteStream Connection{..} evt_stream exp_ver hard_del = do- (k, as) <- createOpAsync- let op = Op.deleteStream _settings evt_stream exp_ver hard_del- pushOperation _prod k op- return as+ p <- newPromise+ let op = Op.deleteStream _settings (streamNameRaw evt_stream) exp_ver hard_del+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | Represents a multi-request transaction with the EventStore.@@ -332,43 +354,43 @@ -------------------------------------------------------------------------------- -- | Starts a transaction on given stream. startTransaction :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> ExpectedVersion -> IO (Async Transaction) startTransaction conn@Connection{..} evt_stream exp_ver = do- (k, as) <- createOpAsync- let op = Op.transactionStart _settings evt_stream exp_ver- pushOperation _prod k op- let _F trans_id =- Transaction- { _tStream = evt_stream- , _tTransId = TransactionId trans_id- , _tExpVer = exp_ver- , _tConn = conn- }- return $ fmap _F as+ p <- newPromise+ let op = Op.transactionStart _settings (streamNameRaw evt_stream) exp_ver+ publishWith _exec (SubmitOperation p op)+ async $ do+ tid <- retrieve p+ return Transaction+ { _tStream = streamNameRaw evt_stream+ , _tTransId = TransactionId tid+ , _tExpVer = exp_ver+ , _tConn = conn+ } -------------------------------------------------------------------------------- -- | Asynchronously writes to a transaction in the EventStore. transactionWrite :: Transaction -> [Event] -> IO (Async ()) transactionWrite Transaction{..} evts = do- (k, as) <- createOpAsync+ p <- newPromise let Connection{..} = _tConn raw_id = _unTransId _tTransId op = Op.transactionWrite _settings _tStream _tExpVer raw_id evts- pushOperation _prod k op- return as+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | Asynchronously commits this transaction. transactionCommit :: Transaction -> IO (Async WriteResult) transactionCommit Transaction{..} = do- (k, as) <- createOpAsync+ p <- newPromise let Connection{..} = _tConn raw_id = _unTransId _tTransId op = Op.transactionCommit _settings _tStream _tExpVer raw_id- pushOperation _prod k op- return as+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | There isn't such of thing in EventStore parlance. Basically, if you want to@@ -379,20 +401,20 @@ -------------------------------------------------------------------------------- -- | Reads a single event from given stream. readEvent :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> Int32 -- ^ Event number -> Bool -- ^ Resolve Link Tos -> IO (Async (ReadResult 'RegularStream Op.ReadEvent)) readEvent Connection{..} stream_id evt_num res_link_tos = do- (k, as) <- createOpAsync- let op = Op.readEvent _settings stream_id evt_num res_link_tos- pushOperation _prod k op- return as+ p <- newPromise+ let op = Op.readEvent _settings (streamNameRaw stream_id) evt_num res_link_tos+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | Reads events from a given stream forward. readStreamEventsForward :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> Int32 -- ^ From event number -> Int32 -- ^ Batch size -> Bool -- ^ Resolve Link Tos@@ -403,7 +425,7 @@ -------------------------------------------------------------------------------- -- | Reads events from a given stream backward. readStreamEventsBackward :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> Int32 -- ^ From event number -> Int32 -- ^ Batch size -> Bool -- ^ Resolve Link Tos@@ -414,16 +436,17 @@ -------------------------------------------------------------------------------- readStreamEventsCommon :: Connection -> ReadDirection- -> Text+ -> StreamName -> Int32 -> Int32 -> Bool -> IO (Async (ReadResult 'RegularStream StreamSlice)) readStreamEventsCommon Connection{..} dir stream_id start cnt res_link_tos = do- (k, as) <- createOpAsync- let op = Op.readStreamEvents _settings dir stream_id start cnt res_link_tos- pushOperation _prod k op- return as+ p <- newPromise+ let name = streamNameRaw stream_id+ op = Op.readStreamEvents _settings dir name start cnt res_link_tos+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | Reads events from the $all stream forward.@@ -453,49 +476,28 @@ -> Bool -> IO (Async AllSlice) readAllEventsCommon Connection{..} dir pos max_c res_link_tos = do- (k, as) <- createOpAsync+ p <- newPromise let op = Op.readAllEvents _settings c_pos p_pos max_c res_link_tos dir- pushOperation _prod k op- return as+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) where Position c_pos p_pos = pos ---------------------------------------------------------------------------------mkSubEnv :: Connection -> SubEnv-mkSubEnv Connection{..} =- SubEnv- { subSettings = _settings- , subPushOp = pushOperation _prod- , subPushConnect = \k cmd ->- case cmd of- PushRegular stream tos ->- pushConnectStream _prod k stream tos- PushPersistent group stream size ->- pushConnectPersist _prod k group stream size- , subPushUnsub = pushUnsubscribe _prod- , subAckCmd = \cmd run uuids ->- case cmd of- AckCmd -> pushAckPersist _prod run uuids- NakCmd act res -> pushNakPersist _prod run act res uuids- , subForceReconnect = \node ->- pushForceReconnect _prod node- }---------------------------------------------------------------------------------- -- | Subcribes to given stream. subscribe :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> Bool -- ^ Resolve Link Tos- -> IO (Subscription Regular)-subscribe conn streamId resLnkTos =- regularSub (mkSubEnv conn) streamId resLnkTos+ -> IO RegularSubscription+subscribe Connection{..} stream resLnkTos =+ newRegularSubscription _exec stream resLnkTos -------------------------------------------------------------------------------- -- | Subcribes to $all stream. subscribeToAll :: Connection -> Bool -- ^ Resolve Link Tos- -> IO (Subscription Regular)-subscribeToAll conn = subscribe conn ""+ -> IO RegularSubscription+subscribeToAll conn = subscribe conn AllStream -------------------------------------------------------------------------------- -- | Subscribes to given stream. If last checkpoint is defined, this will@@ -503,15 +505,15 @@ -- beginning. Once last stream event reached up, a subscription request will -- be sent using 'subscribe'. subscribeFrom :: Connection- -> Text -- ^ Stream name+ -> StreamName -- ^ Stream name -> Bool -- ^ Resolve Link Tos -> Maybe Int32 -- ^ Last checkpoint -> Maybe Int32 -- ^ Batch size- -> IO (Subscription Catchup)+ -> IO CatchupSubscription subscribeFrom conn streamId resLnkTos lastChkPt batch = subscribeFromCommon conn resLnkTos batch tpe where- tpe = Op.RegularCatchup streamId (fromMaybe 0 lastChkPt)+ tpe = Op.RegularCatchup (streamNameRaw streamId) (fromMaybe 0 lastChkPt) -------------------------------------------------------------------------------- -- | Same as 'subscribeFrom' but applied to $all stream.@@ -519,112 +521,95 @@ -> Bool -- ^ Resolve Link Tos -> Maybe Position -- ^ Last checkpoint -> Maybe Int32 -- ^ Batch size- -> IO (Subscription Catchup)+ -> IO CatchupSubscription subscribeToAllFrom conn resLnkTos lastChkPt batch = subscribeFromCommon conn resLnkTos batch tpe where Position cPos pPos = fromMaybe positionStart lastChkPt- tpe = Op.AllCatchup cPos pPos+ tpe = Op.AllCatchup (Position cPos pPos) -------------------------------------------------------------------------------- subscribeFromCommon :: Connection -> Bool -> Maybe Int32 -> Op.CatchupState- -> IO (Subscription Catchup)-subscribeFromCommon conn resLnkTos batch tpe =- catchupSub (mkSubEnv conn) params- where- params = CatchupParams { catchupResLnkTos = resLnkTos- , catchupState = tpe- , catchupBatchSize = batch- }+ -> IO CatchupSubscription+subscribeFromCommon Connection{..} resLnkTos batch tpe =+ newCatchupSubscription _exec resLnkTos batch tpe -------------------------------------------------------------------------------- -- | Asynchronously sets the metadata for a stream. setStreamMetadata :: Connection- -> Text+ -> StreamName -> ExpectedVersion -> StreamMetadata -> IO (Async WriteResult) setStreamMetadata Connection{..} evt_stream exp_ver metadata = do- (k, as) <- createOpAsync- let op = Op.setMetaStream _settings evt_stream exp_ver metadata- pushOperation _prod k op- return as+ p <- newPromise+ let name = streamNameRaw evt_stream+ op = Op.setMetaStream _settings name exp_ver metadata+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | Asynchronously gets the metadata of a stream.-getStreamMetadata :: Connection -> Text -> IO (Async StreamMetadataResult)+getStreamMetadata :: Connection -> StreamName -> IO (Async StreamMetadataResult) getStreamMetadata Connection{..} evt_stream = do- (k, as) <- createOpAsync- let op = Op.readMetaStream _settings evt_stream- pushOperation _prod k op- return as+ p <- newPromise+ let op = Op.readMetaStream _settings (streamNameRaw evt_stream)+ publishWith _exec (SubmitOperation p op)+ async (retrieve p) -------------------------------------------------------------------------------- -- | Asynchronously create a persistent subscription group on a stream. createPersistentSubscription :: Connection -> Text- -> Text+ -> StreamName -> PersistentSubscriptionSettings -> IO (Async (Maybe PersistActionException))-createPersistentSubscription Connection{..} group stream sett = do- mvar <- newEmptyTMVarIO- let _F res = atomically $- case res of- Left e -> putTMVar mvar (Just e)- _ -> putTMVar mvar Nothing- pushCreatePersist _prod _F group stream sett- async $ atomically $ readTMVar mvar+createPersistentSubscription Connection{..} grp stream sett = do+ p <- newPromise+ let op = Op.createPersist grp (streamNameRaw stream) sett+ publishWith _exec (SubmitOperation p op)+ async (persistAsync p) -------------------------------------------------------------------------------- -- | Asynchronously update a persistent subscription group on a stream. updatePersistentSubscription :: Connection -> Text- -> Text+ -> StreamName -> PersistentSubscriptionSettings -> IO (Async (Maybe PersistActionException))-updatePersistentSubscription Connection{..} group stream sett = do- mvar <- newEmptyTMVarIO- let _F res = atomically $- case res of- Left e -> putTMVar mvar (Just e)- _ -> putTMVar mvar Nothing- pushUpdatePersist _prod _F group stream sett- async $ atomically $ readTMVar mvar+updatePersistentSubscription Connection{..} grp stream sett = do+ p <- newPromise+ let op = Op.updatePersist grp (streamNameRaw stream) sett+ publishWith _exec (SubmitOperation p op)+ async (persistAsync p) -------------------------------------------------------------------------------- -- | Asynchronously delete a persistent subscription group on a stream. deletePersistentSubscription :: Connection -> Text- -> Text+ -> StreamName -> IO (Async (Maybe PersistActionException))-deletePersistentSubscription Connection{..} group stream = do- mvar <- newEmptyTMVarIO- let _F res = atomically $- case res of- Left e -> putTMVar mvar (Just e)- _ -> putTMVar mvar Nothing- pushDeletePersist _prod _F group stream- async $ atomically $ readTMVar mvar+deletePersistentSubscription Connection{..} grp stream = do+ p <- newPromise+ let op = Op.deletePersist grp (streamNameRaw stream)+ publishWith _exec (SubmitOperation p op)+ async (persistAsync p) -------------------------------------------------------------------------------- -- | Asynchronously connect to a persistent subscription given a group on a -- stream. connectToPersistentSubscription :: Connection -> Text- -> Text+ -> StreamName -> Int32- -> IO (Subscription Persistent)-connectToPersistentSubscription conn group stream bufSize =- persistentSub (mkSubEnv conn) group stream bufSize+ -> IO PersistentSubscription+connectToPersistentSubscription Connection{..} group stream bufSize =+ newPersistentSubscription _exec group stream bufSize ---------------------------------------------------------------------------------createOpAsync :: IO (Either OperationError a -> IO (), Async a)-createOpAsync = do- mvar <- newEmptyMVar- as <- async $ do- res <- readMVar mvar- either throwIO return res- return (putMVar mvar, as)+persistAsync :: Callback (Maybe PersistActionException)+ -> IO (Maybe PersistActionException)+persistAsync = either throw return <=< tryRetrieve
+ Database/EventStore/Internal/Callback.hs view
@@ -0,0 +1,111 @@+{-# 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/Command.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-} -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore.Internal.Command@@ -10,18 +11,406 @@ -- Portability : non-portable -- ---------------------------------------------------------------------------------module Database.EventStore.Internal.Command (Command(..)) where-----------------------------------------------------------------------------------import ClassyPrelude+module Database.EventStore.Internal.Command where --------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Utils -------------------------------------------------------------------------------- -- | Internal command representation.-newtype Command = Command { cmdWord8 :: Word8 } deriving (Eq, Ord, Num)+data Command =+ Command { cmdWord8 :: !Word8+ , cmdDesc :: !Text+ } -------------------------------------------------------------------------------- instance Show Command where- show (Command w) = prettyWord8 w+ show c = "(" <> prettyWord8 (cmdWord8 c) <> ")" <> unpack (cmdDesc c)++--------------------------------------------------------------------------------+instance Eq Command where+ c1 == c2 = cmdWord8 c1 == cmdWord8 c2++--------------------------------------------------------------------------------+instance Ord Command where+ compare c1 c2 = compare (cmdWord8 c1) (cmdWord8 c2)++--------------------------------------------------------------------------------+heartbeatRequestCmd :: Command+heartbeatRequestCmd =+ Command { cmdWord8 = 0x01+ , cmdDesc = "[heartbeat-request]"+ }++--------------------------------------------------------------------------------+heartbeatResponseCmd :: Command+heartbeatResponseCmd =+ Command { cmdWord8 = 0x02+ , cmdDesc = "[heartbeat-response]"+ }++--------------------------------------------------------------------------------+writeEventsCmd :: Command+writeEventsCmd =+ Command { cmdWord8 = 0x82+ , cmdDesc = "[write-events]"+ }++--------------------------------------------------------------------------------+writeEventsCompletedCmd :: Command+writeEventsCompletedCmd =+ Command { cmdWord8 = 0x83+ , cmdDesc = "[write-events-completed]"+ }++--------------------------------------------------------------------------------+transactionStartCmd :: Command+transactionStartCmd =+ Command { cmdWord8 = 0x84+ , cmdDesc = "[transaction-start]"+ }++--------------------------------------------------------------------------------+transactionStartCompletedCmd :: Command+transactionStartCompletedCmd =+ Command { cmdWord8 = 0x85+ , cmdDesc = "[transaction-start-completed]"+ }++--------------------------------------------------------------------------------+transactionWriteCmd :: Command+transactionWriteCmd =+ Command { cmdWord8 = 0x86+ , cmdDesc = "[transaction-write]"+ }++--------------------------------------------------------------------------------+transactionWriteCompletedCmd :: Command+transactionWriteCompletedCmd =+ Command { cmdWord8 = 0x87+ , cmdDesc = "[transaction-write-completed]"+ }++--------------------------------------------------------------------------------+transactionCommitCmd :: Command+transactionCommitCmd =+ Command { cmdWord8 = 0x88+ , cmdDesc = "[transaction-commit]"+ }++--------------------------------------------------------------------------------+transactionCommitCompletedCmd :: Command+transactionCommitCompletedCmd =+ Command { cmdWord8 = 0x89+ , cmdDesc = "[transaction-commit-completed]"+ }++--------------------------------------------------------------------------------+deleteStreamCmd :: Command+deleteStreamCmd =+ Command { cmdWord8 = 0x8A+ , cmdDesc = "[delete-stream]"+ }++--------------------------------------------------------------------------------+deleteStreamCompletedCmd :: Command+deleteStreamCompletedCmd =+ Command { cmdWord8 = 0x8B+ , cmdDesc = "[delete-stream-completed]"+ }++--------------------------------------------------------------------------------+readEventCmd :: Command+readEventCmd =+ Command { cmdWord8 = 0xB0+ , cmdDesc = "[read-event]"+ }++--------------------------------------------------------------------------------+readEventCompletedCmd :: Command+readEventCompletedCmd =+ Command { cmdWord8 = 0xB1+ , cmdDesc = "[read-event-completed]"+ }++--------------------------------------------------------------------------------+readStreamEventsForwardCmd :: Command+readStreamEventsForwardCmd =+ Command { cmdWord8 = 0xB2+ , cmdDesc = "[read-stream-events-forward]"+ }++--------------------------------------------------------------------------------+readStreamEventsForwardCompletedCmd :: Command+readStreamEventsForwardCompletedCmd =+ Command { cmdWord8 = 0xB3+ , cmdDesc = "[read-stream-events-forward-completed]"+ }++--------------------------------------------------------------------------------+readStreamEventsBackwardCmd :: Command+readStreamEventsBackwardCmd =+ Command { cmdWord8 = 0xB4+ , cmdDesc = "[read-stream-events-backward]"+ }++--------------------------------------------------------------------------------+readStreamEventsBackwardCompletedCmd :: Command+readStreamEventsBackwardCompletedCmd =+ Command { cmdWord8 = 0xB5+ , cmdDesc = "[read-stream-events-backward]"+ }++--------------------------------------------------------------------------------+readAllEventsForwardCmd :: Command+readAllEventsForwardCmd =+ Command { cmdWord8 = 0xB6+ , cmdDesc = "[read-all-events-forward]"+ }++--------------------------------------------------------------------------------+readAllEventsForwardCompletedCmd :: Command+readAllEventsForwardCompletedCmd =+ Command { cmdWord8 = 0xB7+ , cmdDesc = "[read-all-events-forward-completed]"+ }++--------------------------------------------------------------------------------+readAllEventsBackwardCmd :: Command+readAllEventsBackwardCmd =+ Command { cmdWord8 = 0xB8+ , cmdDesc = "[read-all-events-backward]"+ }++--------------------------------------------------------------------------------+readAllEventsBackwardCompletedCmd :: Command+readAllEventsBackwardCompletedCmd =+ Command { cmdWord8 = 0xB9+ , cmdDesc = "[read-all-events-backward-completed]"+ }++--------------------------------------------------------------------------------+subscribeToStreamCmd :: Command+subscribeToStreamCmd =+ Command { cmdWord8 = 0xC0+ , cmdDesc = "[subscribe-to-stream]"+ }++--------------------------------------------------------------------------------+subscriptionConfirmationCmd :: Command+subscriptionConfirmationCmd =+ Command { cmdWord8 = 0xC1+ , cmdDesc = "[subscription-confirmation]"+ }++--------------------------------------------------------------------------------+streamEventAppearedCmd :: Command+streamEventAppearedCmd =+ Command { cmdWord8 = 0xC2+ , cmdDesc = "[stream-event-appeared]"+ }++--------------------------------------------------------------------------------+unsubscribeFromStreamCmd :: Command+unsubscribeFromStreamCmd =+ Command { cmdWord8 = 0xC3+ , cmdDesc = "[unsubscribe-from-stream]"+ }++--------------------------------------------------------------------------------+subscriptionDroppedCmd :: Command+subscriptionDroppedCmd =+ Command { cmdWord8 = 0xC4+ , cmdDesc = "[subscription-dropped]"+ }++--------------------------------------------------------------------------------+connectToPersistentSubscriptionCmd :: Command+connectToPersistentSubscriptionCmd =+ Command { cmdWord8 = 0xC5+ , cmdDesc = "[connect-to-persistent-subscription]"+ }++--------------------------------------------------------------------------------+persistentSubscriptionConfirmationCmd :: Command+persistentSubscriptionConfirmationCmd =+ Command { cmdWord8 = 0xC6+ , cmdDesc = "[persistent-subscription-confirmation]"+ }++--------------------------------------------------------------------------------+persistentSubscriptionStreamEventAppearedCmd :: Command+persistentSubscriptionStreamEventAppearedCmd =+ Command { cmdWord8 = 0xC7+ , cmdDesc = "[persistent-subscription-stream-event-appeared]"+ }++--------------------------------------------------------------------------------+createPersistentSubscriptionCmd :: Command+createPersistentSubscriptionCmd =+ Command { cmdWord8 = 0xC8+ , cmdDesc = "[create-persistent-subscription]"+ }++--------------------------------------------------------------------------------+createPersistentSubscriptionCompletedCmd :: Command+createPersistentSubscriptionCompletedCmd =+ Command { cmdWord8 = 0xC9+ , cmdDesc = "[create-persistent-subscription-completed]"+ }++--------------------------------------------------------------------------------+deletePersistentSubscriptionCmd :: Command+deletePersistentSubscriptionCmd =+ Command { cmdWord8 = 0xCA+ , cmdDesc = "[delete-persistent-subscription]"+ }++--------------------------------------------------------------------------------+deletePersistentSubscriptionCompletedCmd :: Command+deletePersistentSubscriptionCompletedCmd =+ Command { cmdWord8 = 0xCB+ , cmdDesc = "[delete-persistent-subscription-completed]"+ }++--------------------------------------------------------------------------------+persistentSubscriptionAckEventsCmd :: Command+persistentSubscriptionAckEventsCmd =+ Command { cmdWord8 = 0xCC+ , cmdDesc = "[persistent-subscription-ack-events]"+ }++--------------------------------------------------------------------------------+persistentSubscriptionNakEventsCmd :: Command+persistentSubscriptionNakEventsCmd =+ Command { cmdWord8 = 0xCD+ , cmdDesc = "[persistent-subscription-nak-events]"+ }++--------------------------------------------------------------------------------+updatePersistentSubscriptionCmd :: Command+updatePersistentSubscriptionCmd =+ Command { cmdWord8 = 0xCE+ , cmdDesc = "[update-persistent-subscription]"+ }++--------------------------------------------------------------------------------+updatePersistentSubscriptionCompletedCmd :: Command+updatePersistentSubscriptionCompletedCmd =+ Command { cmdWord8 = 0xCF+ , cmdDesc = "[update-persistent-subscription-completed]"+ }++--------------------------------------------------------------------------------+badRequestCmd :: Command+badRequestCmd =+ Command { cmdWord8 = 0xF0+ , cmdDesc = "[bad-request]"+ }++--------------------------------------------------------------------------------+notHandledCmd :: Command+notHandledCmd =+ Command { cmdWord8 = 0xF1+ , cmdDesc = "[not-handled]"+ }++--------------------------------------------------------------------------------+authenticateCmd :: Command+authenticateCmd =+ Command { cmdWord8 = 0xF2+ , cmdDesc = "[authenticate]"+ }++--------------------------------------------------------------------------------+authenticatedCmd :: Command+authenticatedCmd =+ Command { cmdWord8 = 0xF3+ , cmdDesc = "[authenticated]"+ }++--------------------------------------------------------------------------------+notAuthenticatedCmd :: Command+notAuthenticatedCmd =+ Command { cmdWord8 = 0xF4+ , cmdDesc = "[not-authenticated]"+ }++--------------------------------------------------------------------------------+identifyClientCmd :: Command+identifyClientCmd =+ Command { cmdWord8 = 0xF5+ , cmdDesc = "[identify-client]"+ }++--------------------------------------------------------------------------------+clientIdentifiedCmd :: Command+clientIdentifiedCmd =+ Command { cmdWord8 = 0xF6+ , cmdDesc = "[client-identified]"+ }++--------------------------------------------------------------------------------+unknownCmd :: Word8 -> Command+unknownCmd w =+ Command { cmdWord8 = w+ , cmdDesc = "[unknown: "<> pack (prettyWord8 w) <> "]"+ }++--------------------------------------------------------------------------------+getCommand :: Word8 -> Command+getCommand w =+ case lookup w _cmdDict of+ Just cmd -> cmd+ Nothing -> unknownCmd w++--------------------------------------------------------------------------------+_cmdDict :: HashMap Word8 Command+_cmdDict = mapFromList+ [ (0x01, heartbeatRequestCmd)+ , (0x02, heartbeatResponseCmd)+ , (0x82, writeEventsCmd)+ , (0x83, writeEventsCompletedCmd)+ , (0x84, transactionStartCmd)+ , (0x85, transactionStartCompletedCmd)+ , (0x86, transactionWriteCmd)+ , (0x87, transactionWriteCompletedCmd)+ , (0x88, transactionCommitCmd)+ , (0x89, transactionCommitCompletedCmd)+ , (0x8A, deleteStreamCmd)+ , (0x8B, deleteStreamCompletedCmd)+ , (0xB0, readEventCmd)+ , (0xB1, readEventCompletedCmd)+ , (0xB2, readStreamEventsForwardCmd)+ , (0xB3, readStreamEventsForwardCompletedCmd)+ , (0xB4, readStreamEventsBackwardCmd)+ , (0xB5, readStreamEventsBackwardCompletedCmd)+ , (0xB6, readAllEventsForwardCmd)+ , (0xB7, readAllEventsForwardCompletedCmd)+ , (0xB8, readAllEventsBackwardCmd)+ , (0xB9, readAllEventsBackwardCompletedCmd)+ , (0xC0, subscribeToStreamCmd)+ , (0xC1, subscriptionConfirmationCmd)+ , (0xC2, streamEventAppearedCmd)+ , (0xC3, unsubscribeFromStreamCmd)+ , (0xC4, subscriptionDroppedCmd)+ , (0xC5, connectToPersistentSubscriptionCmd)+ , (0xC6, persistentSubscriptionConfirmationCmd)+ , (0xC7, persistentSubscriptionStreamEventAppearedCmd)+ , (0xC8, createPersistentSubscriptionCmd)+ , (0xC9, createPersistentSubscriptionCompletedCmd)+ , (0xCA, deletePersistentSubscriptionCmd)+ , (0xCB, deletePersistentSubscriptionCompletedCmd)+ , (0xCC, persistentSubscriptionAckEventsCmd)+ , (0xCD, persistentSubscriptionNakEventsCmd)+ , (0xCE, updatePersistentSubscriptionCmd)+ , (0xCF, updatePersistentSubscriptionCompletedCmd)+ , (0xF0, badRequestCmd)+ , (0xF1, notHandledCmd)+ , (0xF2, authenticateCmd)+ , (0xF3, authenticatedCmd)+ , (0xF4, notAuthenticatedCmd)+ , (0xF5, identifyClientCmd)+ , (0xF6, clientIdentifiedCmd)+ ]
+ Database/EventStore/Internal/Communication.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Communication+-- 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.Communication where++--------------------------------------------------------------------------------+import Data.Typeable++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Callback+import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+data SystemInit = SystemInit deriving Typeable++--------------------------------------------------------------------------------+data SystemShutdown = SystemShutdown deriving Typeable++--------------------------------------------------------------------------------+data Service+ = ConnectionManager+ | TimerService+ deriving (Show, Eq, Enum, Bounded, Typeable, Generic)++--------------------------------------------------------------------------------+instance Hashable Service++--------------------------------------------------------------------------------+data Initialized = Initialized Service deriving Typeable++--------------------------------------------------------------------------------+data InitFailed = InitFailed Service deriving Typeable++--------------------------------------------------------------------------------+data FatalException+ = forall e. Exception e => FatalException e+ | FatalCondition Text+ deriving Typeable++--------------------------------------------------------------------------------+data SubmitOperation =+ forall a. SubmitOperation (Callback a) (Operation a)+ deriving Typeable++--------------------------------------------------------------------------------+data ServiceTerminated = ServiceTerminated Service deriving Typeable++--------------------------------------------------------------------------------+data NewTimer =+ forall e. Typeable e => NewTimer e Duration Bool+ deriving Typeable++--------------------------------------------------------------------------------+newtype SendPackage = SendPackage Package deriving Typeable
Database/EventStore/Internal/Connection.hs view
@@ -1,11 +1,9 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE LambdaCase #-} -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore.Internal.Connection--- Copyright : (C) 2015 Yorick Laupa+-- Copyright : (C) 2017 Yorick Laupa -- License : (see the file LICENSE) -- -- Maintainer : Yorick Laupa <yo.eight@gmail.com>@@ -14,284 +12,247 @@ -- -------------------------------------------------------------------------------- module Database.EventStore.Internal.Connection- ( InternalConnection- , ConnectionException(..)- , connUUID- , connClose- , connSend- , connRecv- , connIsClosed- , newConnection- , connForceReconnect- ) where+ ( ConnectionBuilder(..)+ , Connection(..)+ , RecvOutcome(..)+ , PackageArrived(..)+ , ConnectionError(..)+ , ConnectionEstablished(..)+ , ConnectionClosed(..)+ , ConnectionRef(..)+ , getConnection+ , connectionBuilder+ , connectionError+ ) where --------------------------------------------------------------------------------+import Prelude (String) import Text.Printf ---------------------------------------------------------------------------------import ClassyPrelude-import Data.Serialize-import Data.UUID-import Data.UUID.V4-import Network.Connection+import Control.Monad.Reader+import Data.Serialize+import Data.UUID+import qualified Network.Connection as Network -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Control import Database.EventStore.Internal.EndPoint-import Database.EventStore.Internal.Discovery+import Database.EventStore.Internal.Logger+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types-import Database.EventStore.Logging ----------------------------------------------------------------------------------- | Type of connection issue that can arise during the communication with the--- server.-data ConnectionException- = MaxAttemptConnectionReached- -- ^ The max reconnection attempt threshold has been reached.- | ClosedConnection- -- ^ Use of a close 'Connection'.- | WrongPackageFraming- -- ^ TCP package sent by the server had a wrong framing.- | PackageParsingError String- -- ^ Server sent a malformed TCP package.- deriving (Show, Typeable)+newtype ConnectionBuilder =+ ConnectionBuilder { connect :: EndPoint -> EventStore Connection } ---------------------------------------------------------------------------------instance Exception ConnectionException+data RecvOutcome+ = ResetByPeer+ | Recv Package+ | WrongFraming+ | ParsingError ---------------------------------------------------------------------------------data In a where- Id :: In UUID- Close :: In ()- Send :: Package -> In ()- Recv :: In Package- ForceReconnect :: NodeEndPoints -> In ()+type ConnectionId = UUID ----------------------------------------------------------------------------------- | Represents connection logic action to carry out.-data Status a where- Noop :: Status ()- WithConnection :: UUID -> Connection -> In a -> Status a- CreateConnection :: In a -> Status a- Errored :: ConnectionException -> Status a+newtype ConnectionRef =+ ConnectionRef { maybeConnection :: EventStore (Maybe Connection) } ----------------------------------------------------------------------------------- | Internal representation of a connection with the server.-data InternalConnection =- InternalConnection- { _var :: TMVar State- , _last :: IORef (Maybe EndPoint)- , _disc :: Discovery- , _setts :: Settings- , _ctx :: ConnectionContext- }+getConnection :: ConnectionRef -> EventStore Connection+getConnection ref =+ maybeConnection ref >>= \case+ Just conn -> return conn+ Nothing -> do+ $(logError) "Expected a connection but got none."+ throwString "No current connection (impossible situation)" ---------------------------------------------------------------------------------data State- = Offline- | Online !UUID !Connection- | Closed+data Connection =+ Connection { connectionId :: ConnectionId+ , connectionEndPoint :: EndPoint+ , enqueuePackage :: Package -> EventStore ()+ , dispose :: EventStore ()+ } ----------------------------------------------------------------------------------- | Creates a new 'InternalConnection'.-newConnection :: Settings -> Discovery -> IO InternalConnection-newConnection setts disc = do- ctx <- initConnectionContext- var <- newTMVarIO Offline- ref <- newIORef Nothing- return $ InternalConnection var ref disc setts ctx+instance Show Connection where+ show Connection{..} = "Connection [" <> show connectionId <> "] on "+ <> show connectionEndPoint ----------------------------------------------------------------------------------- | Gets current 'InternalConnection' 'UUID'.-connUUID :: InternalConnection -> IO UUID-connUUID conn = execute conn Id+instance Eq Connection where+ a == b = connectionId a == connectionId b ----------------------------------------------------------------------------------- | Closes the 'InternalConnection'. It will not retry to reconnect after that--- call. it means a new 'InternalConnection' has to be created.--- 'ClosedConnection' exception will be raised if the same--- 'InternalConnection' object is used after a 'connClose' call.-connClose :: InternalConnection -> IO ()-connClose conn = execute conn Close+newtype ConnectionState =+ ConnectionState { _sendQueue :: TBMQueue Package } ----------------------------------------------------------------------------------- | Sends 'Package' to the server.-connSend :: InternalConnection -> Package -> IO ()-connSend conn pkg = execute conn (Send pkg)+data PackageArrived = PackageArrived Connection Package deriving Typeable ----------------------------------------------------------------------------------- | Asks the requested amount of bytes from the 'handle'.-connRecv :: InternalConnection -> IO Package-connRecv conn = execute conn Recv+data ConnectionError =+ ConnectionError Connection SomeException deriving Typeable ----------------------------------------------------------------------------------- | Returns True if the connection is in closed state.-connIsClosed :: InternalConnection -> STM Bool-connIsClosed InternalConnection{..} = do- r <- readTMVar _var- case r of- Closed -> return True- _ -> return False+connectionError :: Exception e => Connection -> e -> ConnectionError+connectionError c = ConnectionError c . toException ----------------------------------------------------------------------------------- | Forces reconnection on given node.-connForceReconnect :: InternalConnection -> NodeEndPoints -> IO ()-connForceReconnect conn = execute conn . ForceReconnect+data ConnectionClosed = ConnectionClosed Connection SomeException+ deriving Typeable ----------------------------------------------------------------------------------- Connection Logic+data ConnectionEstablished =+ ConnectionEstablished Connection deriving Typeable+ ---------------------------------------------------------------------------------onlineLogic :: forall a. TMVar State- -> UUID- -> Connection- -> In a- -> STM (Status a)-onlineLogic var uuid conn input =- let status = WithConnection uuid conn input- state =- case input of- Close -> Closed- _ -> Online uuid conn in- status <$ putTMVar var state+newtype ConnectionResetByPeer = ConnectionResetByPeer SomeException+ deriving Typeable ---------------------------------------------------------------------------------offlineLogic :: forall a. TMVar State -> In a -> STM (Status a)-offlineLogic var Close = Noop <$ putTMVar var Closed-offlineLogic _ other = return $ CreateConnection other+instance Show ConnectionResetByPeer where+ show (ConnectionResetByPeer reason) =+ "Connection reset by peer: " <> show reason ---------------------------------------------------------------------------------closedLogic :: forall a. TMVar State -> In a -> STM (Status a)-closedLogic var input = do- putTMVar var Closed- case input of- Close -> return Noop- _ -> return $ Errored ClosedConnection+instance Exception ConnectionResetByPeer ---------------------------------------------------------------------------------connectionLogic :: forall a. TMVar State -> In a -> STM (Status a)-connectionLogic var input = do- state <- takeTMVar var- case state of- Online uuid conn -> onlineLogic var uuid conn input- Offline -> offlineLogic var input- Closed -> closedLogic var input+data ProtocolError+ = WrongFramingError !String+ | PackageParsingError !String+ deriving Typeable --------------------------------------------------------------------------------+instance Show ProtocolError where+ show (WrongFramingError reason) = "Package framing error: " <> reason+ show (PackageParsingError reason) = "Package parsing error: " <> reason+ ---------------------------------------------------------------------------------handleInput :: forall a. UUID -> Connection -> In a -> IO a-handleInput _ conn (Send pkg) = send conn pkg-handleInput _ conn Recv = recv conn-handleInput uuid _ Id = return uuid-handleInput _ conn Close = liftIO $ connectionClose conn-handleInput _ _ ForceReconnect{} = return ()+instance Exception ProtocolError ----------------------------------------------------------------------------------- | Main connection logic. It will automatically reconnect to the server when--- a exception occured while the 'Handle' is accessed.-execute :: forall a. InternalConnection -> In a -> IO a-execute iconn input = do- res <- atomically $ connectionLogic (_var iconn) input+connectionBuilder :: Settings -> IO ConnectionBuilder+connectionBuilder setts = do+ ctx <- Network.initConnectionContext+ return $ ConnectionBuilder $ \ept -> do+ cid <- freshUUID+ state <- createState - case res of- Noop -> return ()- Errored e -> throwIO e- WithConnection uuid conn op -> handleInput uuid conn op- CreateConnection op -> do- (uuid, conn) <-- case op of- ForceReconnect node -> openConnection iconn (Just node)- _ -> openConnection iconn Nothing- atomically $ putTMVar (_var iconn) (Online uuid conn)- handleInput uuid conn op+ mfix $ \self -> do+ tcpConnAsync <- async $+ tryAny (createConnection setts ctx ept) >>= \case+ Left e -> do+ publish (ConnectionClosed self e)+ throw e+ Right conn -> do+ publish (ConnectionEstablished self)+ return conn + sendAsync <- async (sending state self tcpConnAsync)+ recvAsync <- async (receiving state self tcpConnAsync)+ return Connection { connectionId = cid+ , connectionEndPoint = ept+ , enqueuePackage = enqueue state+ , dispose = do+ closeState state+ disposeConnection tcpConnAsync+ cancel sendAsync+ cancel recvAsync+ }+ ---------------------------------------------------------------------------------reachedMaxAttempt :: Retry -> Int -> Bool-reachedMaxAttempt KeepRetrying _ = False-reachedMaxAttempt (AtMost n) cur = n <= cur+createState :: EventStore ConnectionState+createState = ConnectionState <$> liftIO (newTBMQueueIO 500) ---------------------------------------------------------------------------------isSsl :: Settings -> Bool-isSsl = isJust . s_ssl+closeState :: ConnectionState -> EventStore ()+closeState ConnectionState{..} = atomically $ closeTBMQueue _sendQueue ---------------------------------------------------------------------------------openConnection :: InternalConnection- -> Maybe NodeEndPoints- -> IO (UUID, Connection)-openConnection InternalConnection{..} nodeM = attempt 1+createConnection :: Settings+ -> Network.ConnectionContext+ -> EndPoint+ -> EventStore Network.Connection+createConnection setts ctx ept = liftIO $ Network.connectTo ctx params where- delay = s_reconnect_delay_secs _setts * secs-- handleFailure trialCount = do- threadDelay delay- when (reachedMaxAttempt (s_retry _setts) trialCount) $ do- atomically $ putTMVar _var Closed- throwIO MaxAttemptConnectionReached- attempt (trialCount + 1)-- attempt trialCount = do- _settingsLog _setts (Info $ Connecting trialCount)- case nodeM of- Just node -> do- let ept = if isSsl _setts- then let Just pt = secureEndPoint node- in pt- else tcpEndPoint node-- host = endPointIp ept- port = endPointPort ept- res <- tryAny $ connect _setts _ctx host port- case res of- Left _ -> handleFailure trialCount- Right st -> st <$ writeIORef _last (Just ept)- Nothing -> do- old <- readIORef _last- ept_opt <- runDiscovery _disc old- case ept_opt of- Nothing -> handleFailure trialCount- Just ept -> do- let host = endPointIp ept- port = endPointPort ept- res <- tryAny $ connect _setts _ctx host port- case res of- Left _ -> handleFailure trialCount- Right st -> st <$ writeIORef _last (Just ept)+ host = endPointIp ept+ port = fromIntegral $ endPointPort ept+ params = Network.ConnectionParams host port (s_ssl setts) Nothing ---------------------------------------------------------------------------------secs :: Int-secs = 1000000+disposeConnection :: Async Network.Connection -> EventStore ()+disposeConnection as = traverse_ tryDisposing =<< poll as+ where+ tryDisposing = traverse_ disposing+ disposing = liftIO . Network.connectionClose ---------------------------------------------------------------------------------connect :: Settings- -> ConnectionContext- -> String- -> Int- -> IO (UUID, Connection)-connect sett ctx host port = do- let params = ConnectionParams host (fromIntegral port) (s_ssl sett) Nothing- conn <- connectTo ctx params- uuid <- nextRandom- _settingsLog sett (Info $ Connected uuid)- return (uuid, conn)+receivePackage :: Connection -> Network.Connection -> EventStore Package+receivePackage self conn =+ tryAny (liftIO $ Network.connectionGetExact conn 4) >>= \case+ Left e -> do+ publish (ConnectionClosed self e)+ throw e+ Right frame ->+ case runGet getLengthPrefix frame of+ Left reason -> do+ let cause = WrongFramingError reason+ publish (connectionError self cause)+ throw cause+ Right prefix -> do+ tryAny (liftIO $ Network.connectionGetExact conn prefix) >>= \case+ Left e -> do+ publish (ConnectionClosed self e)+ throw e+ Right payload ->+ case runGet getPackage payload of+ Left reason -> do+ let cause = PackageParsingError reason+ publish (connectionError self cause)+ throw cause+ Right pkg -> return pkg ----------------------------------------------------------------------------------- Binary operations+receiving :: ConnectionState+ -> Connection+ -> Async Network.Connection+ -> EventStore ()+receiving ConnectionState{..} self tcpConnAsync =+ forever . go =<< wait tcpConnAsync+ where+ go conn =+ publish . PackageArrived self =<< receivePackage self conn+ ---------------------------------------------------------------------------------recv :: Connection -> IO Package-recv con = do- header_bs <- connectionGetExact con 4- case runGet getLengthPrefix header_bs of- Left _ -> throwIO WrongPackageFraming- Right length_prefix -> do- bs <- connectionGetExact con length_prefix- case runGet getPackage bs of- Left e -> throwIO $ PackageParsingError e- Right pkg -> return pkg+enqueue :: ConnectionState -> Package -> EventStore ()+enqueue ConnectionState{..} pkg@Package{..} = do+ $(logDebug) [i|Package enqueued: #{pkg}|]+ atomically $ writeTBMQueue _sendQueue pkg ---------------------------------------------------------------------------------send :: Connection -> Package -> IO ()-send con pkg = connectionPut con bs+sending :: ConnectionState+ -> Connection+ -> Async Network.Connection+ -> EventStore ()+sending ConnectionState{..} self tcpConnAsync = go =<< wait tcpConnAsync where- bs = runPut $ putPackage pkg+ go conn =+ let loop = traverse_ send =<< atomically (readTBMQueue _sendQueue)+ send pkg =+ tryAny (liftIO $ Network.connectionPut conn bytes) >>= \case+ Left e -> publish (ConnectionClosed self e)+ Right _ -> do+ monitorAddDataTransmitted (length bytes)+ loop+ where+ bytes = runPut $ putPackage pkg in+ loop -------------------------------------------------------------------------------- -- Serialization@@ -304,22 +265,22 @@ putWord8 flag_word8 putLazyByteString corr_bytes for_ cred_m $ \(Credentials login passw) -> do- putWord8 $ fromIntegral $ olength login+ putWord8 $ fromIntegral $ length login putByteString login- putWord8 $ fromIntegral $ olength passw+ putWord8 $ fromIntegral $ length passw putByteString passw putByteString pack_data where pack_data = packageData pkg cred_len = maybe 0 credSize cred_m- length_prefix = fromIntegral (olength pack_data + mandatorySize + cred_len)+ length_prefix = fromIntegral (length pack_data + mandatorySize + cred_len) cred_m = packageCred pkg flag_word8 = maybe 0x00 (const 0x01) cred_m corr_bytes = toByteString $ packageCorrelation pkg -------------------------------------------------------------------------------- credSize :: Credentials -> Int-credSize (Credentials login passw) = olength login + olength passw + 2+credSize (Credentials login passw) = length login + length passw + 2 -------------------------------------------------------------------------------- -- | The minimun size a 'Package' should have. It's basically a command byte,@@ -344,7 +305,7 @@ dta <- getBytes rest let pkg = Package- { packageCmd = Command cmd+ { packageCmd = getCommand cmd , packageCorrelation = col , packageData = dta , packageCred = cred
+ Database/EventStore/Internal/ConnectionManager.hs view
@@ -0,0 +1,499 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.ConnectionManager+-- 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.ConnectionManager+ ( connectionManager ) where++--------------------------------------------------------------------------------+import Data.Typeable++--------------------------------------------------------------------------------+import Control.Monad.Reader+import Data.Time++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Callback+import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Communication+import Database.EventStore.Internal.Connection+import Database.EventStore.Internal.Control+import Database.EventStore.Internal.Discovery+import Database.EventStore.Internal.EndPoint+import Database.EventStore.Internal.Logger+import Database.EventStore.Internal.Operation+import qualified Database.EventStore.Internal.OperationManager as Operation+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Stopwatch+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+data Stage+ = Init+ | Connecting Attempts ConnectingState+ | Connected Connection+ | Closed++--------------------------------------------------------------------------------+instance Show Stage where+ show Init = "Init"+ show (Connecting a s) = "Connecting: " <> show (a, s)+ show (Connected c) = "Connected on" <> show c+ show Closed = "Closed"++--------------------------------------------------------------------------------+data ConnectingState+ = Reconnecting+ | EndpointDiscovery+ | ConnectionEstablishing Connection+ deriving Show++--------------------------------------------------------------------------------+data Attempts =+ Attempts { attemptCount :: !Int+ , attemptLastStart :: !NominalDiffTime+ } deriving Show++--------------------------------------------------------------------------------+freshAttempt :: Stopwatch -> EventStore Attempts+freshAttempt = fmap (Attempts 1) . stopwatchElapsed++--------------------------------------------------------------------------------+data ConnectionMaxAttemptReached = ConnectionMaxAttemptReached+ deriving Typeable++--------------------------------------------------------------------------------+instance Show ConnectionMaxAttemptReached where+ show _ = "Reconnection limit reached."++--------------------------------------------------------------------------------+instance Exception ConnectionMaxAttemptReached++--------------------------------------------------------------------------------+data EstablishConnection = EstablishConnection EndPoint deriving Typeable++--------------------------------------------------------------------------------+newtype CloseConnection = CloseConnection SomeException+ deriving (Show, Typeable)++--------------------------------------------------------------------------------+instance Exception CloseConnection++--------------------------------------------------------------------------------+data Tick = Tick deriving Typeable++--------------------------------------------------------------------------------+timerPeriod :: Duration+timerPeriod = msDuration 200++--------------------------------------------------------------------------------+data HeartbeatStage = Interval | Timeout++--------------------------------------------------------------------------------+data HeartbeatTracker =+ HeartbeatTracker { _pkgNum :: !Integer+ , _heartbeatStage :: !HeartbeatStage+ , _startedSince :: !NominalDiffTime+ }++--------------------------------------------------------------------------------+newHeartbeatTracker :: MonadBaseControl IO m+ => Stopwatch+ -> m (IORef HeartbeatTracker)+newHeartbeatTracker =+ newIORef . HeartbeatTracker 0 Interval <=< stopwatchElapsed++--------------------------------------------------------------------------------+initHeartbeatTracker :: Internal -> EventStore ()+initHeartbeatTracker Internal{..} = do+ elapsed <- stopwatchElapsed _stopwatch+ pkgNum <- readIORef _lastPkgNum+ let tracker = HeartbeatTracker pkgNum Interval elapsed+ atomicWriteIORef _tracker tracker++--------------------------------------------------------------------------------+data Internal =+ Internal { _disc :: Discovery+ , _builder :: ConnectionBuilder+ , _stage :: IORef Stage+ , _last :: IORef (Maybe EndPoint)+ , _sending :: TVar Bool+ , _opMgr :: Operation.Manager+ , _stopwatch :: Stopwatch+ , _lastCheck :: IORef NominalDiffTime+ , _lastConnected :: IORef Bool+ , _tracker :: IORef HeartbeatTracker+ , _lastPkgNum :: IORef Integer+ }++--------------------------------------------------------------------------------+incrPackageNumber :: Internal -> EventStore ()+incrPackageNumber Internal{..} = do+ atomicModifyIORef' _lastPkgNum $ \n -> (n + 1, ())+ monitorIncrPkgCount++--------------------------------------------------------------------------------+connectionManager :: ConnectionBuilder+ -> Discovery+ -> Hub+ -> IO ()+connectionManager builder disc mainBus = do+ stageRef <- newIORef Init+ let mkInternal = Internal disc builder stageRef+ connRef = ConnectionRef $ lookingUpConnection stageRef++ stopwatch <- newStopwatch+ timeoutCheck <- stopwatchElapsed stopwatch+ internal <- mkInternal <$> newIORef Nothing+ <*> newTVarIO False+ <*> Operation.new connRef+ <*> return stopwatch+ <*> newIORef timeoutCheck+ <*> newIORef False+ <*> newHeartbeatTracker stopwatch+ <*> newIORef 0++ subscribe mainBus (onInit internal)+ subscribe mainBus (onEstablish internal)+ subscribe mainBus (onEstablished internal)+ subscribe mainBus (onArrived internal)+ subscribe mainBus (onSubmitOperation internal)+ subscribe mainBus (onConnectionError internal)+ subscribe mainBus (onConnectionClosed internal)+ subscribe mainBus (onCloseConnection internal)+ subscribe mainBus (onShutdown internal)+ subscribe mainBus (onTick internal)+ subscribe mainBus (onSendPackage internal)++--------------------------------------------------------------------------------+onInit :: Internal -> SystemInit -> EventStore ()+onInit self@Internal{..} _ = do+ publish (NewTimer Tick timerPeriod False)+ startConnect self++--------------------------------------------------------------------------------+startConnect :: Internal -> EventStore ()+startConnect self@Internal{..} =+ readIORef _stage >>= \case+ Init -> do+ atts <- freshAttempt _stopwatch+ atomicWriteIORef _stage (Connecting atts Reconnecting)+ discover self+ _ -> return ()++--------------------------------------------------------------------------------+discover :: Internal -> EventStore ()+discover Internal{..} =+ readIORef _stage >>= \case+ Connecting att p ->+ case p of+ Reconnecting{} -> do+ atomicWriteIORef _stage (Connecting att EndpointDiscovery)+ old <- readIORef _last+ _ <- fork $+ tryAny (liftIO $ runDiscovery _disc old) >>= \case+ Left e -> do+ $logError+ [i| Failed to resolve TCP endpoint to which to connect #{e}.|]+ publish (CloseConnection e)+ Right opt ->+ case opt of+ Nothing -> do+ $logWarn+ "Failed to resolve TCP endpoint to which to connect."+ Just ept -> publish (EstablishConnection ept)+ return ()+ _ -> return ()+ _ -> return ()++--------------------------------------------------------------------------------+establish :: Internal -> EndPoint -> EventStore ()+establish Internal{..} ept = do+ $(logDebug) [i|Establish tcp connection on [#{ept}]|]+ readIORef _stage >>= \case+ Connecting att s ->+ case s of+ EndpointDiscovery -> do+ conn <- connect _builder ept+ connected <- atomicModifyIORef' _lastConnected $ \c -> (True, c)+ unless connected $+ publish (Initialized ConnectionManager)+ atomicWriteIORef _stage (Connecting att (ConnectionEstablishing conn))+ _ -> return ()+ _ -> return ()++--------------------------------------------------------------------------------+established :: Internal -> Connection -> EventStore ()+established self@Internal{..} conn =+ readIORef _stage >>= \case+ Connecting _ (ConnectionEstablishing known) -> do+ when (conn == known) $ do+ $logDebug [i|TCP connection established: #{conn}.|]+ atomicWriteIORef _stage (Connected conn)+ initHeartbeatTracker self+ _ -> return ()++--------------------------------------------------------------------------------+onEstablished :: Internal -> ConnectionEstablished -> EventStore ()+onEstablished self (ConnectionEstablished conn) = established self conn++--------------------------------------------------------------------------------+closeConnection :: Exception e => Internal -> e -> EventStore ()+closeConnection self@Internal{..} cause = do+ $logDebug [i|CloseConnection: #{cause}.|]+ mConn <- lookupConnectionAndSwitchToClosed self+ Operation.cleanup _opMgr+ traverse_ (closeTcpConnection self cause) mConn+ $logInfo [i|CloseConnection: connection cleanup done for [#{cause}].|]+ publish (FatalException cause)++--------------------------------------------------------------------------------+lookupConnectionAndSwitchToClosed :: Internal -> EventStore (Maybe Connection)+lookupConnectionAndSwitchToClosed self@Internal{..} = do+ outcome <- lookupConnection self+ atomicWriteIORef _stage Closed+ return outcome++--------------------------------------------------------------------------------+closeTcpConnection :: Exception e => Internal -> e -> Connection -> EventStore ()+closeTcpConnection Internal{..} cause conn = do+ let cid = connectionId conn+ $logDebug [i|CloseTcpConnection: connection [#{cid}]. Cause: #{cause}.|]+ dispose conn+ $logDebug [i|CloseTcpConnection: connection [#{cid}] disposed.|]++ readIORef _stage >>= \case+ Closed -> return ()+ stage -> do+ att <-+ case stage of+ Connecting old _ -> return old+ _ -> freshAttempt _stopwatch+ atomicWriteIORef _stage (Connecting att Reconnecting)++--------------------------------------------------------------------------------+data ForceReconnect = ForceReconnect EndPoint deriving (Typeable, Show)++--------------------------------------------------------------------------------+instance Exception ForceReconnect++--------------------------------------------------------------------------------+forceReconnect :: Internal -> NodeEndPoints -> EventStore ()+forceReconnect self@Internal{..} node = do+ setts <- getSettings+ let ept = if isJust $ s_ssl setts+ then let Just pt = secureEndPoint node in pt+ else tcpEndPoint node++ Connected conn <- readIORef _stage+ when (connectionEndPoint conn /= ept) $ do+ monitorIncrForceReconnect+ closeTcpConnection self (ForceReconnect ept) conn+ att <- freshAttempt _stopwatch+ atomicWriteIORef _stage (Connecting att EndpointDiscovery)+ $logInfo [i|#{conn}: going to reconnect to #{ept}.|]+ establish self ept++--------------------------------------------------------------------------------+onEstablish :: Internal -> EstablishConnection -> EventStore ()+onEstablish self (EstablishConnection ept) = establish self ept++--------------------------------------------------------------------------------+onTick :: Internal -> Tick -> EventStore ()+onTick self@Internal{..} _ = do+ setts <- getSettings+ readIORef _stage >>= \case+ Connecting Attempts{..} s+ | onGoingConnection s -> do+ elapsed <- stopwatchElapsed _stopwatch+ if elapsed - attemptLastStart >= s_reconnect_delay setts+ then do+ let retries = attemptCount + 1+ att = Attempts retries elapsed+ atomicWriteIORef _stage (Connecting att Reconnecting)+ case s_retry setts of+ AtMost n+ | attemptCount <= n -> retryConnection attemptCount+ | otherwise -> maxAttemptReached+ KeepRetrying -> retryConnection attemptCount+ else return ()+ | otherwise -> manageHeartbeats self+ Connected _ -> do+ elapsed <- stopwatchElapsed _stopwatch+ timeoutCheckStart <- readIORef _lastCheck++ when (elapsed - timeoutCheckStart >= s_operationTimeout setts) $ do+ Operation.check _opMgr+ atomicWriteIORef _lastCheck elapsed++ manageHeartbeats self+ _ -> return ()+ where+ onGoingConnection Reconnecting = True+ onGoingConnection ConnectionEstablishing{} = True+ onGoingConnection _ = False++ maxAttemptReached = do+ closeConnection self ConnectionMaxAttemptReached+ publish (FatalException ConnectionMaxAttemptReached)++ retryConnection cnt = do+ $logDebug [i|Checking reconnection... (attempt #{cnt}).|]+ discover self++--------------------------------------------------------------------------------+data ServerHeartbeatTimeout = ServerHeartbeatTimeout deriving Typeable++--------------------------------------------------------------------------------+instance Show ServerHeartbeatTimeout where+ show _ = "Server connection has heartbeat timeout"++--------------------------------------------------------------------------------+instance Exception ServerHeartbeatTimeout++--------------------------------------------------------------------------------+manageHeartbeats :: Internal -> EventStore ()+manageHeartbeats self@Internal{..} = traverse_ go =<< lookupConnection self+ where+ go conn = do+ elapsed <- stopwatchElapsed _stopwatch+ pkgNum <- readIORef _lastPkgNum+ tracker <- readIORef _tracker+ setts <- getSettings++ let interval = s_heartbeatInterval setts+ timeout = s_heartbeatInterval setts+ initTracker = tracker+ { _heartbeatStage = Interval+ , _startedSince = elapsed+ , _pkgNum = pkgNum+ }++ if _pkgNum tracker /= pkgNum+ then atomicWriteIORef _tracker initTracker+ else+ case _heartbeatStage tracker of+ Interval+ | elapsed - _startedSince tracker >= interval -> do+ uuid <- freshUUID+ let pkg = heartbeatRequestPackage uuid+ newTracker = tracker+ { _heartbeatStage = Timeout+ , _startedSince = elapsed+ , _pkgNum = pkgNum+ }+ enqueuePackage conn pkg+ atomicWriteIORef _tracker newTracker+ | otherwise -> return ()+ Timeout+ | elapsed - _startedSince tracker >= timeout -> do+ monitorIncrHeartbeatTimeouts+ $logInfo [i|Closing #{conn} due to HEARTBEAT TIMEOUT at pkgNum #{pkgNum}|]+ closeTcpConnection self ServerHeartbeatTimeout conn+ | otherwise -> return ()++--------------------------------------------------------------------------------+onArrived :: Internal -> PackageArrived -> EventStore ()+onArrived self@Internal{..} (PackageArrived conn pkg@Package{..}) = do+ withConnection $ \knownConn -> do+ if knownConn == conn+ then do+ $logDebug [i|Package received: #{pkg}.|]+ incrPackageNumber self+ handlePackage+ else do+ $logDebug [i|Package IGNORED: #{pkg}.|]+ where+ withConnection :: (Connection -> EventStore ()) -> EventStore ()+ withConnection k = do+ readIORef _stage >>= \case+ Connecting _ (ConnectionEstablishing c) -> k c+ Connected c -> k c+ _ -> $logDebug [i|Package IGNORED: #{pkg}|]++ heartbeatResponse = heartbeatResponsePackage packageCorrelation++ handlePackage+ | packageCmd == heartbeatResponseCmd = return ()+ | 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++--------------------------------------------------------------------------------+isSameConnection :: Internal -> Connection -> EventStore Bool+isSameConnection Internal{..} conn = go <$> readIORef _stage+ where+ go (Connected known) = known == conn+ go (Connecting _ (ConnectionEstablishing known)) = known == conn+ go _ = False++--------------------------------------------------------------------------------+onConnectionError :: Internal -> ConnectionError -> EventStore ()+onConnectionError self@Internal{..} (ConnectionError conn e) =+ whenM (isSameConnection self conn) $ do+ $logError [i|TCP #{conn} error. Cause: #{e}.|]+ closeConnection self e++--------------------------------------------------------------------------------+onConnectionClosed :: Internal -> ConnectionClosed -> EventStore ()+onConnectionClosed self@Internal{..} (ConnectionClosed conn cause) =+ whenM (isSameConnection self conn) $ do+ closeTcpConnection self cause conn+ monitorIncrConnectionDrop++--------------------------------------------------------------------------------+onShutdown :: Internal -> SystemShutdown -> EventStore ()+onShutdown self@Internal{..} _ = do+ $logDebug "Shutting down..."+ mConn <- lookupConnectionAndSwitchToClosed self+ Operation.cleanup _opMgr+ traverse_ dispose mConn+ $logDebug "Shutdown properly."+ publish (ServiceTerminated ConnectionManager)++--------------------------------------------------------------------------------+onSubmitOperation :: Internal -> SubmitOperation -> EventStore ()+onSubmitOperation Internal{..} (SubmitOperation callback op) =+ readIORef _stage >>= \case+ Closed -> reject callback Aborted+ _ -> Operation.submit _opMgr op callback++--------------------------------------------------------------------------------+onCloseConnection :: Internal -> CloseConnection -> EventStore ()+onCloseConnection self e = closeConnection self e++--------------------------------------------------------------------------------+lookupConnection :: Internal -> EventStore (Maybe Connection)+lookupConnection Internal{..} = lookingUpConnection _stage++--------------------------------------------------------------------------------+lookingUpConnection :: IORef Stage -> EventStore (Maybe Connection)+lookingUpConnection ref = go <$> readIORef ref+ where+ go (Connected conn) = Just conn+ go (Connecting _ (ConnectionEstablishing conn)) = Just conn+ go _ = Nothing++--------------------------------------------------------------------------------+onSendPackage :: Internal -> SendPackage -> EventStore ()+onSendPackage self (SendPackage pkg) =+ traverse_ sending =<< lookupConnection self+ where+ sending conn = enqueuePackage conn pkg
+ Database/EventStore/Internal/Control.hs view
@@ -0,0 +1,419 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Control+-- Copyright : (C) 2014 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+--------------------------------------------------------------------------------+module Database.EventStore.Internal.Control+ (+ -- * Control+ EventStore+ , runEventStore+ , getSettings+ , freshUUID+ -- * Messaging+ , Pub(..)+ , Sub(..)+ , Hub+ , Subscribe+ , Publish+ , asPub+ , asSub+ , asHub+ , Bus+ , newBus+ , busStop+ , Message+ , toMsg+ , fromMsg+ , busProcessedEverything+ , publish+ , publishWith+ , subscribe+ , stopBus+ , publisher+ -- * Monitoring+ , monitorIncrPkgCount+ , monitorIncrConnectionDrop+ , monitorAddDataTransmitted+ , monitorIncrForceReconnect+ , monitorIncrHeartbeatTimeouts+ -- * Re-export+ , module Database.EventStore.Internal.Settings+ ) where++--------------------------------------------------------------------------------+import Data.Typeable+import Data.Typeable.Internal++--------------------------------------------------------------------------------+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+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings++--------------------------------------------------------------------------------+data Env =+ Env { __logRef :: LoggerRef+ , __settings :: Settings+ , __bus :: Bus+ , __monitor :: Maybe Monitoring+ }++--------------------------------------------------------------------------------+newtype EventStore a =+ EventStore { unEventStore :: ReaderT Env IO a }+ deriving ( Functor+ , Applicative+ , Monad+ , MonadThrow+ , MonadCatch+ , MonadIO+ , MonadFix+ )++--------------------------------------------------------------------------------+getEnv :: EventStore Env+getEnv = EventStore ask++--------------------------------------------------------------------------------+getSettings :: EventStore Settings+getSettings = __settings <$> getEnv++--------------------------------------------------------------------------------+freshUUID :: EventStore UUID+freshUUID = liftIO nextRandom++--------------------------------------------------------------------------------+publisher :: EventStore Publish+publisher = fmap (asPub . __bus) getEnv++--------------------------------------------------------------------------------+stopBus :: EventStore ()+stopBus = busStop . __bus =<< getEnv++--------------------------------------------------------------------------------+instance MonadBase IO EventStore where+ liftBase m = EventStore $ liftBase m++--------------------------------------------------------------------------------+instance MonadBaseControl IO EventStore where+ type StM EventStore a = a+ liftBaseWith run = EventStore $ do+ env <- ask+ s <- liftIO $ run (\m -> runReaderT (unEventStore m) env)+ restoreM s+ restoreM = return++--------------------------------------------------------------------------------+instance MonadLogger EventStore where+ monadLoggerLog loc src lvl msg = do+ loggerRef <- __logRef <$> getEnv+ liftIO $ loggerCallback loggerRef loc src lvl (toLogStr msg)++--------------------------------------------------------------------------------+instance MonadLoggerIO EventStore where+ askLoggerIO = do+ loggerRef <- __logRef <$> getEnv+ return (loggerCallback loggerRef)++--------------------------------------------------------------------------------+runEventStore :: LoggerRef -> Settings -> Bus -> EventStore a -> IO a+runEventStore ref setts bus (EventStore action) =+ runReaderT action (Env ref setts bus (_monitoring bus))++--------------------------------------------------------------------------------+-- Messaging+--------------------------------------------------------------------------------+data Message where+ Message :: Typeable a => a -> Message+ deriving Typeable++--------------------------------------------------------------------------------+instance Show Message where+ show (Message a) = "Message: " <> show (typeOf a)++--------------------------------------------------------------------------------+toMsg :: Typeable a => a -> Message+toMsg = Message++--------------------------------------------------------------------------------+fromMsg :: Typeable a => Message -> Maybe a+fromMsg (Message a) = cast a++--------------------------------------------------------------------------------+class Pub p where+ publishSTM :: Typeable a => p -> a -> STM Bool++--------------------------------------------------------------------------------+publish :: Typeable a => a -> EventStore ()+publish a = do+ bus <- __bus <$> getEnv+ publishWith bus a++--------------------------------------------------------------------------------+publishWith :: (Pub p, Typeable a, MonadIO m) => p -> a -> m ()+publishWith p a = atomically $ do+ _ <- publishSTM p a+ return ()++--------------------------------------------------------------------------------+class Sub s where+ subscribeEventHandler :: s -> EventHandler -> IO ()++--------------------------------------------------------------------------------+subscribe :: (Sub s, Typeable a)+ => s+ -> (a -> EventStore ())+ -> IO ()+subscribe s k = subscribeEventHandler s (EventHandler Proxy k)++--------------------------------------------------------------------------------+data Publish = forall p. Pub p => Publish p++--------------------------------------------------------------------------------+instance Pub Publish where+ publishSTM (Publish p) a = publishSTM p a++--------------------------------------------------------------------------------+data Subscribe = forall p. Sub p => Subscribe p++--------------------------------------------------------------------------------+instance Sub Subscribe where+ subscribeEventHandler (Subscribe p) a = subscribeEventHandler p a++--------------------------------------------------------------------------------+data Hub = forall h. (Sub h, Pub h) => Hub h++--------------------------------------------------------------------------------+instance Sub Hub where+ subscribeEventHandler (Hub h) = subscribeEventHandler h++--------------------------------------------------------------------------------+instance Pub Hub where+ publishSTM (Hub h) = publishSTM h++--------------------------------------------------------------------------------+asSub :: Sub s => s -> Subscribe+asSub = Subscribe++--------------------------------------------------------------------------------+asPub :: Pub p => p -> Publish+asPub = Publish++--------------------------------------------------------------------------------+asHub :: (Sub h, Pub h) => h -> Hub+asHub = Hub++--------------------------------------------------------------------------------+data Type = Type TypeRep Fingerprint++--------------------------------------------------------------------------------+instance Show Type where+ show (Type rep _) = "type " <> show rep++--------------------------------------------------------------------------------+instance Eq Type where+ Type _ a == Type _ b = a == b++--------------------------------------------------------------------------------+instance Ord Type where+ compare (Type _ a) (Type _ b) = compare a b++--------------------------------------------------------------------------------+instance Hashable Type where+ hashWithSalt s (Type _ (Fingerprint b l)) = hashWithSalt s (b, l)++--------------------------------------------------------------------------------+data GetType+ = forall a. Typeable a => FromTypeable a+ | forall prx a. Typeable a => FromProxy (prx a)++--------------------------------------------------------------------------------+getFingerprint :: TypeRep -> Fingerprint+#if __GLASGOW_HASKELL__ == 708+getFingerprint (TypeRep fp _ _) = fp+#else+getFingerprint = typeRepFingerprint+#endif++--------------------------------------------------------------------------------+getType :: GetType -> Type+getType op = Type t (getFingerprint t)+ where+ t = case op of+ FromTypeable a -> typeOf a+ FromProxy prx -> typeRep prx++--------------------------------------------------------------------------------+type EventHandlers = HashMap Type (Seq EventHandler)++--------------------------------------------------------------------------------+propagate :: Typeable a => a -> Seq EventHandler -> EventStore ()+propagate a = traverse_ $ \(EventHandler _ k) -> do+ let Just b = cast a+ tpe = typeOf b+ outcome <- tryAny $ k b+ case outcome of+ Right _ -> return ()+ Left e -> $(logError) [i|Exception when propagating #{tpe}: #{e}.|]++--------------------------------------------------------------------------------+data EventHandler where+ EventHandler :: Typeable a+ => Proxy a+ -> (a -> EventStore ())+ -> EventHandler++--------------------------------------------------------------------------------+instance Show EventHandler where+ show (EventHandler prx _) = "Handle " <> show (typeRep prx)++--------------------------------------------------------------------------------+data Bus =+ Bus { _busLoggerRef :: LoggerRef+ , _busSettings :: Settings+ , _busEventHandlers :: IORef EventHandlers+ , _busQueue :: TBMQueue Message+ , _workerAsync :: Async ()+ , _monitoring :: Maybe Monitoring+ }++--------------------------------------------------------------------------------+busStop :: MonadIO m => Bus -> m ()+busStop Bus{..} = atomically $ closeTBMQueue _busQueue++--------------------------------------------------------------------------------+busProcessedEverything :: Bus -> IO ()+busProcessedEverything Bus{..} = wait _workerAsync++--------------------------------------------------------------------------------+messageType :: Type+messageType = getType (FromProxy (Proxy :: Proxy Message))++--------------------------------------------------------------------------------+newBus :: LoggerRef -> Settings -> IO Bus+newBus ref setts = do+ bus <- mfix $ \b -> do+ Bus ref setts <$> newIORef mempty+ <*> newTBMQueueIO 500+ <*> async (worker b)+ <*> traverse configureMonitoring (s_monitoring setts)++ return bus++--------------------------------------------------------------------------------+worker :: Bus -> IO ()+worker self@Bus{..} = loop+ where+ handleMsg (Message a) = do+ callbacks <- readIORef _busEventHandlers+ publishing self callbacks a+ loop++ loop = traverse_ handleMsg =<< atomically (readTBMQueue _busQueue)++--------------------------------------------------------------------------------+instance Sub Bus where+ subscribeEventHandler Bus{..} hdl@(EventHandler prx _) =+ atomicModifyIORef' _busEventHandlers update+ where+ update :: EventHandlers -> (EventHandlers, ())+ update callbacks =+ let tpe = getType (FromProxy prx)+ next = alterMap $ \input ->+ case input of+ Nothing -> Just (singleton hdl)+ Just hs -> Just (snoc hs hdl) in+ (next tpe callbacks, ())++--------------------------------------------------------------------------------+instance Pub Bus where+ publishSTM Bus{..} a = do+ closed <- isClosedTBMQueue _busQueue+ writeTBMQueue _busQueue (toMsg a)+ return $ not closed++--------------------------------------------------------------------------------+publishing :: Typeable a => Bus -> EventHandlers -> a -> IO ()+publishing self@Bus{..} callbacks a = do+ let tpe = getType (FromTypeable a)+ runEventStore _busLoggerRef _busSettings self $ do+ $(logDebug) [i|Publishing message #{tpe}.|]+ traverse_ (propagate a) (lookup tpe callbacks)+ $(logDebug) [i|Message #{tpe} propagated.|]++ unless (tpe == messageType) $+ traverse_ (propagate (toMsg a)) (lookup messageType callbacks)++--------------------------------------------------------------------------------+-- 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++--------------------------------------------------------------------------------+monitorIncrConnectionDrop :: EventStore ()+monitorIncrConnectionDrop = do+ Env{..} <- getEnv+ for_ __monitor $ \Monitoring{..}->+ liftIO $ inc _connDrops++--------------------------------------------------------------------------------+monitorAddDataTransmitted :: Int -> EventStore ()+monitorAddDataTransmitted siz = do+ Env{..} <- getEnv+ for_ __monitor $ \Monitoring{..}->+ liftIO $ add _dataTx (fromIntegral siz)++--------------------------------------------------------------------------------+monitorIncrForceReconnect :: EventStore ()+monitorIncrForceReconnect = do+ Env{..} <- getEnv+ for_ __monitor $ \Monitoring{..}->+ liftIO $ inc _forceReco++--------------------------------------------------------------------------------+monitorIncrHeartbeatTimeouts :: EventStore ()+monitorIncrHeartbeatTimeouts = do+ Env{..} <- getEnv+ for_ __monitor $ \Monitoring{..}->+ liftIO $ inc _heartTimeout
Database/EventStore/Internal/Discovery.hs view
@@ -34,10 +34,10 @@ ) where --------------------------------------------------------------------------------+import Prelude (String) import Data.Maybe ---------------------------------------------------------------------------------import ClassyPrelude import Data.Aeson import Data.Aeson.Types import Data.Array.IO@@ -50,6 +50,7 @@ -------------------------------------------------------------------------------- import Database.EventStore.Internal.EndPoint+import Database.EventStore.Internal.Prelude -------------------------------------------------------------------------------- data DnsDiscoveryException@@ -64,7 +65,7 @@ httpRequest :: EndPoint -> String -> IO Request httpRequest (EndPoint ip p) path = parseUrlThrow url where- url = "http://" ++ ip ++ ":" ++ show p ++ path+ url = "http://" <> ip <> ":" <> show p <> path -------------------------------------------------------------------------------- -- | Represents a source of cluster gossip.@@ -228,7 +229,7 @@ parseJSON (String txt) = case fromText txt of Just uuid -> return $ GUUID uuid- _ -> fail $ "Wrong UUID format " ++ show txt+ _ -> fail $ "Wrong UUID format " <> show txt parseJSON invalid = typeMismatch "UUID" invalid --------------------------------------------------------------------------------
Database/EventStore/Internal/EndPoint.hs view
@@ -12,15 +12,22 @@ module Database.EventStore.Internal.EndPoint where ---------------------------------------------------------------------------------import ClassyPrelude+import Prelude (String) --------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude++-------------------------------------------------------------------------------- -- | Gathers both an IPv4 and a port. data EndPoint = EndPoint { endPointIp :: !String , endPointPort :: !Int- } deriving Show+ } deriving Eq++--------------------------------------------------------------------------------+instance Show EndPoint where+ show (EndPoint h p) = h <> ":" <> show p -------------------------------------------------------------------------------- emptyEndPoint :: EndPoint
+ Database/EventStore/Internal/Exec.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Exec+-- 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.Exec+ ( Exec+ , Terminated(..)+ , newExec+ , execSettings+ , execWaitTillClosed+ ) where++--------------------------------------------------------------------------------+import Prelude (String)+import Data.Typeable++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Communication+import Database.EventStore.Internal.Connection+import Database.EventStore.Internal.ConnectionManager+import Database.EventStore.Internal.Control+import Database.EventStore.Internal.Discovery+import Database.EventStore.Internal.Logger+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.TimerService++--------------------------------------------------------------------------------+type ServicePendingInit = HashMap Service ()++--------------------------------------------------------------------------------+data Stage+ = Init+ | Available Publish+ | Errored String++--------------------------------------------------------------------------------+newtype Terminated = Terminated String deriving (Show, Typeable)++--------------------------------------------------------------------------------+instance Exception Terminated++--------------------------------------------------------------------------------+data Exec =+ Exec { execSettings :: Settings+ , _execPub :: STM Publish+ , _internal :: Internal+ , execWaitTillClosed :: IO ()+ }++--------------------------------------------------------------------------------+data Internal =+ Internal { _initRef :: IORef ServicePendingInit+ , _finishRef :: IORef ServicePendingInit+ , _stageVar :: TVar Stage+ }++--------------------------------------------------------------------------------+instance Pub Exec where+ publishSTM e a = do+ pub <- _execPub e+ handled <- publishSTM pub a+ unless handled $+ throwSTM $ Terminated "Connection Closed."++ return handled++--------------------------------------------------------------------------------+stageSTM :: TVar Stage -> STM Publish+stageSTM var = do+ stage <- readTVar var+ case stage of+ Init -> retrySTM+ Available pub -> return pub+ Errored msg -> throwSTM $ Terminated msg++--------------------------------------------------------------------------------+errored :: TVar Stage -> String -> STM ()+errored var err = do+ stage <- readTVar var+ case stage of+ Errored _ -> return ()+ _ -> writeTVar var (Errored err)++--------------------------------------------------------------------------------+initServicePending :: ServicePendingInit+initServicePending = foldMap (\svc -> singletonMap svc ()) [minBound..]++--------------------------------------------------------------------------------+newExec :: Settings -> Bus -> ConnectionBuilder -> Discovery -> IO Exec+newExec setts mainBus builder disc = do++ internal <- Internal <$> newIORef initServicePending+ <*> newIORef initServicePending+ <*> newTVarIO Init++ let stagePub = stageSTM $ _stageVar internal+ exe = Exec setts stagePub internal (busProcessedEverything mainBus)+ hub = asHub mainBus++ timerService hub+ connectionManager builder disc hub++ subscribe mainBus (onInit internal)+ subscribe mainBus (onInitFailed internal)+ subscribe mainBus (onFatal internal)+ subscribe mainBus (onTerminated internal)+ subscribe mainBus (onShutdown internal)++ publishWith mainBus SystemInit++ return exe++--------------------------------------------------------------------------------+onInit :: Internal -> Initialized -> EventStore ()+onInit Internal{..} (Initialized svc) = do+ $logInfo [i|Service #{svc} initialized|]+ initialized <- atomicModifyIORef' _initRef $ \m ->+ let m' = deleteMap svc m in+ (m', null m')++ when initialized $ do+ $logInfo "Entire system initialized properly"+ pub <- publisher+ atomically $ writeTVar _stageVar (Available pub)++--------------------------------------------------------------------------------+onInitFailed :: Internal -> InitFailed -> EventStore ()+onInitFailed Internal{..} (InitFailed svc) = do+ atomically $ errored _stageVar "Driver failed to initialized"+ $logError [i|Service #{svc} failed to initialize.|]+ stopBus+ $logError "System can't start."++--------------------------------------------------------------------------------+onFatal :: Internal -> FatalException -> EventStore ()+onFatal self@Internal{..} situation = do+ case situation of+ FatalException e ->+ $(logOther "Fatal") [i|Fatal exception: #{e}|]+ FatalCondition msg ->+ $(logOther "Fatal") [i|Driver is in unrecoverable state: #{msg}.|]++ shutdown self++--------------------------------------------------------------------------------+onTerminated :: Internal -> ServiceTerminated -> EventStore ()+onTerminated Internal{..} (ServiceTerminated svc) = do+ $logInfo [i|Service #{svc} terminated.|]+ terminated <- atomicModifyIORef' _finishRef $ \m ->+ let m' = deleteMap svc m in+ (m', null m')++ when terminated $ do+ $logInfo "Entire system shutdown properly"+ stopBus++--------------------------------------------------------------------------------+onShutdown :: Internal -> SystemShutdown -> EventStore ()+onShutdown Internal{..} _ =+ atomically $ writeTVar _stageVar (Errored "Connection closed")++--------------------------------------------------------------------------------+shutdown :: Internal -> EventStore ()+shutdown Internal{..} = do+ atomically $ writeTVar _stageVar (Errored "Connection closed")+ publish SystemShutdown
− Database/EventStore/Internal/Execution/Production.hs
@@ -1,587 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Execution.Production--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Production execution model. It's striving for robustness. The model consists--- on 4 threads. The Reader thread that reads 'Package' from the connection, the--- Runner thread which executes finalizers submitted by the user (typically what--- to do on operation completion or when a event has arrived for a subscription)--- , the writer thread that sends 'Package' to the server, and the Manager--- thread that handles requests coming both from the user and the Reader--- thread. If the Reader or Runner threads die, it will be restarted by the--- Manager thread if the connection hasn't been closed by user in the meantime.----------------------------------------------------------------------------------module Database.EventStore.Internal.Execution.Production- ( Production- , newExecutionModel- , pushOperation- , shutdownExecutionModel- , pushConnectStream- , pushConnectPersist- , pushCreatePersist- , pushUpdatePersist- , pushDeletePersist- , pushAckPersist- , pushNakPersist- , pushUnsubscribe- , prodWaitTillClosed- , pushForceReconnect- ) where-----------------------------------------------------------------------------------import Control.Exception (AsyncException(..), asyncExceptionFromException)-import Control.Monad.Fix-import Data.Int-----------------------------------------------------------------------------------import ClassyPrelude-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.Connection-import Database.EventStore.Internal.Discovery-import Database.EventStore.Internal.EndPoint-import Database.EventStore.Internal.Generator-import Database.EventStore.Internal.Manager.Subscription.Driver hiding- ( submitPackage- , unsubscribe- , ackPersist- , nakPersist- , abort- )-import Database.EventStore.Internal.Manager.Subscription.Model-import Database.EventStore.Internal.Operation-import Database.EventStore.Internal.Processor-import Database.EventStore.Internal.Types-import Database.EventStore.Logging-----------------------------------------------------------------------------------data Worker- = Reader ThreadId- | Runner ThreadId- | Writer ThreadId- deriving Show------------------------------------------------------------------------------------- | Used to determine if we hit the end of the queue.-data Slot a = Slot !a | End------------------------------------------------------------------------------------- | A 'TQueue' that can be cycled.-newtype CycleQueue a = CycleQueue (TQueue (Slot a))------------------------------------------------------------------------------------- | Creates an empty 'CycleQueue'.-newCycleQueue :: IO (CycleQueue a)-newCycleQueue = fmap CycleQueue newTQueueIO------------------------------------------------------------------------------------- | Gets an element from the 'CycleQueue'.-readCycleQueue :: CycleQueue a -> STM a-readCycleQueue (CycleQueue q) = do- Slot a <- readTQueue q- return a------------------------------------------------------------------------------------- | Writes an element to the 'CycleQueue'.-writeCycleQueue :: CycleQueue a -> a -> STM ()-writeCycleQueue (CycleQueue q) a = writeTQueue q (Slot a)------------------------------------------------------------------------------------- | Empties a 'CycleQueue'.-emptyCycleQueue :: CycleQueue a -> STM ()-emptyCycleQueue (CycleQueue q) = writeTQueue q End >> go- where- go = do- s <- readTQueue q- case s of- End -> return ()- _ -> go------------------------------------------------------------------------------------- | Updates a 'CycleQueue'.-updateCycleQueue :: CycleQueue a -> (a -> STM (Maybe a)) -> STM ()-updateCycleQueue (CycleQueue q) k = writeTQueue q End >> go- where- go = do- s <- readTQueue q- case s of- End -> return ()- Slot a -> do- r <- k a- case r of- Nothing -> go- Just a' -> writeTQueue q (Slot a') >> go------------------------------------------------------------------------------------- | Indicates if a 'CycleQueue' is empty.-isEmptyCycleQueue :: CycleQueue a -> STM Bool-isEmptyCycleQueue (CycleQueue q) = isEmptyTQueue q-----------------------------------------------------------------------------------wkUpdState :: Worker -> State -> State-wkUpdState (Reader tid) s = s { _reader = Just tid }-wkUpdState (Runner tid) s = s { _runner = Just tid }-wkUpdState (Writer tid) s = s { _writer = Just tid }------------------------------------------------------------------------------------- | Holds the execution model state.-data Production =- Prod- { _submit :: TVar (Msg -> IO ())- -- ^ The action to call when pushing new command.- , _waitClosed :: STM ()- -- ^ Action that attests the execution model has been closed successfully.- -- It doesn't mean the execution model hasn't been shutdown because of- -- some random exception.- }------------------------------------------------------------------------------------- | Main execution environment used among different transitions.-data Env =- Env- { _setts :: Settings- -- ^ Global settings reference.- , _queue :: CycleQueue Msg- -- ^ That queue ties the user, the reader thread and the manager thread.- -- The user and the reader push new messages onto the queue while the- -- manager dequeue and handles one message at the time.- , _pkgQueue :: CycleQueue Package- -- ^ That queue ties the writer thread with the manager thread. The writer- -- dequeue packages from that queue and sends those to the server. While- -- the manager pushes new packages on every new submitted operation.- , _jobQueue :: CycleQueue Job- -- ^ That queue ties the runner thread with the manager thread. The runner- -- dequeues IO action from it while the manager pushes new command- -- finalizers as those arrived.- , _state :: TVar State- -- ^ Holds manager thread state.- , _nextSubmit :: TVar (Msg -> IO ())- -- ^ Indicates the action to call in order to push new commands.- , _connVar :: TVar InternalConnection- -- ^ Connection to the server.- , _disposed :: TMVar ()- -- ^ Indicates when the production execution model has been shutdown and- -- disposed any ongoing operations.- }-----------------------------------------------------------------------------------data Msg- = Stopped Worker SomeException- | Arrived Package- | Shutdown- | forall a.- NewOperation (Either OperationError a -> IO ()) (Operation a)- | ConnectStream (SubConnectEvent -> IO ()) Text Bool- | ConnectPersist (SubConnectEvent -> IO ()) Text Text Int32- | Unsubscribe Running- | CreatePersist (Either PersistActionException ConfirmedAction -> IO ())- Text Text PersistentSubscriptionSettings- | UpdatePersist (Either PersistActionException ConfirmedAction -> IO ())- Text Text PersistentSubscriptionSettings- | DeletePersist (Either PersistActionException ConfirmedAction -> IO ())- Text Text- | AckPersist Running [UUID]- | NakPersist Running NakAction (Maybe Text) [UUID]- | ForceReconnect NodeEndPoints-----------------------------------------------------------------------------------pushCmd :: Production -> Msg -> IO ()-pushCmd (Prod _sender _) msg = do- push <- readTVarIO _sender- push msg------------------------------------------------------------------------------------- | Asks to shutdown the connection to the server asynchronously.-shutdownExecutionModel :: Production -> IO ()-shutdownExecutionModel prod = pushCmd prod Shutdown------------------------------------------------------------------------------------- | Pushes a new 'Operation' asynchronously.-pushOperation :: Production- -> (Either OperationError a -> IO ())- -> Operation a- -> IO ()-pushOperation prod k op = pushCmd prod (NewOperation k op)------------------------------------------------------------------------------------- | Subscribes to a regular stream.-pushConnectStream :: Production- -> (SubConnectEvent -> IO ())- -> Text- -> Bool- -> IO ()-pushConnectStream prod k n tos = pushCmd prod (ConnectStream k n tos)------------------------------------------------------------------------------------- | Subscribes to a persistent subscription.-pushConnectPersist :: Production- -> (SubConnectEvent -> IO ())- -> Text- -> Text- -> Int32- -> IO ()-pushConnectPersist prod k g n buf = pushCmd prod (ConnectPersist k g n buf)------------------------------------------------------------------------------------- | Creates a persistent subscription.-pushCreatePersist :: Production- -> (Either PersistActionException ConfirmedAction -> IO ())- -> Text- -> Text- -> PersistentSubscriptionSettings- -> IO ()-pushCreatePersist prod k g n setts = pushCmd prod (CreatePersist k g n setts)------------------------------------------------------------------------------------- | Updates a persistent subscription.-pushUpdatePersist :: Production- -> (Either PersistActionException ConfirmedAction -> IO ())- -> Text- -> Text- -> PersistentSubscriptionSettings- -> IO ()-pushUpdatePersist prod k g n setts = pushCmd prod (UpdatePersist k g n setts)------------------------------------------------------------------------------------- | Deletes a persistent subscription.-pushDeletePersist :: Production- -> (Either PersistActionException ConfirmedAction -> IO ())- -> Text- -> Text- -> IO ()-pushDeletePersist prod k g n = pushCmd prod (DeletePersist k g n)------------------------------------------------------------------------------------- | Acknowledges a set of events has been successfully handled.-pushAckPersist :: Production -> Running -> [UUID] -> IO ()-pushAckPersist prod run evts = pushCmd prod (AckPersist run evts)------------------------------------------------------------------------------------- | Acknowledges a set of events hasn't been handled successfully.-pushNakPersist :: Production- -> Running- -> NakAction- -> Maybe Text- -> [UUID]- -> IO ()-pushNakPersist prod run act res evts =- pushCmd prod (NakPersist run act res evts)------------------------------------------------------------------------------------- | Unsubscribe from a subscription.-pushUnsubscribe :: Production -> Running -> IO ()-pushUnsubscribe prod r = pushCmd prod (Unsubscribe r)------------------------------------------------------------------------------------- | Waits the execution model to close properly.-prodWaitTillClosed :: Production -> IO ()-prodWaitTillClosed (Prod _ disposed) = atomically disposed-----------------------------------------------------------------------------------pushForceReconnect :: Production -> NodeEndPoints -> IO ()-pushForceReconnect prod n = pushCmd prod (ForceReconnect n)-----------------------------------------------------------------------------------newtype Job = Job (IO ())------------------------------------------------------------------------------------- Internal Production state.----------------------------------------------------------------------------------data State =- State- { _proc :: !(Processor (IO ()))- , _reader :: !(Maybe ThreadId)- , _runner :: !(Maybe ThreadId)- , _writer :: !(Maybe ThreadId)- }-----------------------------------------------------------------------------------emptyState :: Settings -> Generator -> State-emptyState setts gen = State (newProcessor setts gen) Nothing Nothing Nothing-----------------------------------------------------------------------------------updateProc :: Processor (IO ()) -> State -> State-updateProc p s = s { _proc = p }------------------------------------------------------------------------------------- | Reader thread. Keeps reading 'Package' from the server.-reader :: Settings -> CycleQueue Msg -> InternalConnection -> IO ()-reader sett queue c = forever $ do- pkg <- connRecv c- let cmd = packageCmd pkg- uuid = packageCorrelation pkg-- atomically $ writeCycleQueue queue (Arrived pkg)- _settingsLog sett $ Info $ PackageReceived cmd uuid------------------------------------------------------------------------------------- | Writer thread, writes incoming 'Package's----------------------------------------------------------------------------------writer :: Settings -> CycleQueue Package -> InternalConnection -> IO ()-writer setts pkg_queue conn = forever $ do- pkg <- atomically $ readCycleQueue pkg_queue- connSend conn pkg- let cmd = packageCmd pkg- uuid = packageCorrelation pkg-- _settingsLog setts $ Info $ PackageSent cmd uuid------------------------------------------------------------------------------------- Runner thread. Keeps running job comming from the Manager thread.----------------------------------------------------------------------------------runner :: CycleQueue Job -> IO ()-runner job_queue = forever $ do- Job j <- atomically $ readCycleQueue job_queue- j------------------------------------------------------------------------------------- | Spawns a new thread worker.-spawn :: Env -> (ThreadId -> Worker) -> IO Worker-spawn Env{..} mk = do- conn <- readTVarIO _connVar- tid <- mfix $ \tid ->- let worker = mk tid- action =- case worker of- Reader _ -> reader _setts _queue conn- Runner _ -> runner _jobQueue- Writer _ -> writer _setts _pkgQueue conn in- forkFinally action $ \r ->- case r of- Left e ->- case asyncExceptionFromException e of- Just ThreadKilled -> return ()- _ -> atomically $ writeCycleQueue _queue- $ Stopped worker e- _ -> return ()- return $ mk tid------------------------------------------------------------------------------------- | Loops over a 'Processor''s 'Transition' state machine, returning an updated--- 'Processor' model at the end.-runTransition :: Env -> Transition (IO ()) -> STM (Processor (IO ()))-runTransition Env{..} = go- where- go (Produce j nxt) = do- let job = Job j- writeCycleQueue _jobQueue job- go nxt- go (Transmit pkg nxt) = do- writeCycleQueue _pkgQueue pkg- go nxt- go (Await new_proc) = return new_proc- go (ForceReconnectCmd node nxt) = do- writeCycleQueue _queue (ForceReconnect node)- go nxt------------------------------------------------------------------------------------- | First execution mode. It spawns initial reader, runner and writer threads.--- Then it switches to 'cruising' mode.-bootstrap :: Env -> IO ()-bootstrap env@Env{..} = do- rew <- spawn env Reader- ruw <- spawn env Runner- wrw <- spawn env Writer- let _F = wkUpdState rew .- wkUpdState ruw .- wkUpdState wrw- atomically $ modifyTVar' _state _F- cruising env-----------------------------------------------------------------------------------data ForceReconnectException = ForceReconnectionException NodeEndPoints- deriving (Show, Typeable)-----------------------------------------------------------------------------------instance Exception ForceReconnectException------------------------------------------------------------------------------------- | Crusing execution mode. Reads and handle message coming from the channel as--- those are arrived. That mode is used when the connection to the server is--- still live. We might have deconnection once in a while but at the end, if--- we managed to reconnect to it, we consider everything is fine.-cruising :: Env -> IO ()-cruising env@Env{..} = do- msg <- atomically $ readCycleQueue _queue- s <- readTVarIO _state- case msg of- Stopped _ e -> throwIO e- ForceReconnect node -> throwIO $ ForceReconnectionException node- Arrived pkg -> do- let sm = submitPackage pkg $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- Shutdown -> throwIO ClosedConnection- NewOperation k op -> do- let sm = newOperation k op $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- ConnectStream k n tos -> do- let sm = connectRegularStream k n tos $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- ConnectPersist k g n b -> do- let sm = connectPersistent k g n b $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- Unsubscribe r -> do- let sm = unsubscribe r $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- CreatePersist k g n psetts -> do- let sm = createPersistent k g n psetts $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- UpdatePersist k g n psetts -> do- let sm = updatePersistent k g n psetts $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- DeletePersist k g n -> do- let sm = deletePersistent k g n $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- AckPersist run evts -> do- let sm = ackPersist (return ()) run evts $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env- NakPersist run act res evts -> do- let sm = nakPersist (return ()) run act res evts $ _proc s- atomically $ do- new_proc <- runTransition env sm- modifyTVar' _state $ updateProc new_proc- cruising env------------------------------------------------------------------------------------- | That mode is triggered either because the user asks to shutdown the--- connection or because the connection to server has been dropped and we--- can't reconnect.-closing :: Env -> IO ()-closing env@Env{..} = do- State _ retid rutid wutid <- readTVarIO _state- -- We kill reader and writer threads to avoid in fly package in the cleaning- -- phase.- traverse_ killThread retid- traverse_ killThread wutid-- -- Discards every 'Package' that was about to be sent.- atomically $ emptyCycleQueue _pkgQueue-- -- Takes care of 'Package's that have already arrived. Just in case those- -- are completing or moving forward ongoing operations. Every ongoing- -- request is kept for later reconnection. Some transient operations like- -- Ack, Nak or Unsubscribe are just discard.- atomically $ updateCycleQueue _queue $ \nxt ->- case nxt of- Arrived pkg -> do- s <- readTVar _state- let sm = submitPackage pkg $ _proc s- nxt_proc <- runTransition env sm- modifyTVar' _state $ updateProc nxt_proc- return Nothing- Shutdown -> return Nothing- AckPersist _ _ -> return Nothing- NakPersist _ _ _ _ -> return Nothing- Unsubscribe _ -> return Nothing- Stopped _ _ -> return Nothing- _ -> return $ Just nxt-- -- If the connection is already closed, it will throw an exception. We just- -- make sure it doesn't interfere with the cleaning process.- conn <- readTVarIO _connVar- _ <- try $ connClose conn :: (IO (Either ConnectionException ()))- atomically $ do- s <- readTVar _state- _ <- runTransition env $ abort $ _proc s- return ()-- -- Waits the runner thread to deal with its jobs list.- atomically $ do- end <- isEmptyCycleQueue _jobQueue- unless end retrySTM-- traverse_ killThread rutid-----------------------------------------------------------------------------------raiseException :: Exception e => e -> Msg -> IO ()-raiseException e _ = throwIO e-----------------------------------------------------------------------------------handles :: SomeException -> [Handler IO a] -> IO a-handles e [] = throwIO e-handles e (Handler k:hs) =- case fromException e of- Just t -> k t- _ -> handles e hs------------------------------------------------------------------------------------- | Main Production execution model entry point.-newExecutionModel :: Settings -> Discovery -> IO Production-newExecutionModel setts disc = do- gen <- newGenerator- queue <- newCycleQueue- pkg_queue <- newCycleQueue- job_queue <- newCycleQueue- conn <- newConnection setts disc- conn_var <- newTVarIO conn- var <- newTVarIO $ emptyState setts gen- nxt_sub <- newTVarIO (atomically . writeCycleQueue queue)- disposed <- newEmptyTMVarIO- let env = Env setts queue pkg_queue job_queue var nxt_sub conn_var disposed- handler res = do- closing env- case res of- Left e -> do- _settingsLog setts (Error $ UnexpectedException e)- handles e- [ Handler $ \(_ :: ConnectionException) ->- atomically $ do- writeTVar nxt_sub (raiseException e)- putTMVar disposed ()- , Handler $ \(ForceReconnectionException node) -> do- new_conn <- newConnection setts disc- connForceReconnect new_conn node- atomically $ writeTVar conn_var new_conn- _ <- forkFinally (bootstrap env) handler- return ()- , Handler $ \(_ :: SomeException) -> do- new_conn <- newConnection setts disc- atomically $ writeTVar conn_var new_conn- _ <- forkFinally (bootstrap env) handler- return ()- ]- _ -> atomically $ putTMVar disposed ()- _ <- forkFinally (bootstrap env) handler- return $ Prod nxt_sub $ do- curConn <- readTVar conn_var- unlessM (connIsClosed curConn) retrySTM- readTMVar disposed
− Database/EventStore/Internal/Generator.hs
@@ -1,43 +0,0 @@------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Generator--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Pure UUID generator.----------------------------------------------------------------------------------module Database.EventStore.Internal.Generator- ( Generator- , nextUUID- , newGenerator- , splitGenerator- ) where-----------------------------------------------------------------------------------import ClassyPrelude-import Data.UUID-import System.Random------------------------------------------------------------------------------------- | Pure 'UUID' generator.-newtype Generator = Generator StdGen------------------------------------------------------------------------------------- | Gets the next fresh 'UUID'.-nextUUID :: Generator -> (UUID, Generator)-nextUUID (Generator g) = let (u, nxt) = random g in (u, Generator nxt)------------------------------------------------------------------------------------- | Builds 2 new 'Generator's out of one.-splitGenerator :: Generator -> (Generator, Generator)-splitGenerator (Generator g) =- let (g1, g2) = split g in (Generator g1, Generator g2)------------------------------------------------------------------------------------- | Creates a new 'Generator'.-newGenerator :: IO Generator-newGenerator = fmap Generator getStdGen
+ Database/EventStore/Internal/Logger.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Logger+-- 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.Logger+ ( LoggerRef+ , LoggerFilter(..)+ , newLoggerRef+ , loggerCallback+ , module Control.Monad.Logger+ , module Data.String.Interpolate.IsString+ , module System.Log.FastLogger+ ) where++--------------------------------------------------------------------------------+import Control.Monad.Logger+import Data.String.Interpolate.IsString+import System.Log.FastLogger hiding (check)++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude++--------------------------------------------------------------------------------+data LoggerFilter+ = LoggerFilter (LogSource -> LogLevel -> Bool)+ | LoggerLevel LogLevel++--------------------------------------------------------------------------------+toLogPredicate :: LoggerFilter -> (LogSource -> LogLevel -> Bool)+toLogPredicate (LoggerFilter k) = k+toLogPredicate (LoggerLevel lvl) = \_ t -> t >= lvl++--------------------------------------------------------------------------------+data LoggerRef+ = LoggerRef !TimedFastLogger !LoggerFilter !Bool !(IO ())+ | NoLogger++--------------------------------------------------------------------------------+loggerCallback :: LoggerRef -> (Loc -> LogSource -> LogLevel -> LogStr -> IO ())+loggerCallback NoLogger = \_ _ _ _ -> return ()+loggerCallback (LoggerRef logger filt detailed _) = \loc src lvl msg ->+ when (predicate src lvl) $+ loggerFormat logger (if detailed then loc else defaultLoc) src lvl msg+ where+ predicate = toLogPredicate filt++--------------------------------------------------------------------------------+loggerFormat :: TimedFastLogger+ -> (Loc -> LogSource -> LogLevel -> LogStr -> IO ())+loggerFormat logger = \loc src lvl msg ->+ logger $ \t ->+ toLogStr ("["`mappend` t `mappend`"]") `mappend` " eventstore "+ `mappend` defaultLogStr loc src lvl msg++--------------------------------------------------------------------------------+newLoggerRef :: LogType -> LoggerFilter -> Bool -> IO LoggerRef+newLoggerRef LogNone _ _ = return NoLogger+newLoggerRef typ filt detailed =+ case typ of+ LogNone -> return NoLogger+ other -> do+ cache <- newTimeCache simpleTimeFormat+ (logger, cleanup) <- newTimedFastLogger cache other+ return $ LoggerRef logger filt detailed cleanup
− Database/EventStore/Internal/Manager/Operation/Model.hs
@@ -1,204 +0,0 @@-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE RecordWildCards #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Manager.Operation.Model--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Main operation bookkeeping structure.----------------------------------------------------------------------------------module Database.EventStore.Internal.Manager.Operation.Model- ( Model- , Transition(..)- , newModel- , pushOperation- , submitPackage- , abort- ) where-----------------------------------------------------------------------------------import ClassyPrelude-import Data.ProtocolBuffers-import Data.Serialize-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.Command-import Database.EventStore.Internal.Generator-import Database.EventStore.Internal.Operation-import Database.EventStore.Internal.Types------------------------------------------------------------------------------------- | Entry of a running 'Operation'.-data Elem r =- forall a resp. Decode resp =>- Elem- { _opOp :: Operation a- , _opCmd :: Command- , _opCont :: resp -> SM a ()- , _opCb :: Either OperationError a -> r- }------------------------------------------------------------------------------------- | Operation internal state.-data State r =- State- { _gen :: Generator- -- ^ 'UUID' generator.- , _pending :: HashMap UUID (Elem r)- -- ^ Contains all running 'Operation's.- }-----------------------------------------------------------------------------------initState :: Generator -> State r-initState g = State g mempty------------------------------------------------------------------------------------- | Type of requests handled by the model.-data Request r- = forall a. New (Operation a) (Either OperationError a -> r)- -- ^ Register a new 'Operation'.- | Pkg Package- -- ^ Submit a package.- | Abort- -- ^ Aborts every pending operation.------------------------------------------------------------------------------------- | Output produces by the interpretation of an 'Operation'.-data Transition r- = Produce r (Transition r)- -- ^ Produces an intermediary value.- | Transmit Package (Transition r)- -- ^ Asks for sending the given 'Package'.- | Await (Model r)- -- ^ waits for more input.- | NotHandled MasterInfo (Transition r)------------------------------------------------------------------------------------- | Main 'Operation' bookkeeping state machine.-newtype Model r = Model (Request r -> Maybe (Transition r))------------------------------------------------------------------------------------- | Pushes a new 'Operation' to model. The given 'Operation' state-machine is--- initialized and produces a 'Package'.-pushOperation :: (Either OperationError a -> r)- -> Operation a- -> Model r- -> Transition r-pushOperation cb op (Model k) = let Just t = k (New op cb) in t------------------------------------------------------------------------------------- | Submits a 'Package' to the model. If the model isn't concerned by the--- 'Package', it will returns 'Nothing'. Because 'Operation' can implement--- complex logic (retry for instance), it returns a 'Step'.-submitPackage :: Package -> Model r -> Maybe (Transition r)-submitPackage pkg (Model k) = k (Pkg pkg)------------------------------------------------------------------------------------- | Aborts every pending operation.-abort :: Model r -> Transition r-abort (Model k) = let Just t = k Abort in t-----------------------------------------------------------------------------------runOperation :: Settings- -> (Either OperationError a -> r)- -> Operation a- -> SM a ()- -> State r- -> Transition r-runOperation setts cb op start init_st = go init_st start- where- go st (Return _) = Await $ Model $ execute setts st- go st (Yield a n) = Produce (cb $ Right a) (go st n)- go st (FreshId k) =- let (new_id, nxt_gen) = nextUUID $ _gen st- nxt_st = st { _gen = nxt_gen } in- go nxt_st $ k new_id- go st (SendPkg ci co rq k) =- let (new_uuid, nxt_gen) = nextUUID $ _gen st- pkg = Package- { packageCmd = ci- , packageCorrelation = new_uuid- , packageData = runPut $ encodeMessage rq- , packageCred = s_credentials setts- }- elm = Elem op co k cb- ps = insertMap new_uuid elm $ _pending st- nxt_st = st { _pending = ps- , _gen = nxt_gen- } in- Transmit pkg (Await $ Model $ execute setts nxt_st)- go st (Failure m) =- case m of- Just e -> Produce (cb $ Left e) (Await $ Model $ execute setts st)- _ -> runOperation setts cb op op st-----------------------------------------------------------------------------------runPackage :: Settings -> State r -> Package -> Maybe (Transition r)-runPackage setts st pkg@Package{..} = do- Elem op resp_cmd cont cb <- lookup packageCorrelation $ _pending st- let nxt_ps = deleteMap packageCorrelation $ _pending st- nxt_st = st { _pending = nxt_ps }- case packageCmd of- -- Bad request- 0xF0 -> do- let reason = packageDataAsText pkg- resp = ServerError reason- value = cb $ Left $ resp- return $ Produce value (Await $ Model $ execute setts nxt_st)- 0xF4 -> do- let value = cb $ Left NotAuthenticatedOp- return $ Produce value (Await $ Model $ execute setts nxt_st)- -- Not handled- 0xF1 -> do- msg <- maybeDecodeMessage packageData- let reason = getField $ notHandledReason msg- case reason of- N_NotMaster -> do- info <- getField $ notHandledAdditionalInfo msg- let next = runOperation setts cb op op nxt_st- return $ NotHandled (masterInfo info) next- -- In this case with just retry the operation.- _ -> return $ runOperation setts cb op op nxt_st- _ | packageCmd /= resp_cmd -> do- let r = cb $ Left $ InvalidServerResponse resp_cmd packageCmd- return $ Produce r (Await $ Model $ execute setts nxt_st)- | otherwise ->- case runGet decodeMessage packageData of- Left e ->- let r = cb $ Left $ ProtobufDecodingError e in- return $ Produce r (Await $ Model $ execute setts nxt_st)- Right m -> return $ runOperation setts cb op (cont m) nxt_st-----------------------------------------------------------------------------------abortOperations :: Settings -> State r -> Transition r-abortOperations setts init_st = go init_st $ mapToList $ _pending init_st- where- go st ((key, Elem _ _ _ k):xs) =- let ps = deleteMap key $ _pending st- nxt_st = st { _pending = ps } in- Produce (k $ Left Aborted) $ go nxt_st xs- go st [] = Await $ Model $ execute setts st------------------------------------------------------------------------------------- | Creates a new 'Operation' model state-machine.-newModel :: Settings -> Generator -> Model r-newModel setts g = Model $ execute setts $ initState g-----------------------------------------------------------------------------------execute :: Settings -> State r -> Request r -> Maybe (Transition r)-execute setts st (New op cb) = Just $ runOperation setts cb op op st-execute setts st (Pkg pkg) = runPackage setts st pkg-execute setts st Abort = Just $ abortOperations setts st-----------------------------------------------------------------------------------maybeDecodeMessage :: Decode a => ByteString -> Maybe a-maybeDecodeMessage bytes =- case runGet decodeMessage bytes of- Right a -> Just a- _ -> Nothing
+ Database/EventStore/Internal/Manager/Operation/Registry.hs view
@@ -0,0 +1,427 @@+{-# 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+--+-- Main operation bookkeeping structure.+--------------------------------------------------------------------------------+module Database.EventStore.Internal.Manager.Operation.Registry+ ( Registry+ , OperationMaxAttemptReached(..)+ , Decision(..)+ , newRegistry+ , register+ , handlePackage+ , abortPendingRequests+ , checkAndRetry+ , startAwaitings+ ) where++--------------------------------------------------------------------------------+import Data.ProtocolBuffers+import Data.Serialize+import Data.Time+import Data.UUID+import Data.UUID.V4++--------------------------------------------------------------------------------+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.Stopwatch+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+data Request =+ Request { _requestCmd :: !Command+ , _requestPayload :: !ByteString+ }++--------------------------------------------------------------------------------+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++--------------------------------------------------------------------------------+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++--------------------------------------------------------------------------------+resumeNoPkgSession :: Registry -> Session -> EventStore ()+resumeNoPkgSession reg session@Session{..} = do+ atomicModifyIORef' sessionStack $ \case+ Optional k -> (Resolved $ k Nothing, ())+ same -> (same, ())++ execute reg session++--------------------------------------------------------------------------------+reinitSession :: Session -> EventStore ()+reinitSession Session{..} = atomicWriteIORef sessionStack (Resolved sessionOp)++--------------------------------------------------------------------------------+restartSession :: Registry -> Session -> EventStore ()+restartSession reg session = do+ reinitSession session+ execute reg session++--------------------------------------------------------------------------------+data Sessions =+ Sessions { sessionsNextId :: IORef SessionId+ , sessionsMap :: IORef SessionMap+ }++--------------------------------------------------------------------------------+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)++--------------------------------------------------------------------------------+destroySession :: Sessions -> Session -> EventStore ()+destroySession Sessions{..} s =+ atomicModifyIORef' sessionsMap $ \m -> (deleteMap (sessionId s) m, ())++--------------------------------------------------------------------------------+sessionDisposed :: Sessions -> Session -> EventStore Bool+sessionDisposed Sessions{..} s =+ notMember (sessionId s) <$> readIORef sessionsMap++--------------------------------------------------------------------------------+newSessions :: IO Sessions+newSessions =+ Sessions <$> newIORef 0 + <*> newIORef mempty++--------------------------------------------------------------------------------+packageOf :: Settings -> Request -> UUID -> Package+packageOf setts Request{..} uuid =+ Package { packageCmd = _requestCmd+ , packageCorrelation = uuid+ , packageData = _requestPayload+ , packageCred = s_credentials setts+ }++--------------------------------------------------------------------------------+data Pending =+ Pending { _pendingRequest :: !(Maybe Request)+ , _pendingSession :: !Session+ , _pendingRetries :: !Int+ , _pendingLastTry :: !NominalDiffTime+ , _pendingConnId :: !UUID+ }++--------------------------------------------------------------------------------+destroyPendingSession :: Sessions -> Pending -> EventStore ()+destroyPendingSession sessions Pending{..} =+ destroySession sessions _pendingSession++--------------------------------------------------------------------------------+type PendingRequests = HashMap UUID Pending++--------------------------------------------------------------------------------+data Awaiting+ = Awaiting !Session+ | AwaitingRequest !Session !Request++--------------------------------------------------------------------------------+type Awaitings = [Awaiting]++--------------------------------------------------------------------------------+rejectPending :: Exception e => Pending -> e -> EventStore ()+rejectPending Pending{..} = rejectSession _pendingSession++--------------------------------------------------------------------------------+applyResponse :: Registry -> Pending -> Package -> EventStore ()+applyResponse reg Pending{..} = resumeSession reg _pendingSession++--------------------------------------------------------------------------------+restartPending :: Registry -> Pending -> EventStore ()+restartPending reg Pending{..} = restartSession reg _pendingSession++--------------------------------------------------------------------------------+data Registry =+ Registry { _regConnRef :: ConnectionRef+ , _regPendings :: IORef PendingRequests+ , _regAwaitings :: IORef Awaitings+ , _stopwatch :: Stopwatch+ , _sessions :: Sessions+ }++--------------------------------------------------------------------------------+newRegistry :: ConnectionRef -> IO Registry+newRegistry ref =+ Registry ref <$> newIORef mempty+ <*> newIORef []+ <*> newStopwatch+ <*> newSessions++--------------------------------------------------------------------------------+scheduleAwait :: Registry -> Awaiting -> EventStore ()+scheduleAwait Registry{..} aw =+ atomicModifyIORef' _regAwaitings $ \stack ->+ (aw : stack, ())++--------------------------------------------------------------------------------+execute :: Registry -> Session -> EventStore ()+execute self@Registry{..} session@Session{..} =+ readIORef sessionStack >>= \case+ Resolved action -> loop action+ _ -> return ()+ where+ loop (MachineT m) =+ case m of+ Failed e -> do+ destroySession _sessions session+ rejectSession session e+ Retry -> do+ reinitSession session+ execute self session+ Proceed s ->+ case s 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 cmd payload -> do+ let req = Request { _requestCmd = cmd+ , _requestPayload = payload+ }+ atomicWriteIORef sessionStack (Required k)+ maybeConnection _regConnRef >>= \case+ Nothing -> scheduleAwait self (AwaitingRequest session req)+ Just conn -> issueRequest self session conn req++ 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++ traverse_ mkNewPending =<< maybeConnection _regConnRef++--------------------------------------------------------------------------------+issueRequest :: Registry+ -> Session+ -> Connection+ -> Request+ -> EventStore ()+issueRequest reg@Registry{..} session conn req = do+ uuid <- liftBase nextRandom+ pending <- createPending session _stopwatch (connectionId conn) (Just req)++ insertPending reg uuid pending+ setts <- getSettings+ enqueuePackage conn (packageOf setts req uuid)++--------------------------------------------------------------------------------+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++--------------------------------------------------------------------------------+insertPending :: Registry -> UUID -> Pending -> EventStore ()+insertPending Registry{..} key pending =+ atomicModifyIORef' _regPendings $ \pendings ->+ (insertMap key pending pendings, ())++--------------------------------------------------------------------------------+register :: Registry -> Operation a -> Callback a -> EventStore ()+register reg op cb = do+ session <- createSession (_sessions reg) op cb+ execute reg session++--------------------------------------------------------------------------------+abortPendingRequests :: Registry -> EventStore ()+abortPendingRequests Registry{..} = do+ m <- atomicModifyIORef' _regPendings $ \pendings -> (mempty, pendings)++ for_ m $ \p -> rejectPending p Aborted++--------------------------------------------------------------------------------+data Decision+ = Handled+ | Reconnect NodeEndPoints++--------------------------------------------------------------------------------+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++--------------------------------------------------------------------------------+executePending :: Registry -> Package -> Pending -> EventStore Decision+executePending reg@Registry{..} pkg@Package{..} p@Pending{..} =+ case packageCmd of+ cmd | cmd == badRequestCmd -> do+ let reason = packageDataAsText pkg++ 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++ 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++--------------------------------------------------------------------------------+-- | Occurs when an operation has been retried more than 's_operationRetry'.+data OperationMaxAttemptReached =+ OperationMaxAttemptReached UUID Command+ deriving (Show, Typeable)++--------------------------------------------------------------------------------+instance Exception OperationMaxAttemptReached++--------------------------------------------------------------------------------+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 setts req uuid+ pending =+ p { _pendingLastTry = elapsed+ , _pendingRetries = _pendingRetries p + 1+ , _pendingConnId = connectionId conn+ }+ nextReg = deleteMap key $ insertMap uuid pending reg++ enqueuePackage conn pkg+ return nextReg++ 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++--------------------------------------------------------------------------------+startAwaitings :: Registry -> EventStore ()+startAwaitings reg@Registry{..} = do+ awaitings <- atomicModifyIORef' _regAwaitings $ \stack ->+ ([], reverse stack)++ traverse_ starting awaitings+ where+ starting (Awaiting session) = execute reg session+ starting (AwaitingRequest session req) = do+ conn <- getConnection _regConnRef+ issueRequest reg session conn req++--------------------------------------------------------------------------------+maybeDecodeMessage :: Decode a => ByteString -> Maybe a+maybeDecodeMessage bytes =+ case runGet decodeMessage bytes of+ Right a -> Just a+ _ -> Nothing
− Database/EventStore/Internal/Manager/Subscription/Command.hs
@@ -1,103 +0,0 @@------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Manager.Subscription.Command--- Copyright : (C) 2016 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable-------------------------------------------------------------------------------------module Database.EventStore.Internal.Manager.Subscription.Command where-----------------------------------------------------------------------------------import ClassyPrelude-import Data.ProtocolBuffers-import Data.Serialize-----------------------------------------------------------------------------------import Database.EventStore.Internal.Command-import Database.EventStore.Internal.Manager.Subscription.Message-import Database.EventStore.Internal.Manager.Subscription.Types-import Database.EventStore.Internal.Types-----------------------------------------------------------------------------------data ServerMessage- = EventAppearedMsg !ResolvedEvent- | PersistentEventAppearedMsg !ResolvedEvent- | ConfirmationMsg !Int64 !(Maybe Int32)- | PersistentConfirmationMsg !Text !Int64 !(Maybe Int32)- | PersistentCreatedMsg CreatePersistentSubscriptionResult- | PersistentUpdatedMsg UpdatePersistentSubscriptionResult- | PersistentDeletedMsg DeletePersistentSubscriptionResult- | DroppedMsg SubDropReason- | BadRequestMsg !(Maybe Text)- | NotHandledMsg !NotHandledReason !(Maybe MasterInfo)- | NotAuthenticatedMsg !(Maybe Text)- | UnknownMsg !(Maybe Command)-----------------------------------------------------------------------------------toSubDropReason :: DropReason -> SubDropReason-toSubDropReason D_Unsubscribed = SubUnsubscribed-toSubDropReason D_NotFound = SubNotFound-toSubDropReason D_AccessDenied = SubAccessDenied-toSubDropReason D_PersistentSubscriptionDeleted = SubPersistDeleted-toSubDropReason D_SubscriberMaxCountReached = SubSubscriberMaxCountReached-----------------------------------------------------------------------------------decodeServerMessage :: Package -> ServerMessage-decodeServerMessage pkg = fromMaybe (UnknownMsg $ Just cmd) $ go cmd- where- cmd = packageCmd pkg- go 0xC2 = do- msg <- maybeDecodeMessage $ packageData pkg- let evt = newResolvedEventFromBuf $ getField $ streamResolvedEvent msg- return $ EventAppearedMsg evt- go 0xC7 = do- msg <- maybeDecodeMessage $ packageData pkg- let evt = newResolvedEvent $ getField $ psseaEvt msg- return $ PersistentEventAppearedMsg evt- go 0xC1 = do- msg <- maybeDecodeMessage $ packageData pkg- let lcp = getField $ subscribeLastCommitPos msg- len = getField $ subscribeLastEventNumber msg- return $ ConfirmationMsg lcp len- go 0xC6 = do- msg <- maybeDecodeMessage $ packageData pkg- let lcp = getField $ pscLastCommitPos msg- sid = getField $ pscId msg- len = getField $ pscLastEvtNumber msg- return $ PersistentConfirmationMsg sid lcp len- go 0xC9 =- fmap (PersistentCreatedMsg . getField . cpscResult)- $ maybeDecodeMessage- $ packageData pkg- go 0xCF =- fmap (PersistentUpdatedMsg . getField . upscResult)- $ maybeDecodeMessage- $ packageData pkg- go 0xCB =- fmap (PersistentDeletedMsg . getField . dpscResult)- $ maybeDecodeMessage- $ packageData pkg- go 0xC4 = do- msg <- maybeDecodeMessage $ packageData pkg- let reason = fromMaybe D_Unsubscribed $ getField $ dropReason msg- return $ DroppedMsg $ toSubDropReason reason- go 0xF0 = return $ BadRequestMsg $ packageDataAsText pkg- go 0xF4 = return $ NotAuthenticatedMsg $ packageDataAsText pkg- go 0xF1 = do- msg <- maybeDecodeMessage $ packageData pkg- let info = fmap masterInfo $ getField $ notHandledAdditionalInfo msg- reason = getField $ notHandledReason msg- return $ NotHandledMsg reason info-- go _ = Nothing-----------------------------------------------------------------------------------maybeDecodeMessage :: Decode a => ByteString -> Maybe a-maybeDecodeMessage bytes =- case runGet decodeMessage bytes of- Right a -> Just a- _ -> Nothing
− Database/EventStore/Internal/Manager/Subscription/Driver.hs
@@ -1,550 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Manager.Subscription.Driver--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Subscription model driver. It drivers the model accordingly depending on the--- 'Package' or commands submitted to it.----------------------------------------------------------------------------------module Database.EventStore.Internal.Manager.Subscription.Driver- ( SubDropReason(..)- , SubConnectEvent(..)- , PersistActionException(..)- , ConfirmedAction(..)- , NakAction(..)- , Driver- , newDriver- , submitPackage- , connectToStream- , connectToPersist- , createPersist- , updatePersist- , deletePersist- , ackPersist- , nakPersist- , unsubscribe- , abort- ) where-----------------------------------------------------------------------------------import Data.Int-import Data.Maybe-----------------------------------------------------------------------------------import ClassyPrelude hiding (group)-import Control.Monad.State-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.Generator-import Database.EventStore.Internal.Manager.Subscription.Command-import Database.EventStore.Internal.Manager.Subscription.Message-import Database.EventStore.Internal.Manager.Subscription.Model-import Database.EventStore.Internal.Manager.Subscription.Packages-import Database.EventStore.Internal.Manager.Subscription.Types-import Database.EventStore.Internal.Types------------------------------------------------------------------------------------- | Set of events that can occurs during a subscription lifetime.-data SubConnectEvent- = EventAppeared ResolvedEvent- -- ^ A wild event appeared !- | Dropped SubDropReason- -- ^ The subscription connection dropped.- | SubConfirmed Running- -- ^ Subscription connection is confirmed. It means that subscription can- -- receive events from the server.- | Unsubscribed------------------------------------------------------------------------------------- | Enumerates all persistent action exceptions.-data PersistActionException- = PersistActionFail- -- ^ The action failed.- | PersistActionAlreadyExist- -- ^ Happens when creating a persistent subscription on a stream with a- -- group name already taken.- | PersistActionDoesNotExist- -- ^ An operation tried to do something on a persistent subscription or a- -- stream that don't exist.- | PersistActionAccessDenied- -- ^ The current user is not allowed to operate on the supplied stream or- -- persistent subscription.- | PersistActionAborted- -- ^ That action has been aborted because the user shutdown the connection- -- to the server or the connection to the server is no longer possible.- deriving (Show, Typeable)-----------------------------------------------------------------------------------instance Exception PersistActionException------------------------------------------------------------------------------------- | Emitted when a persistent action has been carried out successfully.-data ConfirmedAction =- ConfirmedAction- { caId :: !UUID- -- ^ Action id.- , caGroup :: !Text- -- ^ Subscription group name.- , caStream :: !Text- -- ^ Stream name.- , caAction :: !PersistAction- -- ^ Persistent action type.- }------------------------------------------------------------------------------------- | Submits a 'Package' to a subscription driver. If the 'Package' was--- processed by the driver, it will return a final value and a new driver with--- its internal state updated accordingly.-submitPackage :: Package -> Driver r -> Maybe (r, Driver r)-submitPackage pkg (Driver k) = k (Pkg pkg)------------------------------------------------------------------------------------- | Starts a regular subscription connection. It returns the associated--- 'Package' and updates driver internal state.-connectToStream :: (SubConnectEvent -> r)- -> Text -- ^ Stream name.- -> Bool -- ^ Resolve Link TOS- -> Driver r- -> (Package, Driver r)-connectToStream c s t (Driver k) = k (Cmd $ ConnectReg c s t)------------------------------------------------------------------------------------- | Starts a persistent subscription connection. It returns the associated--- 'Package' and updates driver internal state.-connectToPersist :: (SubConnectEvent -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> Int32 -- ^ Buffer size.- -> Driver r- -> (Package, Driver r)-connectToPersist c g s b (Driver k) = k (Cmd $ ConnectPersist c g s b)------------------------------------------------------------------------------------- | Creates a persistent subscription. It returns the associated 'Package' and--- updates driver internal state.-createPersist :: (Either PersistActionException ConfirmedAction -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> PersistentSubscriptionSettings- -> Driver r- -> (Package, Driver r)-createPersist c g s ss (Driver k) =- k (Cmd $ ApplyPersistAction c g s (PersistCreate ss))------------------------------------------------------------------------------------- | Updates a persistent subscription. It returns the associated 'Package' and--- updates driver internal state.-updatePersist :: (Either PersistActionException ConfirmedAction -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> PersistentSubscriptionSettings- -> Driver r- -> (Package, Driver r)-updatePersist c g s ss (Driver k) =- k (Cmd $ ApplyPersistAction c g s (PersistUpdate ss))------------------------------------------------------------------------------------- | Deletes a persistent subscription. It returns the associated 'Package' and--- updates driver internal state.-deletePersist :: (Either PersistActionException ConfirmedAction -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> Driver r- -> (Package, Driver r)-deletePersist c g s (Driver k) =- k (Cmd $ ApplyPersistAction c g s PersistDelete)------------------------------------------------------------------------------------- | Given a persistent subscription, acknowledges a set of events have been--- successfully processed. It returns the associated 'Package' and updates--- driver internal state.-ackPersist :: r- -> Running- -> [UUID] -- ^ Event ids.- -> Driver r- -> (Package, Driver r)-ackPersist r i evts (Driver k) = k (Cmd $ PersistAck r i evts)------------------------------------------------------------------------------------- | Given a persistent subscription, indicates a set of event haven't been--- processed correctly. It returns the associated 'Package' and updates driver--- internal state.-nakPersist :: r- -> Running- -> NakAction- -> Maybe Text -- ^ Reason.- -> [UUID] -- ^ Event ids.- -> Driver r- -> (Package, Driver r)-nakPersist r i na mt evts (Driver k) =- k (Cmd $ PersistNak r i na mt evts)------------------------------------------------------------------------------------- | Unsubscribe from a subscription.-unsubscribe :: Running -> Driver r -> (Package, Driver r)-unsubscribe r (Driver k) = k (Cmd $ Unsubscribe r)------------------------------------------------------------------------------------- | Aborts every pending action.-abort :: Driver r -> [r]-abort (Driver k) = k Abort------------------------------------------------------------------------------------- EventStore result mappers:--- =========================--- EventStore protocol has several values that means the exact same thing. Those--- functions convert a specific EventStore to uniform result type common to all--- persistent actions.----------------------------------------------------------------------------------createRException :: CreatePersistentSubscriptionResult- -> Maybe PersistActionException-createRException CPS_Success = Nothing-createRException CPS_AlreadyExists = Just PersistActionAlreadyExist-createRException CPS_Fail = Just PersistActionFail-createRException CPS_AccessDenied = Just PersistActionAccessDenied-----------------------------------------------------------------------------------deleteRException :: DeletePersistentSubscriptionResult- -> Maybe PersistActionException-deleteRException DPS_Success = Nothing-deleteRException DPS_DoesNotExist = Just PersistActionDoesNotExist-deleteRException DPS_Fail = Just PersistActionFail-deleteRException DPS_AccessDenied = Just PersistActionAccessDenied-----------------------------------------------------------------------------------updateRException :: UpdatePersistentSubscriptionResult- -> Maybe PersistActionException-updateRException UPS_Success = Nothing-updateRException UPS_DoesNotExist = Just PersistActionDoesNotExist-updateRException UPS_Fail = Just PersistActionFail-updateRException UPS_AccessDenied = Just PersistActionAccessDenied---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Type of inputs handled by the 'Subscription' driver.-data In r a where- -- A command consists of receiving some parameters, updating the- -- 'Subscription' model accordingly and thus modifying driver internal- -- state.- Cmd :: Cmd r -> In r (Package, Driver r)- -- A 'Package' has been submitted to the 'Subscription' driver. If the- -- driver recognize that 'Package', it returns a final value and update- -- the driver internal state.- Pkg :: Package -> In r (Maybe (r, Driver r))- -- Aborts every pending action.- Abort :: In r [r]------------------------------------------------------------------------------------- | Set of commands handled by the driver.-data Cmd r- = ConnectReg (SubConnectEvent -> r) Text Bool- -- ^ Creates a regular 'Subscription' connection. When a 'SubConnectEvent'- -- has arrived, the driver will use the provided callback and emit a- -- final value. It holds a stream name and `Resolve Link TOS` setting.- | ConnectPersist (SubConnectEvent -> r)- Text- Text- Int32- -- ^ Creates a persistent 'Subscription' connection. When a- -- 'SubConnectEvent' has arrived, the driver will use the provided- -- callback and emit a final value. It holds a group name, stream- -- name and a buffer size.-- | Unsubscribe Running- -- ^ Unsubscribe from a subscription.-- | ApplyPersistAction (Either PersistActionException ConfirmedAction -> r)- Text- Text- PersistAction- -- ^ Creates a persistent action. Depending of the failure or the success- -- of that action, the driver will use the provided callback to emit a- -- final value. It hols a group name, a stream name and a persistent- -- action.-- | PersistAck r Running [UUID]- -- ^ Acks a set of Event 'UUID' to notify those events have been correctly- -- handled. It holds a 'Running' subscription and a set of `UUID`- -- representing event id. When the ack would be confirmed the driver- -- will return the supplied final value.- | PersistNak r- Running- NakAction- (Maybe Text)- [UUID]- -- ^ Naks a set of Event 'UUID' to notify those events haven't been- -- handled correctly. it holds a 'Running' subscription, a 'NakAction',- -- an optional reason and a set of event ids. When the nak would be- -- confirmed, the driver will return the provided final value.-----------------------------------------------------------------------------------cmdSubCallback :: Cmd r -> Maybe (SubConnectEvent -> r)-cmdSubCallback (ConnectReg k _ _) = Just k-cmdSubCallback (ConnectPersist k _ _ _) = Just k-cmdSubCallback _ = Nothing------------------------------------------------------------------------------------- | Driver internal state.-data Internal r =- Internal- { _model :: !Model- -- ^ Subscription model.- , _gen :: !Generator- -- ^ 'UUID' generator.- , _reg :: !(HashMap UUID (Cmd r))- -- ^ Holds ongoing commands. When stored, it means an action hasn't been- -- confirmed yet.- }-----------------------------------------------------------------------------------initInternal :: Generator -> Internal r-initInternal gen = Internal newModel gen mempty------------------------------------------------------------------------------------- | Subscription driver state machine.-newtype Driver r = Driver (forall a. In r a -> a)-----------------------------------------------------------------------------------newtype DriverM r m a = DriverM (ReaderT Settings (StateT (Internal r) m) a)- deriving ( Functor- , Applicative- , Monad- , MonadReader Settings- , MonadState (Internal r)- )-----------------------------------------------------------------------------------instance MonadTrans (DriverM r) where- lift m = DriverM $ lift $ lift m-----------------------------------------------------------------------------------noop :: DriverM r Maybe a-noop = lift Nothing-----------------------------------------------------------------------------------modelSubRunning :: UUID -> DriverM r Maybe Running-modelSubRunning uuid = do- model <- gets _model- lift $ querySubscription uuid $ model-----------------------------------------------------------------------------------modelSubConfirmed :: Monad m => UUID -> Meta -> DriverM r m ()-modelSubConfirmed uuid meta = do- model <- gets _model- let nxt = confirmedSubscription uuid meta model- modify $ \s -> s { _model = nxt }-----------------------------------------------------------------------------------modelActionConfirmed :: Monad m => UUID -> DriverM r m ()-modelActionConfirmed uuid =- modify $ \s -> s { _model = confirmedAction uuid $ _model s }-----------------------------------------------------------------------------------modelUnsubscribed :: UUID -> DriverM r Maybe ()-modelUnsubscribed uuid = do- run <- modelSubRunning uuid- model <- gets _model- modify $ \s -> s { _model = unsubscribed run model }-----------------------------------------------------------------------------------registerDelete :: Monad m => UUID -> DriverM r m ()-registerDelete uuid = do- reg <- gets _reg- let nxtR = deleteMap uuid reg- modify $ \s -> s { _reg = nxtR }-----------------------------------------------------------------------------------registerAdd :: Monad m => UUID -> Cmd r -> DriverM r m ()-registerAdd uuid cmd = do- reg <- gets _reg- modify $ \s -> s { _reg = insertMap uuid cmd reg }-----------------------------------------------------------------------------------modelPersistentAction :: UUID -> DriverM r Maybe PendingAction-modelPersistentAction uuid = do- model <- gets _model- lift $ queryPersistentAction uuid model-----------------------------------------------------------------------------------freshUUID :: Monad m => DriverM r m UUID-freshUUID = do- (uuid, nxtG) <- gets (nextUUID . _gen)- modify $ \s -> s { _gen = nxtG }- return uuid-----------------------------------------------------------------------------------modelConnectReg :: Monad m => Text -> Bool -> DriverM r m UUID-modelConnectReg stream tos = do- uuid <- freshUUID- model <- gets _model- modify $ \s -> s { _model = connectReg stream tos uuid model }- return uuid-----------------------------------------------------------------------------------modelConnectPersist :: Monad m => Text -> Text -> Int32 -> DriverM r m UUID-modelConnectPersist group name batch = do- uuid <- freshUUID- model <- gets _model- modify $ \s -> s { _model = connectPersist group name batch uuid model }- return uuid-----------------------------------------------------------------------------------modelPersistAction :: Monad m- => Text- -> Text- -> PersistAction- -> DriverM r m UUID-modelPersistAction group name action = do- uuid <- freshUUID- model <- gets _model- modify $ \s -> s { _model = persistAction group name uuid action model }- return uuid-----------------------------------------------------------------------------------runDriverM :: Monad m- => Settings- -> Internal r- -> DriverM r m a- -> m (a, Driver r)-runDriverM setts st (DriverM m) = do- (a, nxtSt) <- runStateT (runReaderT m setts) st- return (a, Driver $ handleDriver setts nxtSt)-----------------------------------------------------------------------------------runDriver :: Settings- -> Internal r- -> DriverM r Identity a- -> (a, Driver r)-runDriver setts st action = runIdentity $ runDriverM setts st action------------------------------------------------------------------------------------- Driver main state machine.-handleDriver :: Settings -> Internal r -> In r a -> a-handleDriver setts st (Pkg pkg) = do- let corrId = packageCorrelation pkg- cmd <- lookup corrId $ _reg st- let action = handleMsg corrId cmd $ decodeServerMessage pkg- runDriverM setts st action-handleDriver setts st (Cmd cmd) =- let action = handleCmd cmd in- runDriver setts st action-handleDriver _ st Abort = (fmap snd $ mapToList $ _reg st) >>= _F- where- _F (ConnectReg k _ _) = [k $ Dropped SubAborted]- _F (ConnectPersist k _ _ _) = [k $ Dropped SubAborted]- _F (ApplyPersistAction k _ _ _) = [k $ Left PersistActionAborted]- _F _ = []------------------------------------------------------------------------------------- | Handles 'Package's coming from the server.-handleMsg :: UUID -> Cmd r -> ServerMessage -> DriverM r Maybe r-handleMsg corrId = go- where- go (ConnectReg k _ _) (EventAppearedMsg evt) = do- _ <- modelSubRunning corrId- return $ k $ EventAppeared evt- go (ConnectPersist k _ _ _) (PersistentEventAppearedMsg evt) = do- _ <- modelSubRunning corrId- return $ k $ EventAppeared evt- go (ConnectReg k _ _) (ConfirmationMsg lcp len) = do- let meta = RegularMeta lcp len- modelSubConfirmed corrId meta- run <- modelSubRunning corrId- return $ k $ SubConfirmed run- go (ConnectPersist k _ _ _) (PersistentConfirmationMsg sid lcp len) = do- let meta = PersistMeta sid lcp len- modelSubConfirmed corrId meta- run <- modelSubRunning corrId- return $ k $ SubConfirmed run- go cmd (PersistentCreatedMsg res) =- confirmPAction cmd $ createRException res- go cmd (PersistentUpdatedMsg res) =- confirmPAction cmd $ updateRException res- go cmd (PersistentDeletedMsg res) =- confirmPAction cmd $ deleteRException res- go cmd (DroppedMsg reason) = do- modelUnsubscribed corrId- registerDelete corrId- let evt =- case reason of- SubUnsubscribed -> Unsubscribed- _ -> Dropped reason- k <- lift $ cmdSubCallback cmd- return $ k evt- go cmd (BadRequestMsg msg) =- go cmd (DroppedMsg $ SubServerError msg)- go cmd (NotAuthenticatedMsg msg) =- go cmd (DroppedMsg $ SubNotAuthenticated msg)- go cmd (NotHandledMsg reason info) =- go cmd (DroppedMsg $ SubNotHandled reason info)- go cmd (UnknownMsg pkgCmdM) = do- k <- lift $ cmdSubCallback cmd- let msgM = fmap (\c -> "unknown command: " <> tshow c) pkgCmdM- return $ k $ Dropped $ SubServerError msgM- go cmd _ = do- k <- lift $ cmdSubCallback cmd- let msg = "Logic error in Subscription Driver (the impossible happened)"- return $ k $ Dropped $ SubClientError msg-- confirmPAction :: Cmd r- -> Maybe PersistActionException- -> DriverM r Maybe r- confirmPAction (ApplyPersistAction k g n c) em = do- _ <- modelPersistentAction corrId- modelActionConfirmed corrId- registerDelete corrId- let evt = ConfirmedAction corrId g n c- case em of- Just e -> return $ k $ Left e- Nothing -> return $ k $ Right evt- confirmPAction _ _ = noop------------------------------------------------------------------------------------- | Handles commands coming from the user.-handleCmd :: Monad m => Cmd r -> DriverM r m Package-handleCmd cmd@(ConnectReg _ s tos) = do- setts <- ask- uuid <- modelConnectReg s tos- registerAdd uuid cmd- return $ createConnectRegularPackage setts uuid s tos-handleCmd cmd@(ConnectPersist _ gn n b) = do- setts <- ask- uuid <- modelConnectPersist gn n b- registerAdd uuid cmd- return $ createConnectPersistPackage setts uuid gn n b-handleCmd (Unsubscribe r) = do- setts <- ask- return $ createUnsubscribePackage setts $ runningUUID r-handleCmd cmd@(ApplyPersistAction _ gn n a) = do- setts <- ask- uuid <- modelPersistAction gn n a- registerAdd uuid cmd- return $ createPersistActionPackage setts uuid gn n a-handleCmd (PersistAck _ run evts) = do- setts <- ask- let RunningPersist _ _ _ _ sid _ _ = run- uuid = runningUUID run- return $ createAckPackage setts uuid sid evts-handleCmd (PersistNak _ run na r evts) = do- setts <- ask- let RunningPersist _ _ _ _ sid _ _ = run- uuid = runningUUID run- return $ createNakPackage setts uuid sid na r evts------------------------------------------------------------------------------------- | Creates a new subscription 'Driver' state machine.-newDriver :: forall r. Settings -> Generator -> Driver r-newDriver setts gen = Driver $ handleDriver setts (initInternal gen)
− Database/EventStore/Internal/Manager/Subscription/Message.hs
@@ -1,408 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}-{-# OPTIONS_GHC -fcontext-stack=26 #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Manager.Subscription.Message--- 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.Manager.Subscription.Message where-----------------------------------------------------------------------------------import Data.Int-----------------------------------------------------------------------------------import ClassyPrelude hiding (group)-import Data.DotNet.TimeSpan-import Data.ProtocolBuffers-----------------------------------------------------------------------------------import Database.EventStore.Internal.Types------------------------------------------------------------------------------------- | Stream subscription connection request.-data SubscribeToStream- = SubscribeToStream- { subscribeStreamId :: Required 1 (Value Text)- , subscribeResolveLinkTos :: Required 2 (Value Bool)- }- deriving (Generic, Show)-----------------------------------------------------------------------------------instance Encode SubscribeToStream------------------------------------------------------------------------------------- | 'SubscribeToStream' smart constructor.-subscribeToStream :: Text -> Bool -> SubscribeToStream-subscribeToStream stream_id res_link_tos =- SubscribeToStream- { subscribeStreamId = putField stream_id- , subscribeResolveLinkTos = putField res_link_tos- }------------------------------------------------------------------------------------- | Stream subscription connection response.-data SubscriptionConfirmation- = SubscriptionConfirmation- { subscribeLastCommitPos :: Required 1 (Value Int64)- , subscribeLastEventNumber :: Optional 2 (Value Int32)- }- deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode SubscriptionConfirmation------------------------------------------------------------------------------------- | Serialized event sent by the server when a new event has been appended to a--- stream.-data StreamEventAppeared- = StreamEventAppeared- { streamResolvedEvent :: Required 1 (Message ResolvedEventBuf) }- deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode StreamEventAppeared------------------------------------------------------------------------------------- | Represents the reason subscription drop happened.-data DropReason- = D_Unsubscribed- | D_AccessDenied- | D_NotFound- | D_PersistentSubscriptionDeleted- | D_SubscriberMaxCountReached- deriving (Enum, Eq, Show)------------------------------------------------------------------------------------- | A message sent by the server when a subscription has been dropped.-data SubscriptionDropped- = SubscriptionDropped- { dropReason :: Optional 1 (Enumeration DropReason) }- deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode SubscriptionDropped------------------------------------------------------------------------------------- | A message sent to the server to indicate the user asked to end a--- subscription.-data UnsubscribeFromStream = UnsubscribeFromStream deriving (Generic, Show)-----------------------------------------------------------------------------------instance Encode UnsubscribeFromStream------------------------------------------------------------------------------------- | Create persistent subscription request.-data CreatePersistentSubscription =- CreatePersistentSubscription- { cpsGroupName :: Required 1 (Value Text)- , cpsStreamId :: Required 2 (Value Text)- , cpsResolveLinkTos :: Required 3 (Value Bool)- , cpsStartFrom :: Required 4 (Value Int32)- , cpsMsgTimeout :: Required 5 (Value Int32)- , cpsRecordStats :: Required 6 (Value Bool)- , cpsLiveBufSize :: Required 7 (Value Int32)- , cpsReadBatchSize :: Required 8 (Value Int32)- , cpsBufSize :: Required 9 (Value Int32)- , cpsMaxRetryCount :: Required 10 (Value Int32)- , cpsPreferRoundRobin :: Required 11 (Value Bool)- , cpsChkPtAfterTime :: Required 12 (Value Int32)- , cpsChkPtMaxCount :: Required 13 (Value Int32)- , cpsChkPtMinCount :: Required 14 (Value Int32)- , cpsSubMaxCount :: Required 15 (Value Int32)- , cpsNamedConsStrategy :: Optional 16 (Value Text)- } deriving (Generic, Show)------------------------------------------------------------------------------------- | 'CreatePersistentSubscription' smart constructor.-_createPersistentSubscription :: Text- -> Text- -> PersistentSubscriptionSettings- -> CreatePersistentSubscription-_createPersistentSubscription group stream sett =- CreatePersistentSubscription- { cpsGroupName = putField group- , cpsStreamId = putField stream- , cpsResolveLinkTos = putField $ psSettingsResolveLinkTos sett- , cpsStartFrom = putField $ psSettingsStartFrom sett- , cpsMsgTimeout = putField- . fromIntegral- . (truncate :: Double -> Int64)- . totalMillis- $ psSettingsMsgTimeout sett- , cpsRecordStats = putField $ psSettingsExtraStats sett- , cpsLiveBufSize = putField $ psSettingsLiveBufSize sett- , cpsReadBatchSize = putField $ psSettingsReadBatchSize sett- , cpsBufSize = putField $ psSettingsHistoryBufSize sett- , cpsMaxRetryCount = putField $ psSettingsMaxRetryCount sett- , cpsPreferRoundRobin = putField False- , cpsChkPtAfterTime = putField- . fromIntegral- . (truncate :: Double -> Int64)- . totalMillis- $ psSettingsCheckPointAfter sett- , cpsChkPtMaxCount = putField $ psSettingsMaxCheckPointCount sett- , cpsChkPtMinCount = putField $ psSettingsMinCheckPointCount sett- , cpsSubMaxCount = putField $ psSettingsMaxSubsCount sett- , cpsNamedConsStrategy = putField $ Just strText- }- where- strText = strategyText $ psSettingsNamedConsumerStrategy sett-----------------------------------------------------------------------------------instance Encode CreatePersistentSubscription------------------------------------------------------------------------------------- | Create persistent subscription outcome.-data CreatePersistentSubscriptionResult- = CPS_Success- | CPS_AlreadyExists- | CPS_Fail- | CPS_AccessDenied- deriving (Enum, Eq, Show)------------------------------------------------------------------------------------- | Create persistent subscription response.-data CreatePersistentSubscriptionCompleted =- CreatePersistentSubscriptionCompleted- { cpscResult :: Required 1 (Enumeration CreatePersistentSubscriptionResult)- , cpscReason :: Optional 2 (Value Text)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode CreatePersistentSubscriptionCompleted------------------------------------------------------------------------------------- | Delete persistent subscription request.-data DeletePersistentSubscription =- DeletePersistentSubscription- { dpsGroupName :: Required 1 (Value Text)- , dpsStreamId :: Required 2 (Value Text)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Encode DeletePersistentSubscription------------------------------------------------------------------------------------- | 'DeletePersistentSubscription' smart construction.-_deletePersistentSubscription :: Text -> Text -> DeletePersistentSubscription-_deletePersistentSubscription group_name stream_id =- DeletePersistentSubscription- { dpsGroupName = putField group_name- , dpsStreamId = putField stream_id- }------------------------------------------------------------------------------------- | Delete persistent subscription outcome.-data DeletePersistentSubscriptionResult- = DPS_Success- | DPS_DoesNotExist- | DPS_Fail- | DPS_AccessDenied- deriving (Enum, Eq, Show)------------------------------------------------------------------------------------- | Delete persistent subscription response.-data DeletePersistentSubscriptionCompleted =- DeletePersistentSubscriptionCompleted- { dpscResult :: Required 1 (Enumeration DeletePersistentSubscriptionResult)- , dpscReason :: Optional 2 (Value Text)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode DeletePersistentSubscriptionCompleted------------------------------------------------------------------------------------- | Update persistent subscription request.-data UpdatePersistentSubscription =- UpdatePersistentSubscription- { upsGroupName :: Required 1 (Value Text)- , upsStreamId :: Required 2 (Value Text)- , upsResolveLinkTos :: Required 3 (Value Bool)- , upsStartFrom :: Required 4 (Value Int32)- , upsMsgTimeout :: Required 5 (Value Int32)- , upsRecordStats :: Required 6 (Value Bool)- , upsLiveBufSize :: Required 7 (Value Int32)- , upsReadBatchSize :: Required 8 (Value Int32)- , upsBufSize :: Required 9 (Value Int32)- , upsMaxRetryCount :: Required 10 (Value Int32)- , upsPreferRoundRobin :: Required 11 (Value Bool)- , upsChkPtAfterTime :: Required 12 (Value Int32)- , upsChkPtMaxCount :: Required 13 (Value Int32)- , upsChkPtMinCount :: Required 14 (Value Int32)- , upsSubMaxCount :: Required 15 (Value Int32)- , upsNamedConsStrategy :: Optional 16 (Value Text)- } deriving (Generic, Show)------------------------------------------------------------------------------------- | 'UpdatePersistentSubscription' smart constructor.-_updatePersistentSubscription :: Text- -> Text- -> PersistentSubscriptionSettings- -> UpdatePersistentSubscription-_updatePersistentSubscription group stream sett =- UpdatePersistentSubscription- { upsGroupName = putField group- , upsStreamId = putField stream- , upsResolveLinkTos = putField $ psSettingsResolveLinkTos sett- , upsStartFrom = putField $ psSettingsStartFrom sett- , upsMsgTimeout = putField- . fromIntegral- . (truncate :: Double -> Int64)- . totalMillis- $ psSettingsMsgTimeout sett- , upsRecordStats = putField $ psSettingsExtraStats sett- , upsLiveBufSize = putField $ psSettingsLiveBufSize sett- , upsReadBatchSize = putField $ psSettingsReadBatchSize sett- , upsBufSize = putField $ psSettingsHistoryBufSize sett- , upsMaxRetryCount = putField $ psSettingsMaxRetryCount sett- , upsPreferRoundRobin = putField False- , upsChkPtAfterTime = putField- . fromIntegral- . (truncate :: Double -> Int64)- . totalMillis- $ psSettingsCheckPointAfter sett- , upsChkPtMaxCount = putField $ psSettingsMaxCheckPointCount sett- , upsChkPtMinCount = putField $ psSettingsMinCheckPointCount sett- , upsSubMaxCount = putField $ psSettingsMaxSubsCount sett- , upsNamedConsStrategy = putField $ Just strText- }- where- strText = strategyText $ psSettingsNamedConsumerStrategy sett-----------------------------------------------------------------------------------instance Encode UpdatePersistentSubscription------------------------------------------------------------------------------------- | Update persistent subscription outcome.-data UpdatePersistentSubscriptionResult- = UPS_Success- | UPS_DoesNotExist- | UPS_Fail- | UPS_AccessDenied- deriving (Enum, Eq, Show)------------------------------------------------------------------------------------- | Update persistent subscription response.-data UpdatePersistentSubscriptionCompleted =- UpdatePersistentSubscriptionCompleted- { upscResult :: Required 1 (Enumeration UpdatePersistentSubscriptionResult)- , upscReason :: Optional 2 (Value Text)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode UpdatePersistentSubscriptionCompleted------------------------------------------------------------------------------------- | Connect to a persistent subscription request.-data ConnectToPersistentSubscription =- ConnectToPersistentSubscription- { ctsId :: Required 1 (Value Text)- , ctsStreamId :: Required 2 (Value Text)- , ctsAllowedInFlightMsgs :: Required 3 (Value Int32)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Encode ConnectToPersistentSubscription------------------------------------------------------------------------------------- | 'ConnectToPersistentSubscription' smart constructor.-_connectToPersistentSubscription :: Text- -> Text- -> Int32- -> ConnectToPersistentSubscription-_connectToPersistentSubscription sub_id stream_id all_fly_msgs =- ConnectToPersistentSubscription- { ctsId = putField sub_id- , ctsStreamId = putField stream_id- , ctsAllowedInFlightMsgs = putField all_fly_msgs- }------------------------------------------------------------------------------------- | Ack processed events request.-data PersistentSubscriptionAckEvents =- PersistentSubscriptionAckEvents- { psaeId :: Required 1 (Value Text)- , psaeProcessedEvtIds :: Repeated 2 (Value ByteString)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Encode PersistentSubscriptionAckEvents------------------------------------------------------------------------------------- | 'PersistentSubscriptionAckEvents' smart constructor.-persistentSubscriptionAckEvents :: Text- -> [ByteString]- -> PersistentSubscriptionAckEvents-persistentSubscriptionAckEvents sub_id evt_ids =- PersistentSubscriptionAckEvents- { psaeId = putField sub_id- , psaeProcessedEvtIds = putField evt_ids- }------------------------------------------------------------------------------------- | Gathers every possible Nak actions.-data NakAction- = NA_Unknown- | NA_Park- | NA_Retry- | NA_Skip- | NA_Stop- deriving (Enum, Eq, Show)------------------------------------------------------------------------------------- | Nak processed events request.-data PersistentSubscriptionNakEvents =- PersistentSubscriptionNakEvents- { psneId :: Required 1 (Value Text)- , psneProcessedEvtIds :: Repeated 2 (Value ByteString)- , psneMsg :: Optional 3 (Value Text)- , psneAction :: Required 4 (Enumeration NakAction)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Encode PersistentSubscriptionNakEvents------------------------------------------------------------------------------------- | 'PersistentSubscriptionNakEvents' smart constructor.-persistentSubscriptionNakEvents :: Text- -> [ByteString]- -> Maybe Text- -> NakAction- -> PersistentSubscriptionNakEvents-persistentSubscriptionNakEvents sub_id evt_ids msg action =- PersistentSubscriptionNakEvents- { psneId = putField sub_id- , psneProcessedEvtIds = putField evt_ids- , psneMsg = putField msg- , psneAction = putField action- }------------------------------------------------------------------------------------- | Connection to persistent subscription response.-data PersistentSubscriptionConfirmation =- PersistentSubscriptionConfirmation- { pscLastCommitPos :: Required 1 (Value Int64)- , pscId :: Required 2 (Value Text)- , pscLastEvtNumber :: Optional 3 (Value Int32)- } deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode PersistentSubscriptionConfirmation------------------------------------------------------------------------------------- | Avalaible event sent by the server in the context of a persistent--- subscription..-data PersistentSubscriptionStreamEventAppeared =- PersistentSubscriptionStreamEventAppeared- { psseaEvt :: Required 1 (Message ResolvedIndexedEvent) }- deriving (Generic, Show)-----------------------------------------------------------------------------------instance Decode PersistentSubscriptionStreamEventAppeared
− Database/EventStore/Internal/Manager/Subscription/Model.hs
@@ -1,317 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE RecordWildCards #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Manager.Subscription.Model--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Main Subscription bookkeeping structure.----------------------------------------------------------------------------------module Database.EventStore.Internal.Manager.Subscription.Model- ( PersistAction(..)- , PendingAction(..)- , Running(..)- , Meta(..)- , Model- , runningUUID- , runningLastEventNumber- , runningLastCommitPosition- , querySubscription- , queryPersistentAction- , confirmedSubscription- , confirmedAction- , newModel- , unsubscribed- , connectReg- , connectPersist- , persistAction- ) where-----------------------------------------------------------------------------------import Data.Int-----------------------------------------------------------------------------------import ClassyPrelude-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.Types------------------------------------------------------------------------------------- | Type of persistent action.-data PersistAction- = PersistCreate PersistentSubscriptionSettings- | PersistUpdate PersistentSubscriptionSettings- | PersistDelete------------------------------------------------------------------------------------- | Represents an persistent action that hasn't been completed yet.-data PendingAction =- PendingAction- { _paGroup :: !Text- , _paStream :: !Text- , _paTpe :: !PersistAction- }-----------------------------------------------------------------------------------type Register a = HashMap UUID a------------------------------------------------------------------------------------- | Represents a 'Subscription' which is about to be confirmed.-data Pending- = PendingReg Text Bool- -- ^ Related to regular subscription. In order of appearance:- --- -- * Stream name.- --- -- * Resolve Link TOS.- | PendingPersist Text Text Int32- -- ^ Related to persistent subscription. In order of appearance:- --- -- * Group name.- --- -- * Stream name.- --- -- * Buffer size.- deriving Show------------------------------------------------------------------------------------- | Represents a running subscription. Gathers useful information.-data Running- = RunningReg UUID Text Bool Int64 (Maybe Int32)- -- ^ Related regular subscription. In order of appearance:- --- -- * Subscription id.- --- -- * Stream name.- --- -- * Resolve Link TOS.- --- -- * Last commit position.- --- -- * Last event number.- | RunningPersist UUID Text Text Int32 Text Int64 (Maybe Int32)- -- ^ Related to persistent subscription. In order of appearance:- --- -- * Subscription id.- --- -- * Group name.- --- -- * Stream name.- --- -- * Buffer size.- --- -- * Persistence subscription id.- --- -- * Last commit position.- --- -- * Last event number.- deriving Show------------------------------------------------------------------------------------- | Gets the event number of a running subscription.-runningLastEventNumber :: Running -> Maybe Int32-runningLastEventNumber (RunningReg _ _ _ _ i) = i-runningLastEventNumber (RunningPersist _ _ _ _ _ _ i) = i------------------------------------------------------------------------------------- | Gets the commit position of a running subscription.-runningLastCommitPosition :: Running -> Int64-runningLastCommitPosition (RunningReg _ _ _ i _) = i-runningLastCommitPosition (RunningPersist _ _ _ _ _ i _) = i------------------------------------------------------------------------------------- | Gets the 'UUID' of a running subscription.-runningUUID :: Running -> UUID-runningUUID (RunningReg i _ _ _ _) = i-runningUUID (RunningPersist i _ _ _ _ _ _) = i------------------------------------------------------------------------------------- | Type of requests handled by the model.-data Request a where- -- Read request.- Query :: Query a -> Request a- -- Write request.- Execute :: Action -> Request Model------------------------------------------------------------------------------------- | Set of a piece of information we can query from the 'Subscription' model.-data Query a where- -- Query a running 'Subscription'.- QuerySub :: UUID -> Query (Maybe Running)- -- Query a pending persistent action.- QueryAction :: UUID -> Query (Maybe PendingAction)------------------------------------------------------------------------------------- | Set of actions handled by the 'Subscription' model.-data Action- = Connect UUID Connect- -- ^ Subscription connection.- | Confirmed Confirmed- -- ^ Subscription action confirmation.- | Unsubscribed UUID- -- ^ Subscription no longer exist.- | PersistAction Text Text UUID PersistAction- -- ^ Add a new persist action.------------------------------------------------------------------------------------- | Subscription connection information.-data Connect- = ConnectReg Text Bool- -- | |---- Resolve TOS link.- -- |--------- Stream name.- | ConnectPersist Text Text Int32- -- | | |---- Buffer size.- -- | |--------- Stream name.- -- |-------------- Group name.------------------------------------------------------------------------------------- | Information related to a confirmed 'Subscription'.-data Meta- = RegularMeta Int64 (Maybe Int32)- -- | |------------- Last commit position.- -- |------------------- Last event number.- | PersistMeta Text Int64 (Maybe Int32)- -- | | |------------- Subscription Id.- -- | |------------------- Last commit position.- -- |------------------------ Last event number.------------------------------------------------------------------------------------- | Subscription action confirmation.-data Confirmed- = ConfirmedConnection UUID Meta- -- ^ Confirms a 'Subscription' connection has handled successfully.- | ConfirmedPersistAction UUID- -- ^ Confirms a persist action has been handled successfully.------------------------------------------------------------------------------------- | Retrieves a running 'Subscription'.-querySubscription :: UUID -> Model -> Maybe Running-querySubscription u (Model k) = k $ Query $ QuerySub u------------------------------------------------------------------------------------- | Retrieves an ongoing persistent action.-queryPersistentAction :: UUID -> Model -> Maybe PendingAction-queryPersistentAction u (Model k) = k $ Query $ QueryAction u------------------------------------------------------------------------------------- | Registers a regular 'Subscription' request.-connectReg :: Text -> Bool -> UUID -> Model -> Model-connectReg n t u (Model k) = k $ Execute $ Connect u (ConnectReg n t)------------------------------------------------------------------------------------- | Registers a persistent 'Subscription' request.-connectPersist :: Text -> Text -> Int32 -> UUID -> Model -> Model-connectPersist g n b u (Model k) =- k $ Execute $ Connect u (ConnectPersist g n b)------------------------------------------------------------------------------------- | Registers a persistent action.-persistAction :: Text -> Text -> UUID -> PersistAction -> Model -> Model-persistAction g n u a (Model k) = k $ Execute $ PersistAction g n u a------------------------------------------------------------------------------------- | Confirms a subscription.-confirmedSubscription :: UUID -> Meta -> Model -> Model-confirmedSubscription u m (Model k) =- k $ Execute $ Confirmed $ ConfirmedConnection u m------------------------------------------------------------------------------------- | Confirms a persistent action. It doesn't assume if the action went well.-confirmedAction :: UUID -> Model -> Model-confirmedAction u (Model k) = k $ Execute $ Confirmed $ ConfirmedPersistAction u------------------------------------------------------------------------------------- | Remove a 'Subscription'.-unsubscribed :: Running -> Model -> Model-unsubscribed r (Model k) = k $ Execute $ Unsubscribed $ runningUUID r------------------------------------------------------------------------------------- | 'Subscription' model internal state.-data State =- State- { _stPending :: !(Register Pending)- -- ^ Holds all pending 'Subscription's- , _stRunning :: !(Register Running)- -- ^ Holds all 'Subscription's that are currently running.- , _stAction :: !(Register PendingAction)- -- ^ Holds all pending persistent actions.- }-----------------------------------------------------------------------------------emptyState :: State-emptyState = State mempty mempty mempty------------------------------------------------------------------------------------- | Subscription operations state machine. Keeps every information related to--- subscription updated.-newtype Model = Model (forall a. Request a -> a)------------------------------------------------------------------------------------- | Creates a new 'Subscription' model.-newModel :: Model-newModel = Model $ modelHandle emptyState------------------------------------------------------------------------------------- | Main model handler.-modelHandle :: State -> Request a -> a-modelHandle s (Execute e) =- case e of- Connect u c ->- case c of- ConnectReg n tos ->- let p = PendingReg n tos- nxt_ps = insertMap u p $ _stPending s- nxt_s = s { _stPending = nxt_ps } in- Model $ modelHandle nxt_s- ConnectPersist g n b ->- let p = PendingPersist g n b- nxt_ps = insertMap u p $ _stPending s- nxt_s = s { _stPending = nxt_ps } in- Model $ modelHandle nxt_s- Confirmed c ->- case c of- ConfirmedConnection u tpe ->- case tpe of- RegularMeta lc le ->- case lookup u $ _stPending s of- Just (PendingReg n tos) ->- let r = RunningReg u n tos lc le- nxt_rs = insertMap u r $ _stRunning s- nxt_s = s { _stRunning = nxt_rs } in- Model $ modelHandle nxt_s- _ -> Model $ modelHandle s- PersistMeta sb lc le ->- case lookup u $ _stPending s of- Just (PendingPersist g n b) ->- let r = RunningPersist u g n b sb lc le- nxt_rs = insertMap u r $ _stRunning s- nxt_s = s { _stRunning = nxt_rs } in- Model $ modelHandle nxt_s- _ -> Model $ modelHandle s- ConfirmedPersistAction u ->- case lookup u $ _stAction s of- Just (PendingAction{}) ->- let nxt_as = deleteMap u $ _stAction s- nxt_s = s { _stAction = nxt_as } in- Model $ modelHandle nxt_s- _ -> Model $ modelHandle s- Unsubscribed u ->- let nxt_ps = deleteMap u $ _stRunning s- nxt_s = s { _stRunning = nxt_ps } in- Model $ modelHandle nxt_s- PersistAction g n u t ->- let a = PendingAction g n t- nxt_as = insertMap u a $ _stAction s- nxt_s = s { _stAction = nxt_as } in- Model $ modelHandle nxt_s-modelHandle s (Query q) =- case q of- QuerySub u -> lookup u $ _stRunning s- QueryAction u -> lookup u $ _stAction s
− Database/EventStore/Internal/Manager/Subscription/Packages.hs
@@ -1,133 +0,0 @@-{-# LANGUAGE RecordWildCards #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Manager.Subscription.Packages--- 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.Manager.Subscription.Packages where-----------------------------------------------------------------------------------import Data.Int-----------------------------------------------------------------------------------import ClassyPrelude-import Data.ProtocolBuffers-import Data.Serialize-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.Manager.Subscription.Message-import Database.EventStore.Internal.Manager.Subscription.Model-import Database.EventStore.Internal.Types------------------------------------------------------------------------------------- | Creates a regular subscription connection 'Package'.-createConnectRegularPackage :: Settings -> UUID -> Text -> Bool -> Package-createConnectRegularPackage Settings{..} uuid stream tos =- Package- { packageCmd = 0xC0- , packageCorrelation = uuid- , packageData = runPut $ encodeMessage msg- , packageCred = s_credentials- }- where- msg = subscribeToStream stream tos------------------------------------------------------------------------------------- | Creates a persistent subscription connection 'Package'.-createConnectPersistPackage :: Settings- -> UUID- -> Text- -> Text- -> Int32- -> Package-createConnectPersistPackage Settings{..} uuid grp stream bufSize =- Package- { packageCmd = 0xC5- , packageCorrelation = uuid- , packageData = runPut $ encodeMessage msg- , packageCred = s_credentials- }- where- msg = _connectToPersistentSubscription grp stream bufSize------------------------------------------------------------------------------------- | Creates a persistent subscription 'Package'.-createPersistActionPackage :: Settings- -> UUID- -> Text- -> Text- -> PersistAction- -> Package-createPersistActionPackage Settings{..} u grp strm tpe =- Package- { packageCmd = cmd- , packageCorrelation = u- , packageData = runPut msg- , packageCred = s_credentials- }- where- msg =- case tpe of- PersistCreate sett ->- encodeMessage $ _createPersistentSubscription grp strm sett- PersistUpdate sett ->- encodeMessage $ _updatePersistentSubscription grp strm sett- PersistDelete ->- encodeMessage $ _deletePersistentSubscription grp strm- cmd =- case tpe of- PersistCreate _ -> 0xC8- PersistUpdate _ -> 0xCE- PersistDelete -> 0xCA------------------------------------------------------------------------------------- | Creates Ack 'Package'.-createAckPackage :: Settings -> UUID -> Text -> [UUID] -> Package-createAckPackage Settings{..} corr sid eids =- Package- { packageCmd = 0xCC- , packageCorrelation = corr- , packageData = runPut $ encodeMessage msg- , packageCred = s_credentials- }- where- bytes = fmap (toStrict . toByteString) eids- msg = persistentSubscriptionAckEvents sid bytes------------------------------------------------------------------------------------- | Create Nak 'Package'.-createNakPackage :: Settings- -> UUID- -> Text- -> NakAction- -> Maybe Text- -> [UUID]- -> Package-createNakPackage Settings{..} corr sid act txt eids =- Package- { packageCmd = 0xCD- , packageCorrelation = corr- , packageData = runPut $ encodeMessage msg- , packageCred = s_credentials- }- where- bytes = fmap (toStrict . toByteString) eids- msg = persistentSubscriptionNakEvents sid bytes txt act------------------------------------------------------------------------------------- | Create an unsubscribe 'Package'.-createUnsubscribePackage :: Settings -> UUID -> Package-createUnsubscribePackage Settings{..} uuid =- Package- { packageCmd = 0xC3- , packageCorrelation = uuid- , packageData = runPut $ encodeMessage UnsubscribeFromStream- , packageCred = s_credentials- }
− Database/EventStore/Internal/Manager/Subscription/Types.hs
@@ -1,40 +0,0 @@------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Manager.Subscription.Types--- Copyright : (C) 2016 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable-------------------------------------------------------------------------------------module Database.EventStore.Internal.Manager.Subscription.Types where-----------------------------------------------------------------------------------import ClassyPrelude-----------------------------------------------------------------------------------import Database.EventStore.Internal.Types------------------------------------------------------------------------------------- | Indicates why a subscription has been dropped.-data SubDropReason- = SubUnsubscribed- -- ^ Subscription connection has been closed by the user.- | SubAccessDenied- -- ^ The current user is not allowed to operate on the supplied stream.- | SubNotFound- -- ^ Given stream name doesn't exist.- | SubPersistDeleted- -- ^ Given stream is deleted.- | SubAborted- -- ^ Occurs when the user shutdown the connection from the server or if- -- the connection to the server is no longer possible.- | SubNotAuthenticated (Maybe Text)- | SubServerError (Maybe Text)- -- ^ Unexpected error from the server.- | SubNotHandled !NotHandledReason !(Maybe MasterInfo)- | SubClientError !Text- | SubSubscriberMaxCountReached- deriving (Show, Eq)
Database/EventStore/Internal/Operation.hs view
@@ -1,8 +1,10 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Rank2Types #-} -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore.Internal.Operation@@ -14,15 +16,43 @@ -- Portability : non-portable -- ---------------------------------------------------------------------------------module Database.EventStore.Internal.Operation where+module Database.EventStore.Internal.Operation+ ( OpResult(..)+ , OperationError(..)+ , Operation+ , Need(..)+ , Code+ , Execution(..)+ , Expect(..)+ , freshId+ , failure+ , retry+ , send+ , request+ , waitFor+ , waitForOr+ , wrongVersion+ , streamDeleted+ , invalidTransaction+ , accessDenied+ , protobufDecodingError+ , serverError+ , invalidServerResponse+ , module Data.Machine+ ) where ---------------------------------------------------------------------------------import ClassyPrelude+import Prelude (String)++--------------------------------------------------------------------------------+import Data.Machine import Data.ProtocolBuffers+import Data.Serialize import Data.UUID -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types @@ -50,6 +80,7 @@ | ProtobufDecodingError String | ServerError (Maybe Text) -- ^ Reason | InvalidOperation Text+ | StreamNotFound Text | NotAuthenticatedOp -- ^ Invalid operation state. If happens, it's a driver bug. | Aborted@@ -61,125 +92,157 @@ instance Exception OperationError ----------------------------------------------------------------------------------- | Main operation state machine instruction.-data SM o a- = Return a- -- ^ Lifts a pure value into the intruction tree. Also marks the end of- -- an instruction tree.- | Yield o (SM o a)- -- ^ Emits an operation return value.- | FreshId (UUID -> SM o a)- -- ^ Asks for an unused 'UUID'.- | forall rq rp. (Encode rq, Decode rp) =>- SendPkg Command Command rq (rp -> SM o a)- -- ^ Send a request message given a command and an expected command.- -- response. It also carries a callback to call when response comes in.- | Failure (Maybe OperationError)- -- ^ Ends the instruction interpretation. If holds Nothing, the- -- interpretation should resume from the beginning. Otherwise it ends- -- by indicating what went wrong.+data Execution a+ = Proceed a+ | Retry+ | Failed !OperationError ---------------------------------------------------------------------------------instance Functor (SM o) where- fmap f (Return a) = Return (f a)- fmap f (Yield o n) = Yield o (fmap f n)- fmap f (FreshId k) = FreshId (fmap f . k)- fmap f (SendPkg ci co p k) = SendPkg ci co p (fmap f . k)- fmap _ (Failure e) = Failure e+instance Functor Execution where+ fmap f (Proceed a) = Proceed (f a)+ fmap _ Retry = Retry+ fmap _ (Failed e) = Failed e ---------------------------------------------------------------------------------instance Applicative (SM o) where- pure = return- (<*>) = ap+instance Applicative Execution where+ pure = return+ (<*>) = ap ---------------------------------------------------------------------------------instance Monad (SM o) where- return = Return+instance Monad Execution where+ return = Proceed - Return a >>= f = f a- Yield o n >>= f = Yield o (n >>= f)- FreshId k >>= f = FreshId ((f =<<) . k)- SendPkg ci co p k >>= f = SendPkg ci co p ((f =<<) . k)- Failure e >>= _ = Failure e+ Proceed a >>= f = f a+ Retry >>= _ = Retry+ Failed e >>= _ = Failed e --------------------------------------------------------------------------------+type Operation output = MachineT Execution Need output++--------------------------------------------------------------------------------+data Need a where+ NeedUUID :: Need UUID+ NeedRemote :: Command -> ByteString -> Need Package+ WaitRemote :: UUID -> Need (Maybe Package)++--------------------------------------------------------------------------------+-- | Instruction that composed an 'Operation'.+type Code o a = PlanT Need o Execution a++-------------------------------------------------------------------------------- -- | Asks for a unused 'UUID'.-freshId :: SM o UUID-freshId = FreshId Return+freshId :: Code o UUID+freshId = awaits NeedUUID -------------------------------------------------------------------------------- -- | Raises an 'OperationError'.-failure :: OperationError -> SM o a-failure e = Failure $ Just e+failure :: OperationError -> Code o a+failure = lift . Failed -------------------------------------------------------------------------------- -- | Asks to resume the interpretation from the beginning.-retry :: SM o a-retry = Failure Nothing+retry :: Code o a+retry = lift Retry ----------------------------------------------------------------------------------- | Sends a request to the server given a command request and response. It--- returns the expected deserialized message.-send :: (Encode rq, Decode rp) => Command -> Command -> rq -> SM o rp-send ci co rq = SendPkg ci co rq Return+-- | Like 'request' except it discards the correlation id of the network+-- exchange.+send :: (Encode req, Decode resp)+ => Command+ -> Command+ -> req+ -> Code o resp+send reqCmd expCmd req = do+ let payload = runPut $ encodeMessage req+ pkg <- awaits $ NeedRemote reqCmd 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+ ----------------------------------------------------------------------------------- | Emits operation return value.-yield :: o -> SM o ()-yield o = Yield o (Return ())+data Expect o where+ Expect :: Decode resp => Command -> (UUID -> resp -> Code o ()) -> Expect o ----------------------------------------------------------------------------------- | Replaces every emitted value, via 'yield' function by calling the given--- callback.-foreach :: SM a x -> (a -> SM b x) -> SM b x-foreach start k = go start- where- go (Return x) = Return x- go (Yield a n) = k a >> go n- go (FreshId ki) = FreshId (go . ki)- go (SendPkg ci co p kp) = SendPkg ci co p (go . kp)- go (Failure e) = Failure e+-- | Runs the first expection that matches.+runFirstMatch :: Package -> [Expect o] -> Code o ()+runFirstMatch _ [] = invalidOperation "No expection 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 ----------------------------------------------------------------------------------- | Maps every emitted value, via 'yield', using given function.-mapOp :: (a -> b) -> SM a () -> SM b ()-mapOp k sm = foreach sm (yield . k)+-- | 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+ -> req+ -> [Expect o]+ -> Code o ()+request reqCmd rq exps = do+ let payload = runPut $ encodeMessage rq+ pkg <- awaits $ NeedRemote reqCmd payload+ runFirstMatch pkg exps ----------------------------------------------------------------------------------- | An operation is just a 'SM' tree.-type Operation a = SM a ()+-- | Like 'waitForOr' but will 'stop' if the connection reset.+waitFor :: UUID -> [Expect o] -> Code o ()+waitFor pid exps = waitForOr pid stop 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 =+ awaits (WaitRemote pid) >>= \case+ Nothing -> alt+ Just pkg ->+ runFirstMatch pkg exps++-------------------------------------------------------------------------------- -- | Raises 'WrongExpectedVersion' exception.-wrongVersion :: Text -> ExpectedVersion -> SM o a+wrongVersion :: Text -> ExpectedVersion -> Code o a wrongVersion stream ver = failure (WrongExpectedVersion stream ver) -------------------------------------------------------------------------------- -- | Raises 'StreamDeleted' exception.-streamDeleted :: Text -> SM o a+streamDeleted :: Text -> Code o a streamDeleted stream = failure (StreamDeleted stream) -------------------------------------------------------------------------------- -- | Raises 'InvalidTransaction' exception.-invalidTransaction :: SM o a+invalidTransaction :: Code o a invalidTransaction = failure InvalidTransaction -------------------------------------------------------------------------------- -- | Raises 'AccessDenied' exception.-accessDenied :: StreamName -> SM o a+accessDenied :: StreamName -> Code oconcat a accessDenied = failure . AccessDenied -------------------------------------------------------------------------------- -- | Raises 'ProtobufDecodingError' exception.-protobufDecodingError :: String -> SM o a+protobufDecodingError :: String -> Code o a protobufDecodingError = failure . ProtobufDecodingError -------------------------------------------------------------------------------- -- | Raises 'ServerError' exception.-serverError :: Maybe Text -> SM o a+serverError :: Maybe Text -> Code o a serverError = failure . ServerError -------------------------------------------------------------------------------- -- | Raises 'InvalidServerResponse' exception.-invalidServerResponse :: Command -> Command -> SM o a+invalidServerResponse :: Command -> Command -> Code o a invalidServerResponse expe got = failure $ InvalidServerResponse expe got++--------------------------------------------------------------------------------+invalidOperation :: Text -> Code o a+invalidOperation = failure . InvalidOperation
Database/EventStore/Internal/Operation/Catchup.hs view
@@ -17,7 +17,6 @@ , CatchupOpResult(..) , Checkpoint(..) , catchup- , catchupStreamName ) where --------------------------------------------------------------------------------@@ -25,14 +24,15 @@ import Data.Maybe ---------------------------------------------------------------------------------import ClassyPrelude---------------------------------------------------------------------------------- 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 Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream+import Database.EventStore.Internal.Subscription.Types import Database.EventStore.Internal.Types --------------------------------------------------------------------------------@@ -47,22 +47,58 @@ -------------------------------------------------------------------------------- streamNotFound :: Text -> OperationError-streamNotFound stream =- InvalidOperation $ "Catchup. inexistant stream [" <> stream <> "]"+streamNotFound stream = StreamNotFound stream -------------------------------------------------------------------------------- -- | Catchup operation state. data CatchupState = RegularCatchup Text Int32 -- ^ Indicates the stream name and the next event number to start from.- | AllCatchup Int64 Int64+ | AllCatchup Position -- ^ Indicates the commit and prepare position. Used when catching up from -- the $all stream.+ deriving Show --------------------------------------------------------------------------------+fetch :: Settings -> Int32 -> Bool -> CatchupState -> Code o SomeSlice+fetch setts batch tos state =+ case state of+ RegularCatchup stream n -> do+ outcome <- deconstruct $ fmap Left $+ readStreamEvents setts Forward stream n batch tos+ fromReadResult stream outcome (return . toSlice)+ AllCatchup (Position com pre) ->+ deconstruct $ fmap (Left . toSlice) $+ readAllEvents setts com pre batch tos Forward++--------------------------------------------------------------------------------+updateState :: CatchupState -> Location -> CatchupState+updateState (RegularCatchup stream _) (StreamEventNumber n) =+ RegularCatchup stream n+updateState (AllCatchup _) (StreamPosition p) = AllCatchup p+updateState x y = error $ "Unexpected input updateState: " <> show (x,y)++--------------------------------------------------------------------------------+sourceStream :: Settings+ -> Int32+ -> Bool+ -> CatchupState+ -> Operation SubAction+sourceStream setts batch tos start = unfoldPlan start go+ where+ go state = do+ s <- fetch setts batch tos state+ traverse_ (yield . Submit) (sliceEvents s)++ when (sliceEOS s)+ stop++ return $ updateState state (sliceNext s)++-------------------------------------------------------------------------------- catchupStreamName :: CatchupState -> Text catchupStreamName (RegularCatchup stream _) = stream-catchupStreamName _ = "$all"+catchupStreamName _ = "" -------------------------------------------------------------------------------- data CatchupOpResult =@@ -77,44 +113,18 @@ -> CatchupState -> Bool -> Maybe Int32- -> Operation CatchupOpResult -- ([ResolvedEvent], Bool, Checkpoint)-catchup setts init_tpe tos bat_siz = go init_tpe+ -> Operation SubAction+catchup setts state tos batchSiz =+ sourceStream setts batch tos state <> volatile stream tos where- batch = fromMaybe defaultBatchSize bat_siz- go tpe = do- let action =- case tpe of- RegularCatchup stream cur_evt ->- let op = readStreamEvents setts Forward stream cur_evt- batch tos in- mapOp Left op- AllCatchup c_pos p_pos ->- let op = readAllEvents setts c_pos p_pos batch- tos Forward in- mapOp Right op-- foreach action $ \res -> do- (eos, evts, nchk, nxt_tpe) <- case res of- Right as -> do- let Position nxt_c nxt_p = sliceNext as- tmp_tpe = AllCatchup nxt_c nxt_p- chk = CheckpointPosition $ sliceNext as- return (sliceEOS as, sliceEvents as, chk, tmp_tpe)- Left rr -> fromReadResult (catchupStreamName tpe) rr $ \as ->- let RegularCatchup s _ = tpe- nxt = sliceNext as- tmp_tpe = RegularCatchup s nxt- chk = CheckpointNumber nxt in- return (sliceEOS as, sliceEvents as, chk, tmp_tpe)-- yield (CatchupOpResult evts eos nchk)- unless eos $ go nxt_tpe+ batch = fromMaybe defaultBatchSize batchSiz+ stream = catchupStreamName state -------------------------------------------------------------------------------- fromReadResult :: Text -> ReadResult 'RegularStream a- -> (a -> SM b x)- -> SM b x+ -> (a -> Code o x)+ -> Code o x fromReadResult stream res k = case res of ReadNoStream -> failure $ streamNotFound stream
Database/EventStore/Internal/Operation/DeleteStream.hs view
@@ -22,12 +22,14 @@ import Data.Maybe ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Command import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.DeleteStream.Message+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types @@ -42,9 +44,9 @@ -> ExpectedVersion -> Maybe Bool -> Operation DeleteResult-deleteStream Settings{..} s v hard = do+deleteStream Settings{..} s v hard = construct $ do let msg = newRequest s (expVersionInt32 v) s_requireMaster hard- resp <- send 0x8A 0x8B msg+ resp <- send deleteStreamCmd deleteStreamCompletedCmd msg let r = getField $ _result resp com_pos = getField $ _commitPosition resp prep_pos = getField $ _preparePosition resp
Database/EventStore/Internal/Operation/DeleteStream/Message.hs view
@@ -17,11 +17,11 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers -------------------------------------------------------------------------------- import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude -------------------------------------------------------------------------------- -- | Delete stream request.
+ Database/EventStore/Internal/Operation/Persist.hs view
@@ -0,0 +1,79 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Operation.Persist+-- 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.Operation.Persist (persist) where++--------------------------------------------------------------------------------+import Data.ProtocolBuffers+import Data.UUID++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Subscription.Types+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+persist :: Text -> Text -> Int32 -> Operation SubAction+persist grp stream bufSize = construct (issueRequest grp stream bufSize)++--------------------------------------------------------------------------------+issueRequest :: Text -> Text -> Int32 -> Code SubAction ()+issueRequest grp stream bufSize = do+ let req = _connectToPersistentSubscription grp stream bufSize+ request connectToPersistentSubscriptionCmd 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++--------------------------------------------------------------------------------+handleDropped :: SubscriptionDropped -> Code SubAction ()+handleDropped d = do+ let reason = fromMaybe D_Unsubscribed (getField $ dropReason d)+ yield (Dropped $ toSubDropReason reason)
+ Database/EventStore/Internal/Operation/PersistOperations.hs view
@@ -0,0 +1,75 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Operation.PersistOperations+-- 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.Operation.PersistOperations+ ( createPersist+ , updatePersist+ , deletePersist+ ) where++--------------------------------------------------------------------------------+import Data.ProtocolBuffers++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Subscription.Types+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+persistOperation :: Text+ -> Text+ -> PersistAction+ -> Operation (Maybe PersistActionException)+persistOperation grp stream tpe = construct go+ where+ go =+ case tpe of+ PersistCreate ss -> do+ let req = _createPersistentSubscription grp stream ss+ resp <- send createPersistentSubscriptionCmd+ createPersistentSubscriptionCompletedCmd req+ let result = createRException $ getField $ cpscResult resp+ yield result+ PersistUpdate ss -> do+ let req = _updatePersistentSubscription grp stream ss+ resp <- send updatePersistentSubscriptionCmd+ updatePersistentSubscriptionCompletedCmd req+ let result = updateRException $ getField $ upscResult resp+ yield result+ PersistDelete -> do+ let req = _deletePersistentSubscription grp stream+ resp <- send deletePersistentSubscriptionCmd+ deletePersistentSubscriptionCompletedCmd req+ let result = deleteRException $ getField $ dpscResult resp+ yield result++--------------------------------------------------------------------------------+createPersist :: Text+ -> Text+ -> PersistentSubscriptionSettings+ -> Operation (Maybe PersistActionException)+createPersist grp stream ss = persistOperation grp stream (PersistCreate ss)++--------------------------------------------------------------------------------+updatePersist :: Text+ -> Text+ -> PersistentSubscriptionSettings+ -> Operation (Maybe PersistActionException)+updatePersist grp stream ss = persistOperation grp stream (PersistUpdate ss)++--------------------------------------------------------------------------------+deletePersist :: Text+ -> Text+ -> Operation (Maybe PersistActionException)+deletePersist grp stream = persistOperation grp stream PersistDelete
Database/EventStore/Internal/Operation/Read/Common.hs view
@@ -16,17 +16,21 @@ module Database.EventStore.Internal.Operation.Read.Common where ---------------------------------------------------------------------------------import Data.Foldable (foldMap)+import Control.Applicative+import Data.Foldable+import Data.Monoid+import Data.Traversable import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude----------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types --------------------------------------------------------------------------------+import Prelude++-------------------------------------------------------------------------------- -- | Enumeration detailing the possible outcomes of reading a stream. data ReadResult :: StreamType -> * -> * where ReadSuccess :: a -> ReadResult t a@@ -93,6 +97,8 @@ -- ^ Gets the starting location of this slice. sliceNext :: a -> Loc a -- ^ Gets the next location of this slice.+ toSlice :: a -> SomeSlice+ -- ^ Returns a common view of a slice. -------------------------------------------------------------------------------- -- | Regular stream slice.@@ -117,6 +123,15 @@ sliceFrom = _ssFrom sliceNext = _ssNext + toSlice s =+ SomeSlice+ { __events = sliceEvents s+ , __eos = sliceEOS s+ , __dir = sliceDirection s+ , __from = StreamEventNumber $ sliceFrom s+ , __next = StreamEventNumber $ sliceNext s+ }+ -------------------------------------------------------------------------------- -- | Represents a slice of the $all stream. data AllSlice =@@ -137,3 +152,39 @@ sliceEOS = _saEOS sliceFrom = _saFrom sliceNext = _saNext++ toSlice s =+ SomeSlice+ { __events = sliceEvents s+ , __eos = sliceEOS s+ , __dir = sliceDirection s+ , __from = StreamPosition $ sliceFrom s+ , __next = StreamPosition $ sliceNext s+ }++--------------------------------------------------------------------------------+data Location+ = StreamEventNumber !Int32+ | StreamPosition !Position+ deriving Show++--------------------------------------------------------------------------------+data SomeSlice =+ SomeSlice+ { __events :: ![ResolvedEvent]+ , __eos :: !Bool+ , __dir :: !ReadDirection+ , __from :: !Location+ , __next :: !Location+ } deriving Show++--------------------------------------------------------------------------------+instance Slice SomeSlice where+ type Loc SomeSlice = Location++ sliceEvents = __events+ sliceDirection = __dir+ sliceEOS = __eos+ sliceFrom = __from+ sliceNext = __next+ toSlice = id
Database/EventStore/Internal/Operation/ReadAllEvents.hs view
@@ -18,13 +18,15 @@ import Data.Maybe ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Command import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Read.Common import Database.EventStore.Internal.Operation.ReadAllEvents.Message+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types @@ -37,15 +39,15 @@ -> Bool -> ReadDirection -> Operation AllSlice-readAllEvents Settings{..} c_pos p_pos max_c tos dir = do+readAllEvents Settings{..} c_pos p_pos max_c tos dir = construct $ do let msg = newRequest c_pos p_pos max_c tos s_requireMaster cmd = case dir of- Forward -> 0xB6- Backward -> 0xB8+ Forward -> readAllEventsForwardCmd+ Backward -> readAllEventsBackwardCmd resp_cmd = case dir of- Forward -> 0xB7- Backward -> 0xB9+ Forward -> readAllEventsForwardCompletedCmd+ Backward -> readAllEventsBackwardCompletedCmd resp <- send cmd resp_cmd msg let r = getField $ _Result resp err = getField $ _Error resp
Database/EventStore/Internal/Operation/ReadAllEvents/Message.hs view
@@ -17,10 +17,10 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types --------------------------------------------------------------------------------
Database/EventStore/Internal/Operation/ReadEvent.hs view
@@ -20,13 +20,15 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Command import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.ReadEvent.Message import Database.EventStore.Internal.Operation.Read.Common+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types @@ -50,9 +52,9 @@ -> Int32 -> Bool -> Operation (ReadResult 'RegularStream ReadEvent)-readEvent Settings{..} s evtn tos = do+readEvent Settings{..} s evtn tos = construct $ do let msg = newRequest s evtn tos s_requireMaster- resp <- send 0xB0 0xB1 msg+ resp <- send readEventCmd readEventCompletedCmd msg let r = getField $ _result resp evt = newResolvedEvent $ getField $ _indexedEvent resp err = getField $ _error resp
Database/EventStore/Internal/Operation/ReadEvent/Message.hs view
@@ -1,6 +1,11 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DataKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DataKinds #-}+#if __GLASGOW_HASKELL__ < 800 {-# OPTIONS_GHC -fcontext-stack=26 #-}+#else+{-# OPTIONS_GHC -freduction-depth=26 #-}+#endif -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore.Internal.Operation.ReadEvent.Message@@ -18,10 +23,10 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types --------------------------------------------------------------------------------@@ -72,3 +77,4 @@ -------------------------------------------------------------------------------- instance Decode Response+instance Encode Response
Database/EventStore/Internal/Operation/ReadStreamEvents.hs view
@@ -18,13 +18,15 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Command import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Read.Common import Database.EventStore.Internal.Operation.ReadStreamEvents.Message+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types @@ -37,15 +39,15 @@ -> Int32 -> Bool -> Operation (ReadResult 'RegularStream StreamSlice)-readStreamEvents Settings{..} dir s st cnt tos = do+readStreamEvents Settings{..} dir s st cnt tos = construct $ do let req_cmd = case dir of- Forward -> 0xB2- Backward -> 0xB4+ Forward -> readStreamEventsForwardCmd+ Backward -> readStreamEventsBackwardCmd resp_cmd = case dir of- Forward -> 0xB3- Backward -> 0xB5+ Forward -> readStreamEventsForwardCompletedCmd+ Backward -> readStreamEventsBackwardCompletedCmd msg = newRequest s st cnt tos s_requireMaster resp <- send req_cmd resp_cmd msg
Database/EventStore/Internal/Operation/ReadStreamEvents/Message.hs view
@@ -17,10 +17,10 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types --------------------------------------------------------------------------------
Database/EventStore/Internal/Operation/StreamMetadata.hs view
@@ -23,7 +23,6 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.Aeson (decode) --------------------------------------------------------------------------------@@ -32,6 +31,8 @@ import Database.EventStore.Internal.Operation.ReadEvent import Database.EventStore.Internal.Operation.Write.Common import Database.EventStore.Internal.Operation.WriteEvents+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types @@ -42,13 +43,14 @@ -------------------------------------------------------------------------------- -- | Read stream metadata operation. readMetaStream :: Settings -> Text -> Operation StreamMetadataResult-readMetaStream setts s =- foreach (readEvent setts (metaStream s) (-1) False) $ \tmp -> do- onReadResult tmp $ \n e_num evt -> do- let bytes = recordedEventData $ resolvedEventOriginal evt- case decode $ fromStrict bytes of- Just pv -> yield $ StreamMetadataResult n e_num pv- Nothing -> failure invalidFormat+readMetaStream setts s = construct $ do+ let op = readEvent setts (metaStream s) (-1) False+ tmp <- deconstruct (fmap Left op)+ onReadResult tmp $ \n e_num evt -> do+ let bytes = recordedEventData $ resolvedEventOriginal evt+ case decode $ fromStrict bytes of+ Just pv -> yield $ StreamMetadataResult n e_num pv+ Nothing -> failure invalidFormat -------------------------------------------------------------------------------- -- | Set stream metadata operation.@@ -60,9 +62,8 @@ setMetaStream setts s v meta = let stream = metaStream s json = streamMetadataJSON meta- evt = createEvent StreamMetadataType Nothing (withJson json)- inner = writeEvents setts stream v [evt] in- foreach inner yield+ evt = createEvent StreamMetadataType Nothing (withJson json) in+ writeEvents setts stream v [evt] -------------------------------------------------------------------------------- invalidFormat :: OperationError@@ -74,8 +75,8 @@ -------------------------------------------------------------------------------- onReadResult :: ReadResult 'RegularStream ReadEvent- -> (Text -> Int32 -> ResolvedEvent -> SM a b)- -> SM a b+ -> (Text -> Int32 -> ResolvedEvent -> Code o a)+ -> Code o a onReadResult (ReadSuccess r) k = case r of ReadEvent s n e -> k s n e
Database/EventStore/Internal/Operation/Transaction.hs view
@@ -25,22 +25,24 @@ import Data.Maybe ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Command import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Transaction.Message import Database.EventStore.Internal.Operation.Write.Common+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types -------------------------------------------------------------------------------- -- | Start transaction operation. transactionStart :: Settings -> Text -> ExpectedVersion -> Operation Int64-transactionStart Settings{..} stream exp_v = do+transactionStart Settings{..} stream exp_v = construct $ do let msg = newStart stream (expVersionInt32 exp_v) s_requireMaster- resp <- send 0x84 0x85 msg+ resp <- send transactionStartCmd transactionStartCompletedCmd msg let tid = getField $ _transId resp r = getField $ _result resp case r of@@ -61,10 +63,10 @@ -> Int64 -> [Event] -> Operation ()-transactionWrite Settings{..} stream exp_v trans_id evts = do+transactionWrite Settings{..} stream exp_v trans_id evts = construct $ do nevts <- traverse eventToNewEvent evts let msg = newWrite trans_id nevts s_requireMaster- resp <- send 0x86 0x87 msg+ resp <- send transactionWriteCmd transactionWriteCompletedCmd msg let r = getField $ _wwResult resp case r of OP_PREPARE_TIMEOUT -> retry@@ -83,9 +85,9 @@ -> ExpectedVersion -> Int64 -> Operation WriteResult-transactionCommit Settings{..} stream exp_v trans_id = do+transactionCommit Settings{..} stream exp_v trans_id = construct $ do let msg = newCommit trans_id s_requireMaster- resp <- send 0x88 0x89 msg+ resp <- send transactionCommitCmd transactionCommitCompletedCmd msg let r = getField $ _ccResult resp com_pos = getField $ _commitPosition resp pre_pos = getField $ _preparePosition resp
Database/EventStore/Internal/Operation/Transaction/Message.hs view
@@ -17,11 +17,11 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers -------------------------------------------------------------------------------- import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types --------------------------------------------------------------------------------
+ Database/EventStore/Internal/Operation/Volatile.hs view
@@ -0,0 +1,78 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Operation.Volatile+-- 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.Operation.Volatile (volatile) where++--------------------------------------------------------------------------------+import Data.ProtocolBuffers+import Data.UUID++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Subscription.Types+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+volatile :: Text -> Bool -> Operation SubAction+volatile stream tos = construct (issueRequest stream tos)++--------------------------------------------------------------------------------+issueRequest :: Text -> Bool -> Code SubAction ()+issueRequest stream tos = do+ let req = subscribeToStream stream tos+ request subscribeToStreamCmd 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)++--------------------------------------------------------------------------------+live :: UUID -> Code SubAction ()+live subscriptionId = loop+ 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)
Database/EventStore/Internal/Operation/Write/Common.hs view
@@ -15,10 +15,8 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude---------------------------------------------------------------------------------- import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types --------------------------------------------------------------------------------@@ -34,7 +32,7 @@ -------------------------------------------------------------------------------- -- | Constructs a 'NewEvent' from an 'Event'.-eventToNewEvent :: Event -> SM a NewEvent+eventToNewEvent :: Event -> Code o NewEvent eventToNewEvent evt = do uuid <- maybe freshId return evt_id return $ newEvent evt_type
Database/EventStore/Internal/Operation/WriteEvents.hs view
@@ -17,13 +17,15 @@ import Data.Maybe ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers --------------------------------------------------------------------------------+import Database.EventStore.Internal.Command import Database.EventStore.Internal.Operation import Database.EventStore.Internal.Operation.Write.Common import Database.EventStore.Internal.Operation.WriteEvents.Message+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings import Database.EventStore.Internal.Stream import Database.EventStore.Internal.Types @@ -34,10 +36,10 @@ -> ExpectedVersion -> [Event] -> Operation WriteResult-writeEvents Settings{..} s v evts = do+writeEvents Settings{..} s v evts = construct $ do nevts <- traverse eventToNewEvent evts let msg = newRequest s (expVersionInt32 v) nevts s_requireMaster- resp <- send 0x82 0x83 msg+ resp <- send writeEventsCmd writeEventsCompletedCmd msg let r = getField $ _result resp com_pos = getField $ _commitPosition resp prep_pos = getField $ _preparePosition resp
Database/EventStore/Internal/Operation/WriteEvents/Message.hs view
@@ -17,11 +17,11 @@ import Data.Int ---------------------------------------------------------------------------------import ClassyPrelude import Data.ProtocolBuffers -------------------------------------------------------------------------------- import Database.EventStore.Internal.Operation+import Database.EventStore.Internal.Prelude import Database.EventStore.Internal.Types --------------------------------------------------------------------------------
+ Database/EventStore/Internal/OperationManager.hs view
@@ -0,0 +1,60 @@+{-# 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/Operations.hs view
@@ -13,6 +13,7 @@ module Database.EventStore.Internal.Operations ( module Database.EventStore.Internal.Operation.Catchup , module Database.EventStore.Internal.Operation.DeleteStream+ , module Database.EventStore.Internal.Operation.PersistOperations , module Database.EventStore.Internal.Operation.ReadAllEvents , module Database.EventStore.Internal.Operation.ReadEvent , module Database.EventStore.Internal.Operation.ReadStreamEvents@@ -24,6 +25,7 @@ -------------------------------------------------------------------------------- import Database.EventStore.Internal.Operation.Catchup import Database.EventStore.Internal.Operation.DeleteStream+import Database.EventStore.Internal.Operation.PersistOperations import Database.EventStore.Internal.Operation.ReadAllEvents import Database.EventStore.Internal.Operation.ReadEvent import Database.EventStore.Internal.Operation.ReadStreamEvents
+ Database/EventStore/Internal/Prelude.hs view
@@ -0,0 +1,185 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Prelude+-- 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.Prelude+ ( IsString(..)+ , Semigroup(..)+ , MonadIO(..)+ , Hashable(..)+ , Monoid(..)+ , Down(..)+ , HashMap+ , Seq+ , Set+ , ByteString+ , Text+ , Generic+ , Alternative(..)+ , MonadBaseControl(..)+ , atomically+ , zip+ , zipWith+ , tshow+ , length+ , lift+ , retrySTM+ , fromMaybe+ , unlessM+ , whenM+ , isJust+ , null+ , module Prelude+ , module Control.Applicative+ , module Data.Int+ , module Data.Foldable+ , module Data.Traversable+ , module Control.Concurrent.Lifted+ , module Control.Concurrent.Async.Lifted+ , module Control.Concurrent.MVar.Lifted+ , module Control.Concurrent.STM+ , module Control.Concurrent.STM.TBMQueue+ , module Control.Exception.Safe+ , module Control.Monad+ , module Control.Monad.Base+ , module Control.Monad.Catch+ , module Control.Monad.Trans.Control+ , module Data.Containers+ , module Data.Functor+ , module Data.IORef.Lifted+ , module Data.Sequences+ , module Data.Time+ , module Data.Typeable+ , module Data.Word+ ) where++--------------------------------------------------------------------------------+import Prelude+ ( IO+ , FilePath+ , Num(..)+ , Show(..)+ , Eq(..)+ , Ord(..)+ , Enum(..)+ , Bounded(..)+ , Either(..)+ , Maybe(..)+ , Bool(..)+ , Integral(..)+ , Float+ , Fractional(..)+ , Ordering(..)+ , Double+ , Integer+ , (.)+ , id+ , ($)+ , otherwise+ , not+ , fromIntegral+ , truncate+ , print+ , realToFrac+ , (||)+ , (&&)+ , error+ , undefined+ , toRational+ , maybe+ , either+ , const+ , flip+ )+import Control.Monad+ ( Monad(..)+ , MonadPlus(..)+ , (=<<)+ , (<=<)+ , foldM+ , foldM_+ , when+ , unless+ , forever+ , ap+ )+import Control.Applicative (Applicative(..), Alternative(..))+import Data.Int+import Data.List (zip, zipWith)+import Data.Maybe (fromMaybe, isJust)+import Data.String (IsString(..))+import Data.Foldable+ ( Foldable+ , foldMap+ , traverse_+ , for_+ , toList+ , forM_+ , foldl'+ )+import Data.Traversable+import Data.Functor+ ( Functor(..)+ , (<$)+ , (<$>)+ )+import Data.Monoid(Monoid(..))+import Data.Ord (Down(..))+import Data.Typeable+import Data.Word+import GHC.Generics (Generic)++--------------------------------------------------------------------------------+import Control.Concurrent.Lifted hiding (throwTo, yield)+import Control.Concurrent.Async.Lifted+import Control.Concurrent.MVar.Lifted+import Control.Concurrent.STM hiding (atomically, retry, check)+import qualified Control.Concurrent.STM as STM+import Control.Concurrent.STM.TBMQueue+import Control.Monad.Catch (MonadCatch(..), MonadThrow(..))+import Control.Monad.Trans.Control hiding (embed, embed_)+import Control.Exception.Safe hiding (handle, throwM, catch)+import Control.Monad.Base+import Control.Monad.Trans+import Data.ByteString (ByteString)+import Data.Containers+import Data.IORef.Lifted+import Data.Hashable (Hashable(..))+import Data.HashMap.Strict (HashMap)+import Data.MonoTraversable.Unprefixed (length, null)+import Data.Semigroup+import Data.Sequences hiding (group)+import Data.Sequence (Seq)+import Data.Set (Set)+import Data.Text (Text)+import Data.Time++--------------------------------------------------------------------------------+-- | Generalized version of 'STM.atomically'.+atomically :: MonadIO m => STM a -> m a+atomically = liftIO . STM.atomically++--------------------------------------------------------------------------------+tshow :: Show a => a -> Text+tshow = pack . show++--------------------------------------------------------------------------------+retrySTM :: STM a+retrySTM = STM.retry++--------------------------------------------------------------------------------+-- | Only perform the action if the predicate returns 'True'.+whenM :: Monad m => m Bool -> m () -> m ()+whenM mbool action = mbool >>= flip when action++--------------------------------------------------------------------------------+-- | Only perform the action if the predicate returns 'False'.+unlessM :: Monad m => m Bool -> m () -> m ()+unlessM mbool action = mbool >>= flip unless action
− Database/EventStore/Internal/Processor.hs
@@ -1,328 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE RecordWildCards #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Processor--- Copyright : (C) 2014 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Top level operation and subscription logic of EventStore driver.----------------------------------------------------------------------------------module Database.EventStore.Internal.Processor- ( Processor- , Transition(..)- , newProcessor- , connectRegularStream- , connectPersistent- , createPersistent- , updatePersistent- , deletePersistent- , ackPersist- , nakPersist- , newOperation- , submitPackage- , unsubscribe- , abort- ) where-----------------------------------------------------------------------------------import Data.Int-----------------------------------------------------------------------------------import ClassyPrelude-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.EndPoint-import Database.EventStore.Internal.Generator-import Database.EventStore.Internal.Operation hiding (SM(..))-import Database.EventStore.Internal.Types-----------------------------------------------------------------------------------import qualified Database.EventStore.Internal.Manager.Operation.Model as Op-import qualified Database.EventStore.Internal.Manager.Subscription.Driver as Sub-import qualified Database.EventStore.Internal.Manager.Subscription.Model as Sub------------------------------------------------------------------------------------- | Type of inputs handled by the 'Processor' driver.-data In r- = Cmd (Cmd r)- -- ^ A command can be an 'Operation' or a 'Subscription' actions.- | Pkg Package- -- ^ Handle a 'Package' coming from the server.------------------------------------------------------------------------------------- | Type of commmand a 'Processor' can handle.-data Cmd r- = SubscriptionCmd (SubscriptionCmd r)- -- ^ Subcription related commands.- | forall a. NewOp (Operation a) (Either OperationError a -> r)- -- ^ Register a new 'Operation'.- | Abort- -- ^ Aborts every pending operation.------------------------------------------------------------------------------------- | Supported subscription command.-data SubscriptionCmd r- = ConnectStream (Sub.SubConnectEvent -> r) Text Bool- -- ^ Creates a regular subscription connection.- | ConnectPersist (Sub.SubConnectEvent -> r) Text Text Int32- -- ^ Creates a persistent subscription connection.-- | CreatePersist (Either Sub.PersistActionException Sub.ConfirmedAction -> r)- Text- Text- PersistentSubscriptionSettings- -- ^ Creates a persistent subscription.-- | Unsubscribe Sub.Running- -- ^ Unsubscribes a subscription.-- | UpdatePersist (Either Sub.PersistActionException Sub.ConfirmedAction -> r)- Text- Text- PersistentSubscriptionSettings- -- ^ Updates a persistent subscription.-- | DeletePersist (Either Sub.PersistActionException Sub.ConfirmedAction -> r)- Text- Text- -- ^ Deletes a persistent subscription.-- | AckPersist r Sub.Running [UUID]- -- ^ Acknowledges a set of events has been successfully handled.-- | NakPersist r Sub.Running Sub.NakAction (Maybe Text) [UUID]- -- ^ Acknowledges a set of events hasn't been handled successfully.------------------------------------------------------------------------------------- | Creates a regular subscription connection.-connectRegularStream :: (Sub.SubConnectEvent -> r)- -> Text -- ^ Stream name.- -> Bool -- ^ Resolve Link TOS.- -> Processor r- -> Transition r-connectRegularStream c s tos (Processor k) =- k $ Cmd $ SubscriptionCmd $ ConnectStream c s tos------------------------------------------------------------------------------------- | Creates a persistent subscription connection.-connectPersistent :: (Sub.SubConnectEvent -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> Int32 -- ^ Buffer size.- -> Processor r- -> Transition r-connectPersistent c g s siz (Processor k) =- k $ Cmd $ SubscriptionCmd $ ConnectPersist c g s siz------------------------------------------------------------------------------------- | Creates a persistent subscription.-createPersistent :: (Either Sub.PersistActionException Sub.ConfirmedAction -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> PersistentSubscriptionSettings- -> Processor r- -> Transition r-createPersistent c g s sett (Processor k) =- k $ Cmd $ SubscriptionCmd $ CreatePersist c g s sett------------------------------------------------------------------------------------- | Updates a persistent subscription.-updatePersistent :: (Either Sub.PersistActionException Sub.ConfirmedAction -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> PersistentSubscriptionSettings- -> Processor r- -> Transition r-updatePersistent c g s sett (Processor k) =- k $ Cmd $ SubscriptionCmd $ UpdatePersist c g s sett------------------------------------------------------------------------------------- | Deletes a persistent subscription.-deletePersistent :: (Either Sub.PersistActionException Sub.ConfirmedAction -> r)- -> Text -- ^ Group name.- -> Text -- ^ Stream name.- -> Processor r- -> Transition r-deletePersistent c g s (Processor k) =- k $ Cmd $ SubscriptionCmd $ DeletePersist c g s------------------------------------------------------------------------------------- | Acknowledges a set of events has been successfully handled.-ackPersist :: r -> Sub.Running -> [UUID] -> Processor r -> Transition r-ackPersist r run evts (Processor k) =- k $ Cmd $ SubscriptionCmd $ AckPersist r run evts------------------------------------------------------------------------------------- | Acknowledges a set of events hasn't been handled successfully.-nakPersist :: r- -> Sub.Running- -> Sub.NakAction- -> Maybe Text- -> [UUID]- -> Processor r- -> Transition r-nakPersist r run act res evts (Processor k) =- k $ Cmd $ SubscriptionCmd $ NakPersist r run act res evts------------------------------------------------------------------------------------- | Registers a new 'Operation'.-newOperation :: (Either OperationError a -> r)- -> Operation a- -> Processor r- -> Transition r-newOperation c op (Processor k) = k $ Cmd $ NewOp op c------------------------------------------------------------------------------------- | Submits a 'Package'.-submitPackage :: Package -> Processor r -> Transition r-submitPackage pkg (Processor k) = k $ Pkg pkg------------------------------------------------------------------------------------- | Unsubscribes a subscription.-unsubscribe :: Sub.Running -> Processor r -> Transition r-unsubscribe r (Processor k) = k $ Cmd $ SubscriptionCmd $ Unsubscribe r------------------------------------------------------------------------------------- | Aborts every pending operation.-abort :: Processor r -> Transition r-abort (Processor k) = k $ Cmd Abort------------------------------------------------------------------------------------- | 'Processor' internal state.-data State r =- State- { _subDriver :: Sub.Driver r- -- ^ Subscription driver.- , _opModel :: Op.Model r- -- ^ Operation model.- }-----------------------------------------------------------------------------------initState :: Settings -> Generator -> State r-initState setts g = State (Sub.newDriver setts g1) (Op.newModel setts g2)- where- (g1, g2) = splitGenerator g------------------------------------------------------------------------------------- | Represents the state transition of 'Processor' state machine.-data Transition r- = Produce r (Transition r)- -- ^ Produces a final value.- | Transmit Package (Transition r)- -- ^ Indicates to send the given 'Package'.- | Await (Processor r)- -- ^ Waits for more input.- | ForceReconnectCmd NodeEndPoints (Transition r)------------------------------------------------------------------------------------- | Processor state-machine.-newtype Processor r = Processor (In r -> Transition r)-----------------------------------------------------------------------------------loopOpTransition :: State r -> Op.Transition r -> Transition r-loopOpTransition st (Op.Produce r nxt) =- Produce r (loopOpTransition st nxt)-loopOpTransition st (Op.Transmit pkg nxt) =- Transmit pkg (loopOpTransition st nxt)-loopOpTransition st (Op.Await m) =- let nxt_st = st { _opModel = m } in Await $ Processor $ execute nxt_st-loopOpTransition st (Op.NotHandled info nxt) =- let node = masterInfoNodeEndPoints info in- ForceReconnectCmd node (loopOpTransition st nxt)-----------------------------------------------------------------------------------abortTransition :: State r -> Op.Transition r -> [r] -> Transition r-abortTransition st init_op init_rs = abortOp init_op- where- abortOp (Op.Produce r nxt) = Produce r (abortOp nxt)- abortOp (Op.Transmit _ nxt) = abortOp nxt- abortOp _ = abortSub init_rs-- abortSub [] = Await $ Processor $ execute st- abortSub (r:rs) = Produce r (abortSub rs)-----------------------------------------------------------------------------------execute :: State r -> In r -> Transition r-execute = go- where- go st (Cmd tpe) =- case tpe of- NewOp op cb ->- let sm = Op.pushOperation cb op $ _opModel st in- loopOpTransition st sm- SubscriptionCmd cmd -> subCmd st cmd- Abort ->- let sm = Op.abort $ _opModel st- rs = Sub.abort $ _subDriver st in- abortTransition st sm rs-- go st (Pkg pkg)- | packageCmd pkg == 0x01 =- let r_pkg = heartbeatResponsePackage $ packageCorrelation pkg in- Transmit r_pkg $ Await $ Processor $ go st- | otherwise =- let sm_m = Op.submitPackage pkg $ _opModel st in- case fmap (loopOpTransition st) sm_m of- Just nxt -> nxt- Nothing ->- case Sub.submitPackage pkg $ _subDriver st of- Nothing -> Await $ Processor $ go st- Just (r, nxt_drv) ->- let nxt_st = st { _subDriver = nxt_drv } in- Produce r $ Await $ Processor $ go nxt_st-- subCmd st@State{..} cmd =- case cmd of- ConnectStream k s tos ->- let (pkg, nxt_drv) = Sub.connectToStream k s tos _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt- ConnectPersist k g s b ->- let (pkg, nxt_drv) = Sub.connectToPersist k g s b _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt- Unsubscribe r ->- let (pkg, nxt_drv) = Sub.unsubscribe r _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt- CreatePersist k g s ss ->- let (pkg, nxt_drv) = Sub.createPersist k g s ss _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt- UpdatePersist k g s ss ->- let (pkg, nxt_drv) = Sub.updatePersist k g s ss _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt- DeletePersist k g s ->- let (pkg, nxt_drv) = Sub.deletePersist k g s _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt- AckPersist r run evts ->- let (pkg, nxt_drv) = Sub.ackPersist r run evts _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt- NakPersist r run act res evts ->- let (pkg, nxt_drv) = Sub.nakPersist r run act res evts- _subDriver- nxt_st = st { _subDriver = nxt_drv }- nxt = Processor $ go nxt_st in- Transmit pkg $ Await nxt------------------------------------------------------------------------------------- | Creates a new 'Processor' state-machine.-newProcessor :: Settings -> Generator -> Processor r-newProcessor setts gen = Processor $ execute $ initState setts gen
+ Database/EventStore/Internal/Settings.hs view
@@ -0,0 +1,149 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Settings+-- 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.Settings where++--------------------------------------------------------------------------------+import Network.Connection (TLSSettings)+import System.Metrics (Store)++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Logger+import Database.EventStore.Internal.Prelude++--------------------------------------------------------------------------------+-- Flag+--------------------------------------------------------------------------------+-- | Indicates either a 'Package' contains 'Credentials' data or not.+data Flag+ = None+ | Authenticated+ deriving Show++--------------------------------------------------------------------------------+-- | Maps a 'Flag' into a 'Word8' understandable by the server.+flagWord8 :: Flag -> Word8+flagWord8 None = 0x00+flagWord8 Authenticated = 0x01++--------------------------------------------------------------------------------+-- Credentials+--------------------------------------------------------------------------------+-- | Holds login and password information.+data Credentials+ = Credentials+ { credLogin :: !ByteString+ , credPassword :: !ByteString+ }+ deriving (Eq, Show)++--------------------------------------------------------------------------------+-- | Creates a 'Credentials' given a login and a password.+credentials :: ByteString -- ^ Login+ -> ByteString -- ^ Password+ -> Credentials+credentials = Credentials++--------------------------------------------------------------------------------+-- | Represents reconnection strategy.+data Retry+ = AtMost Int+ | KeepRetrying++--------------------------------------------------------------------------------+-- | Indicates how many times we should try to reconnect to the server. A value+-- less than or equal to 0 means no retry.+atMost :: Int -> Retry+atMost = AtMost++--------------------------------------------------------------------------------+-- | Indicates we should try to reconnect to the server until the end of the+-- Universe.+keepRetrying :: Retry+keepRetrying = KeepRetrying++--------------------------------------------------------------------------------+-- | Global 'Connection' settings+data Settings+ = Settings+ { s_heartbeatInterval :: NominalDiffTime+ -- ^ Maximum delay of inactivity before the client sends a heartbeat+ -- request.+ , s_heartbeatTimeout :: NominalDiffTime+ -- ^ Maximum delay the server has to issue a heartbeat response.+ , s_requireMaster :: Bool+ -- ^ On a cluster settings. Requires the master node when performing a+ -- write operation.+ , s_credentials :: Maybe Credentials+ -- ^ 'Credentials' used for an authenticated communication.+ , s_retry :: Retry+ -- ^ Retry strategy when failing to connect.+ , s_reconnect_delay :: NominalDiffTime+ -- ^ Delay before issuing a new connection request.+ , s_ssl :: Maybe TLSSettings+ -- ^ SSL settings.+ , s_loggerType :: LogType+ -- ^ Type of logging to use.+ , s_loggerFilter :: LoggerFilter+ -- ^ Restriction of what would be logged.+ , s_loggerDetailed :: Bool+ -- ^ Detailed logging output. Currently, it also indicates the location+ -- where the log occurred.+ , s_operationTimeout :: NominalDiffTime+ -- ^ 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.+ }++--------------------------------------------------------------------------------+-- | Default global settings.+--+-- * 's_heartbeatInterval' = 750 ms+-- * 's_heartbeatTimeout' = 1500 ms+-- * 's_requireMaster' = 'True'+-- * 's_credentials' = 'Nothing'+-- * 's_retry' = 'atMost' 3+-- * 's_reconnect_delay' = 3 seconds+-- * 's_ssl' = 'Nothing'+-- * 's_loggerType' = 'LogNone'+-- * 's_loggerFilter' = 'LoggerLevel' 'LevelInfo'+-- * 's_loggerDetailed' = 'False'+-- * 's_operationTimeout' = 10 seconds+-- * 's_operationRetry' = 'atMost' 3+-- * 's_monitoring' = 'Nothing'+defaultSettings :: Settings+defaultSettings = Settings+ { s_heartbeatInterval = msDiffTime 750 -- 750ms+ , s_heartbeatTimeout = msDiffTime 1500 -- 1500ms+ , s_requireMaster = True+ , s_credentials = Nothing+ , s_retry = atMost 3+ , s_reconnect_delay = 3+ , s_ssl = Nothing+ , s_loggerType = LogNone+ , s_loggerFilter = LoggerLevel LevelInfo+ , s_loggerDetailed = False+ , s_operationTimeout = 10 -- secs+ , s_operationRetry = atMost 3+ , s_monitoring = Nothing+ }++--------------------------------------------------------------------------------+-- | Default SSL settings based on 'defaultSettings'.+defaultSSLSettings :: TLSSettings -> Settings+defaultSSLSettings tls = defaultSettings { s_ssl = Just tls }++--------------------------------------------------------------------------------+-- | Millisecond timespan+msDiffTime :: Float -> NominalDiffTime+msDiffTime n = fromRational $ toRational (n / 1000)
+ Database/EventStore/Internal/Stopwatch.hs view
@@ -0,0 +1,54 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Stopwatch+-- 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.Stopwatch+ ( Stopwatch+ , newStopwatch+ , stopwatchElapsed+ ) where++--------------------------------------------------------------------------------+import Data.Time++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude++--------------------------------------------------------------------------------+data Internal =+ Internal { _lastTime :: !UTCTime+ , _acc :: !NominalDiffTime+ }++--------------------------------------------------------------------------------+initInternal :: UTCTime -> Internal+initInternal now = Internal now 0++--------------------------------------------------------------------------------+update :: UTCTime -> Internal -> Internal+update now (Internal before acc) = Internal now acc'+ where+ acc' = acc + diffUTCTime now before++--------------------------------------------------------------------------------+newtype Stopwatch = Stopwatch (MVar Internal)++--------------------------------------------------------------------------------+newStopwatch :: MonadBase IO m => m Stopwatch+newStopwatch =+ fmap Stopwatch . newMVar . initInternal =<< liftBase getCurrentTime++--------------------------------------------------------------------------------+stopwatchElapsed :: MonadBaseControl IO m => Stopwatch -> m NominalDiffTime+stopwatchElapsed (Stopwatch var) =+ modifyMVar var $ \prev -> do+ now <- liftBase getCurrentTime+ let next = update now prev+ return (next, _acc next)
Database/EventStore/Internal/Stream.hs view
@@ -1,6 +1,7 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE OverloadedStrings #-} -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore.Internal.Stream@@ -15,7 +16,7 @@ module Database.EventStore.Internal.Stream where ---------------------------------------------------------------------------------import ClassyPrelude+import Database.EventStore.Internal.Prelude -------------------------------------------------------------------------------- -- | A stream can either point to $all or a regular one.@@ -26,6 +27,16 @@ data StreamName = StreamName Text | AllStream deriving Eq --------------------------------------------------------------------------------+streamNameRaw :: StreamName -> Text+streamNameRaw (StreamName n) = n+streamNameRaw AllStream = ""++-------------------------------------------------------------------------------- instance Show StreamName where show (StreamName t) = show t show AllStream = "$all"++--------------------------------------------------------------------------------+instance IsString StreamName where+ fromString "$all" = AllStream+ fromString stream = StreamName $ pack stream
− Database/EventStore/Internal/Subscription.hs
@@ -1,810 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Internal.Subscription--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Main subscription state machine declaration module. It also declares every--- functions required to drive a 'Subscription'.----------------------------------------------------------------------------------module Database.EventStore.Internal.Subscription- ( Regular(..)- , Persistent(..)- , Catchup(..)- , CatchupParams(..)- , Running(..)- , Checkpoint(..)- , Subscription- , SubscriptionId- , SubscriptionClosed(..)- , SubEnv(..)- , PushCmd(..)- , AckCmd(..)- , catchupSub- , regularSub- , persistentSub- , hasCaughtUp- , getSubId- , getSubStream- , unsubscribe- , isSubscribedToAll- , getSubLastCommitPos- , getSubLastEventNumber- , nextEventMaybeSTM- , nextEvent- , nextEventMaybe- , waitConfirmation- , getSubResolveLinkTos- , waitTillCatchup- , hasCaughtUpSTM- , notifyEventsProcessed- , acknowledge- , acknowledgeEvents- , failed- , eventsFailed- , notifyEventsFailed- , unsubscribeConfirmed- , waitUnsubscribeConfirmed- , unsubscribeConfirmedSTM- ) where-----------------------------------------------------------------------------------import Data.Int-----------------------------------------------------------------------------------import ClassyPrelude-import Data.Sequence (ViewL(..), viewl, dropWhileL)-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.EndPoint-import Database.EventStore.Internal.Manager.Subscription.Driver hiding (unsubscribe)-import Database.EventStore.Internal.Manager.Subscription.Model-import Database.EventStore.Internal.Operation-import Database.EventStore.Internal.Operation.Catchup-import Database.EventStore.Internal.Types------------------------------------------------------------------------------------- | Also referred as volatile subscription. For example, if a stream has 100--- events in it when a subscriber connects, the subscriber can expect to see--- event number 101 onwards until the time the subscription is closed or--- dropped.-data Regular = Regular { _subTos :: Bool }------------------------------------------------------------------------------------- | This kind of subscription specifies a starting point, in the form of an--- event number or transaction file position. The given function will be--- called for events from the starting point until the end of the stream, and--- then for subsequently written events.------ For example, if a starting point of 50 is specified when a stream has 100--- events in it, the subscriber can expect to see events 51 through 100, and--- then any events subsequently written until such time as the subscription is--- dropped or closed.-data Catchup = Catchup------------------------------------------------------------------------------------- | 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 Persistent = Persistent { _perGroup :: Text }------------------------------------------------------------------------------------- | Represents the different type of inputs a subscription state-machine can--- handle.-data Input t a where- -- A event has written to the stream. Subscription state machine should- -- store that event withing its state.- Arrived :: ResolvedEvent -> Input t (SubStateMachine t)- -- The user asks for the next event coming from the server.- ReadNext :: Input t (Maybe (ResolvedEvent, SubStateMachine t))- -- A batch read has been made. It's only use for 'Catchup' subscription- -- type. It gives the list of read events and indicates if it reaches the- -- end of the stream along with the next checkpoint to point at.- BatchRead :: [ResolvedEvent]- -> Bool- -> Checkpoint- -> Input Catchup (SubStateMachine Catchup)- -- Used only for 'Catchup' subscription type. Asks if the subscription- -- read every events up to the checkpoint given by the user.- CaughtUp :: Input Catchup Bool-- -- Returns the last event number read by the user.- LastEventNum :: Input Catchup (Int32, Maybe Position)------------------------------------------------------------------------------------- | Main subscription state machine.-newtype SubStateMachine t = SubStateMachine (forall a. Input t a -> a)-----------------------------------------------------------------------------------type PushOp a = (Either OperationError a -> IO ()) -> Operation a -> IO ()-----------------------------------------------------------------------------------type PushConnect = (SubConnectEvent -> IO ()) -> PushCmd -> IO ()-----------------------------------------------------------------------------------data PushCmd- = PushRegular Text Bool- | PushPersistent Text Text Int32-----------------------------------------------------------------------------------data AckCmd = AckCmd | NakCmd NakAction (Maybe Text)-----------------------------------------------------------------------------------data SubEnv =- SubEnv { subSettings :: Settings- , subPushOp :: forall a. PushOp a- , subPushConnect :: PushConnect- , subPushUnsub :: Running -> IO ()- , subAckCmd :: AckCmd -> Running -> [UUID] -> IO ()- , subForceReconnect :: NodeEndPoints -> IO ()- }-----------------------------------------------------------------------------------data SubState t- = SubOnline (SubStateMachine t)- | SubDropped SubDropReason- | forall e. Exception e => SubException e- | SubUserUnsubscribed------------------------------------------------------------------------------------- | Modifies 'SubState' internal state machine, letting any 'SubDropReason'--- untouched.-modifySubSM :: (SubStateMachine a -> SubStateMachine a)- -> SubState a- -> SubState a-modifySubSM k (SubOnline sm) = SubOnline (k sm)-modifySubSM _ s = s-----------------------------------------------------------------------------------data CatchupParams =- CatchupParams { catchupResLnkTos :: !Bool- , catchupState :: !CatchupState- , catchupBatchSize :: !(Maybe Int32)- }------------------------------------------------------------------------------------- | Represents a subscription life cycle.-data SubLifeCycle a =- SubLifeCycle- {- onConfirm :: Running -> IO ()- -- ^ When the server's confirmed this subscription's been created.- , readState :: STM (SubState a)- -- ^ Reads this subscription internal state.- , writeState :: SubState a -> STM ()- -- ^ Modifies this subscription internal state.- , onError :: SubDropReason -> IO ()- -- ^ When an error's occured.- , onUserUnsubscribed :: IO ()- -- ^ When the server confirmed the subscription is no longer live.- -- This action is triggered because the user asks to unsubscribe.- , retrySub :: IO ()- -- ^ Retry the all subscription, this behavior is transparent to the user.- }------------------------------------------------------------------------------------- | It's possible to subscribe to a stream and be notified when new events are--- written to that stream. There are three types of subscription which are--- available, all of which can be useful in different situations.------ * 'Regular'------ * 'Catchup'------ * 'Persistent'-data Subscription t =- Subscription { subStream :: Text- , subLifeCycle :: SubLifeCycle t- , subEnv :: SubEnv- , subRun :: TMVar Running- , subType :: t- }------------------------------------------------------------------------------------- | This exception is raised when the user tries to get the next event from a--- 'Subscription' that is already closed.-data SubscriptionClosed- = SubscriptionClosed SubDropReason- | SubscriptionUnsubscribedByUser- deriving (Show, Typeable)-----------------------------------------------------------------------------------instance Exception SubscriptionClosed-----------------------------------------------------------------------------------catchupSub :: SubEnv -> CatchupParams -> IO (Subscription Catchup)-catchupSub env params = do- mvarRun <- newEmptyTMVarIO- mvarState <- newEmptyTMVarIO-- let streamId = catchupStreamName $ catchupState params- pushCmd = PushRegular streamId (catchupResLnkTos params)- lcycle =- SubLifeCycle- { onConfirm = confirmSub mvarRun- , readState = readTMVar mvarState- , writeState = \s -> () <$ swapTMVar mvarState s- , onError = \e ->- case e of- SubAborted ->- tryRetryCatcupSubscription pushCmd env mvarState- lcycle params- SubNotHandled reason infoM ->- subNotHandledMsg env lcycle reason infoM- _ -> atomically $ do- s <- takeTMVar mvarState- case s of- SubOnline{} -> putTMVar mvarState $ SubDropped e- _ -> putTMVar mvarState s- , onUserUnsubscribed = atomically $ do- _ <- takeTMVar mvarState- putTMVar mvarState SubUserUnsubscribed- , retrySub = tryRetryCatcupSubscription pushCmd env mvarState- lcycle params- }-- op = createCatchupOperation env params-- subPushOp env (catchupOpEventHandler mvarState) op- subPushConnect env (subEventHandler lcycle) pushCmd- return $ Subscription streamId lcycle env mvarRun Catchup-----------------------------------------------------------------------------------tryRetryCatcupSubscription :: PushCmd- -> SubEnv- -> TMVar (SubState Catchup)- -> SubLifeCycle Catchup- -> CatchupParams- -> IO ()-tryRetryCatcupSubscription pushCmd env mvarState lcycle params = do- state <- atomically $ readTMVar mvarState-- case state of- -- In this case, we do our best to re-engage the- -- catchup subscription where it was at before- -- losing the connection with the server.- SubOnline sm -> do- let (num, posM) = lastEventNumSM sm- newStart =- case catchupState params of- RegularCatchup stream _ ->- RegularCatchup stream num- AllCatchup{} ->- case posM of- Just (Position npc npp) ->- AllCatchup npc npp- _ -> catchupState params-- newParams = params { catchupState = newStart }-- newOp = createCatchupOperation env newParams-- subPushOp env (catchupOpEventHandler mvarState)- newOp- subPushConnect env (subEventHandler lcycle)- pushCmd- _ -> return ()-----------------------------------------------------------------------------------regularSub :: SubEnv -> Text -> Bool -> IO (Subscription Regular)-regularSub env streamId resLnkTos = do- mvarRun <- newEmptyTMVarIO- varState <- newTVarIO $ SubOnline regularSubscription- let lcycle =- SubLifeCycle- { onConfirm = confirmSub mvarRun- , readState = readTVar varState- , writeState = writeTVar varState- , onError = \r -> atomically $ do- s <- readTVar varState- case s of- SubOnline{} -> writeTVar varState $ SubDropped r- _ -> return ()- , onUserUnsubscribed =- atomically $ writeTVar varState SubUserUnsubscribed- , retrySub = do- atomically $ do- state <- readState lcycle- case state of- SubOnline{} -> return ()- _ -> writeState lcycle $ SubOnline regularSubscription- subPushConnect env (subEventHandler lcycle)- (PushRegular streamId resLnkTos)- }-- subPushConnect env (subEventHandler lcycle) (PushRegular streamId resLnkTos)- return $ Subscription streamId lcycle env mvarRun (Regular resLnkTos)-----------------------------------------------------------------------------------persistentSub :: SubEnv -> Text -> Text -> Int32 -> IO (Subscription Persistent)-persistentSub env grp stream bufSize = do- mvarRun <- newEmptyTMVarIO- varState <- newTVarIO $ SubOnline persistentSubscription- let lcycle =- SubLifeCycle- { onConfirm = confirmSub mvarRun- , readState = readTVar varState- , writeState = writeTVar varState- , onError = \r -> atomically $ do- s <- readTVar varState- case s of- SubOnline{} -> writeTVar varState $ SubDropped r- _ -> return ()- , onUserUnsubscribed =- atomically $ writeTVar varState SubUserUnsubscribed- , retrySub = do- atomically $ writeState lcycle- $ SubOnline persistentSubscription- subPushConnect env (subEventHandler lcycle) pushCmd- }-- pushCmd = PushPersistent grp stream bufSize-- subPushConnect env (subEventHandler lcycle) pushCmd- return $ Subscription stream lcycle env mvarRun (Persistent grp)------------------------------------------------------------------------------------- | Makes sure to not cause deadlock because the subscription already been--- confirmed but because of a connection drop, need to be recconfirmed again.-confirmSub :: TMVar Running -> Running -> IO ()-confirmSub mvarRun r = atomically $ do- emptyVar <- isEmptyTMVar mvarRun- if emptyVar- then putTMVar mvarRun r- else () <$ swapTMVar mvarRun r-----------------------------------------------------------------------------------subNotHandledMsg :: SubEnv- -> SubLifeCycle s- -> NotHandledReason- -> Maybe MasterInfo- -> IO ()-subNotHandledMsg env _ N_NotMaster (Just info) =- subForceReconnect env $ masterInfoNodeEndPoints info-subNotHandledMsg _ lcycle N_NotMaster _ =- atomically $ writeState lcycle- $ SubDropped $ SubServerError (Just msg)- where- msg = "Been asked to connect to new master node \- \ but no master info been sent."-subNotHandledMsg _ lcycle N_NotReady _ = retrySub lcycle-subNotHandledMsg _ lcycle N_TooBusy _ = retrySub lcycle-----------------------------------------------------------------------------------createCatchupOperation :: SubEnv -> CatchupParams -> Operation CatchupOpResult-createCatchupOperation env params =- catchup (subSettings env)- (catchupState params)- (catchupResLnkTos params)- (catchupBatchSize params)------------------------------------------------------------------------------------- | We want to notify the user that something went wrong in the first phase of--- a catchup subscription (e.g. reading the stream forward until we catchup to--- stream's end). This prevents a deadlock on user side in case where the user--- calls `waitTillCatchup` on a stream that doesn't exist.-catchupOpEventHandler :: TMVar (SubState Catchup)- -> Either OperationError CatchupOpResult- -> IO ()-catchupOpEventHandler mvarState (Left e) = atomically $ do- isEmpty <- isEmptyTMVar mvarState- if isEmpty- then putTMVar mvarState (SubException e)- else () <$ swapTMVar mvarState (SubException e)-catchupOpEventHandler mvarState (Right res) = atomically $ do- -- When a catchup subscription receives events for the- -- first time.- whenM (isEmptyTMVar mvarState) $ do- let initState = SubOnline catchupSubscription- putTMVar mvarState initState-- subState <- takeTMVar mvarState- let cmd = batchReadSM (catchupReadEvents res)- (catchupEndOfStream res)- (catchupCheckpoint res)-- nxtSubState = modifySubSM cmd subState-- putTMVar mvarState nxtSubState------------------------------------------------------------------------------------- | Subscription event handler. Used during a subscription lifetime.-subEventHandler :: SubLifeCycle a -> SubConnectEvent -> IO ()-subEventHandler lcycle (SubConfirmed run) = onConfirm lcycle run-subEventHandler lcycle (EventAppeared e) = atomically $ do- st <- readState lcycle- case st of- SubOnline sm ->- writeState lcycle $ SubOnline $ eventArrivedSM e sm- SubException _ ->- -- At this moment [07 October 2016], this can only happen during- -- the first phase of a catchup subscription where the user- -- asked for a subscription on a stream that doesn't exist.- return ()- _ -> error "Impossible: subEventHandler"-subEventHandler lcycle Unsubscribed = onUserUnsubscribed lcycle-subEventHandler lcycle (Dropped r) = onError lcycle r------------------------------------------------------------------------------------- | Submit a new event to the subscription state machine. Internally,--- that event should be stored into the subscription buffer.-eventArrivedSM :: ResolvedEvent -> SubStateMachine t -> SubStateMachine t-eventArrivedSM e (SubStateMachine k) = k (Arrived e)------------------------------------------------------------------------------------- | Reads the next available event. Returns 'Nothing' it there is any. When--- returning an event, it will be removed from the subscription buffer.-readNextSM :: SubStateMachine t -> Maybe (ResolvedEvent, SubStateMachine t)-readNextSM (SubStateMachine k) = k ReadNext------------------------------------------------------------------------------------- | Submits a list of events read from a stream. It's only used by a 'Catchup'--- subscription.-batchReadSM :: [ResolvedEvent]- -> Bool -- ^ If it reaches the end of the stream.- -> Checkpoint- -> SubStateMachine Catchup- -> SubStateMachine Catchup-batchReadSM es eos nxt (SubStateMachine k) = k (BatchRead es eos nxt)------------------------------------------------------------------------------------- | Indicates if the subscription caught up the end of the stream, meaning the--- subscription is actually live. Only used by 'Catchup' subscription.-hasCaughtUpSM :: SubStateMachine Catchup -> Bool-hasCaughtUpSM (SubStateMachine k) = k CaughtUp------------------------------------------------------------------------------------- | Last event number read by the user.-lastEventNumSM :: SubStateMachine Catchup -> (Int32, Maybe Position)-lastEventNumSM (SubStateMachine k) = k LastEventNum------------------------------------------------------------------------------------- | Main 'Regular' subscription state machine.-regularSubscription :: SubStateMachine Regular-regularSubscription = baseSubStateMachine------------------------------------------------------------------------------------- | Main 'Persistent' subscription state machine.-persistentSubscription :: SubStateMachine Persistent-persistentSubscription = baseSubStateMachine------------------------------------------------------------------------------------- | Depending either if the subscription concerns a regular stream or $all,--- indicates if an event number (or 'Position') is lesser that the current the--- given 'CheckPoint'.-beforeChk :: Checkpoint -> ResolvedEvent -> Bool-beforeChk (CheckpointNumber num) re =- recordedEventNumber (resolvedEventOriginal re) < num-beforeChk (CheckpointPosition pos) re =- maybe False (< pos) $ resolvedEventPosition re------------------------------------------------------------------------------------- | This data structure is only used by catchup subscription state machine.-data CatchupSMState =- CatchupSMState { csmReadSeq :: !(Seq ResolvedEvent)- -- ^ This sequence is used to pack events coming from reading- -- a stream forward.- , csmLiveSeq :: !(Seq ResolvedEvent)- -- ^ This sequence is used to pack events coming from live- -- subscription.- , csmLastNum :: !(Maybe Int32)- -- ^ Tracks the last event read.- , csmLastPos :: !(Maybe Position)- }-----------------------------------------------------------------------------------initialCatchupSMState :: CatchupSMState-initialCatchupSMState = CatchupSMState empty empty Nothing Nothing-----------------------------------------------------------------------------------insertReadEvents :: [ResolvedEvent]- -> Checkpoint- -> CatchupSMState- -> CatchupSMState-insertReadEvents es chp s = result- where- temp = s { csmReadSeq = foldl' snoc (csmReadSeq s) es- , csmLiveSeq = dropWhileL (beforeChk chp) (csmLiveSeq s)- }-- result =- case chp of- CheckpointNumber n -> temp { csmLastNum = Just n }- CheckpointPosition p -> temp { csmLastPos = Just p }-----------------------------------------------------------------------------------insertLiveEvent :: ResolvedEvent -> CatchupSMState -> CatchupSMState-insertLiveEvent e s = s { csmLiveSeq = csmLiveSeq s `snoc` e }-----------------------------------------------------------------------------------readNextFromBatchSeq :: CatchupSMState -> Maybe (ResolvedEvent, CatchupSMState)-readNextFromBatchSeq s =- case viewl $ csmReadSeq s of- EmptyL -> Nothing- e :< rest ->- let newLast = recordedEventNumber $ resolvedEventOriginal e- nxtS = s { csmReadSeq = rest- , csmLastNum = Just newLast- , csmLastPos = resolvedEventPosition e- } in- Just (e, nxtS)-----------------------------------------------------------------------------------readNextFromLiveSeq :: CatchupSMState -> Maybe (ResolvedEvent, CatchupSMState)-readNextFromLiveSeq s =- case viewl $ csmLiveSeq s of- EmptyL -> Nothing- e :< rest ->- let newLast = recordedEventNumber $ resolvedEventOriginal e- nxtS = s { csmLiveSeq = rest- , csmLastNum = Just newLast- , csmLastPos = resolvedEventPosition e- } in- Just (e, nxtS)-----------------------------------------------------------------------------------lastEventNumber :: CatchupSMState -> (Int32, Maybe Position)-lastEventNumber s = (fromMaybe 0 $ csmLastNum s, csmLastPos s)-----------------------------------------------------------------------------------isBatchReqEmpty :: CatchupSMState -> Bool-isBatchReqEmpty s =- case viewl $ csmReadSeq s of- EmptyL -> True- _ -> False------------------------------------------------------------------------------------- | That subscription state machine accumulates events coming from batch read--- and any real time change made on a stream. That state machine will not--- served any recent change made on the stream until it reaches the end of the--- stream. On every batch read, it makes sure events contained in that batch--- are deleted from the subscription buffer in order to avoid duplicates. That--- implemention has been chosen to avoid potential message lost between the--- moment with reach the end of the stream and the delay required by asking--- for a subscription.-catchupSubscription :: SubStateMachine Catchup-catchupSubscription = SubStateMachine $ catchingUp initialCatchupSMState- where- catchingUp :: forall a. CatchupSMState -> Input Catchup a -> a- catchingUp s (Arrived e) =- SubStateMachine $ catchingUp $ insertLiveEvent e s- catchingUp s ReadNext =- let _F (e, sm) = (e, SubStateMachine $ catchingUp sm) in- fmap _F $ readNextFromBatchSeq s- catchingUp s (BatchRead es eos chk) =- let nxtS = insertReadEvents es chk s in- SubStateMachine $- if eos- then caughtUp nxtS- else catchingUp nxtS- catchingUp _ CaughtUp = False- catchingUp s LastEventNum = lastEventNumber s-- caughtUp :: forall a. CatchupSMState -> Input Catchup a -> a- caughtUp s (Arrived e) = SubStateMachine $ caughtUp $ insertLiveEvent e s- caughtUp s ReadNext =- case readNextFromBatchSeq s of- Nothing -> live s ReadNext- Just (e, nxtS) ->- if isBatchReqEmpty nxtS- then Just (e, SubStateMachine $ live s)- else Just (e, SubStateMachine $ caughtUp nxtS)- caughtUp s input@BatchRead{} = catchingUp s input- caughtUp _ CaughtUp = False- caughtUp s LastEventNum = lastEventNumber s-- live :: forall a. CatchupSMState -> Input Catchup a -> a- live s (Arrived e) = SubStateMachine $ live $ insertLiveEvent e s- live s ReadNext =- let _F (e, sm) = (e, SubStateMachine $ live sm) in- fmap _F $ readNextFromLiveSeq s- live s BatchRead{} = SubStateMachine $ live s- live _ CaughtUp = True- live s LastEventNum = lastEventNumber s------------------------------------------------------------------------------------- | Base subscription used for 'Regular' or 'Persistent' subscription.-baseSubStateMachine :: forall t. SubStateMachine t-baseSubStateMachine = SubStateMachine $ go empty- where- go :: forall a. Seq ResolvedEvent -> Input t a -> a- go s (Arrived e) = SubStateMachine $ go (s `snoc` e)- go s ReadNext =- case viewl s of- EmptyL -> Nothing- e :< rest -> Just (e, SubStateMachine $ go rest)- go _ _ = error "impossible: base subscription"------------------------------------------------------------------------------------- Subscription API------------------------------------------------------------------------------------ | Represents a subscription id.-newtype SubscriptionId = SubId UUID deriving (Eq, Ord, Show)------------------------------------------------------------------------------------- | Gets the ID of the subscription.-getSubId :: Subscription a -> IO SubscriptionId-getSubId Subscription{..} = atomically $ do- run <- readTMVar subRun- return $ SubId $ runningUUID run------------------------------------------------------------------------------------- | Gets the subscription stream name.-getSubStream :: Subscription a -> Text-getSubStream = subStream------------------------------------------------------------------------------------- | Asynchronously unsubscribe from the the stream.-unsubscribe :: Subscription a -> IO ()-unsubscribe Subscription{..} = do- run <- atomically $ readTMVar subRun- subPushUnsub subEnv run------------------------------------------------------------------------------------- | If the subscription is on the $all stream.-isSubscribedToAll :: Subscription a -> Bool-isSubscribedToAll = (== "") . getSubStream------------------------------------------------------------------------------------- | The last commit position seen on the subscription (if this a subscription--- to $all stream).-getSubLastCommitPos :: Subscription a -> IO Int64-getSubLastCommitPos Subscription{..} = atomically $ do- run <- readTMVar subRun- return $ runningLastCommitPosition run------------------------------------------------------------------------------------- | The last event number seen on the subscription (if this is a subscription--- to a single stream).-getSubLastEventNumber :: Subscription a -> IO (Maybe Int32)-getSubLastEventNumber Subscription{..} = atomically $ do- run <- readTMVar subRun- return $ runningLastEventNumber run------------------------------------------------------------------------------------- | Asks for the next incoming event like 'nextEventMaybe' while still being--- in the the 'STM'.-nextEventMaybeSTM :: Subscription a -> STM (Maybe ResolvedEvent)-nextEventMaybeSTM Subscription{..} = do- st <- readState subLifeCycle- case st of- SubException e -> throwSTM e- SubDropped r -> throwSTM $ SubscriptionClosed r- SubOnline sub -> do- case readNextSM sub of- Just (e, nxt) ->- Just e <$ writeState subLifeCycle (SubOnline nxt)- _ -> return Nothing- SubUserUnsubscribed -> throwSTM SubscriptionUnsubscribedByUser------------------------------------------------------------------------------------- | Awaits for the next event.-nextEvent :: Subscription a -> IO ResolvedEvent-nextEvent sub = atomically $ do- m <- nextEventMaybeSTM sub- case m of- Nothing -> retrySTM- Just e -> return e------------------------------------------------------------------------------------- | Non blocking version of 'nextEvent'.-nextEventMaybe :: Subscription a -> IO (Maybe ResolvedEvent)-nextEventMaybe = atomically . nextEventMaybeSTM------------------------------------------------------------------------------------- | Waits until the `Subscription` has been confirmed.-waitConfirmation :: Subscription a -> IO ()-waitConfirmation s = atomically $ do- _ <- readTMVar $ subRun s- return ()------------------------------------------------------------------------------------- | Determines whether or not any link events encontered in the stream will be--- resolved.-getSubResolveLinkTos :: Subscription Regular -> Bool-getSubResolveLinkTos = _subTos . subType------------------------------------------------------------------------------------- | Non blocking version of `waitTillCatchup`.-hasCaughtUp :: Subscription Catchup -> IO Bool-hasCaughtUp sub = atomically $ hasCaughtUpSTM sub------------------------------------------------------------------------------------- | Waits until 'CatchupSubscription' subscription catch-up its stream.-waitTillCatchup :: Subscription Catchup -> IO ()-waitTillCatchup sub = atomically $- unlessM (hasCaughtUpSTM sub) retrySTM------------------------------------------------------------------------------------- | Like 'hasCaughtUp' but lives in 'STM' monad.-hasCaughtUpSTM :: Subscription Catchup -> STM Bool-hasCaughtUpSTM Subscription{..} = do- res <- readState subLifeCycle- case res of- SubOnline sm -> return $ hasCaughtUpSM sm- SubException e -> throwSTM e- SubDropped r -> throwSTM $ SubscriptionClosed r- SubUserUnsubscribed -> throwSTM SubscriptionUnsubscribedByUser------------------------------------------------------------------------------------- | Like 'unsubscribeConfirmed' but lives in 'STM' monad.-unsubscribeConfirmedSTM :: Subscription a -> STM Bool-unsubscribeConfirmedSTM Subscription{..} = do- res <- readState subLifeCycle- case res of- SubOnline _ -> return False- SubException e -> throwSTM e- SubDropped r -> throwSTM $ SubscriptionClosed r- SubUserUnsubscribed -> return True------------------------------------------------------------------------------------- | Non blocking version of `waitUnsubscribeConfirmed`.-unsubscribeConfirmed :: Subscription a -> IO Bool-unsubscribeConfirmed = atomically . unsubscribeConfirmedSTM------------------------------------------------------------------------------------- | Wait until unsubscription has been confirmed by the server.-waitUnsubscribeConfirmed :: Subscription a -> IO ()-waitUnsubscribeConfirmed sub = atomically $- unlessM (unsubscribeConfirmedSTM sub) retrySTM------------------------------------------------------------------------------------- | Acknowledges those event ids have been successfully processed.-notifyEventsProcessed :: Subscription Persistent -> [UUID] -> IO ()-notifyEventsProcessed Subscription{..} evts = do- run <- atomically $ readTMVar subRun- subAckCmd subEnv AckCmd run evts------------------------------------------------------------------------------------- | Acknowledges that 'ResolvedEvent' has been successfully processed.-acknowledge :: Subscription Persistent -> ResolvedEvent -> IO ()-acknowledge sub e = notifyEventsProcessed sub [resolvedEventOriginalId e]------------------------------------------------------------------------------------- | Acknowledges those 'ResolvedEvent's have been successfully processed.-acknowledgeEvents :: Subscription Persistent -> [ResolvedEvent] -> IO ()-acknowledgeEvents sub = notifyEventsProcessed sub . fmap resolvedEventOriginalId------------------------------------------------------------------------------------- | Mark a message that has failed processing. The server will take action--- based upon the action parameter.-failed :: Subscription Persistent- -> ResolvedEvent- -> NakAction- -> Maybe Text- -> IO ()-failed sub e a r = notifyEventsFailed sub a r [resolvedEventOriginalId e]------------------------------------------------------------------------------------- | Mark messages that have failed processing. The server will take action--- based upon the action parameter.-eventsFailed :: Subscription Persistent- -> [ResolvedEvent]- -> NakAction- -> Maybe Text- -> IO ()-eventsFailed sub evts a r =- notifyEventsFailed sub a r $ fmap resolvedEventOriginalId evts------------------------------------------------------------------------------------- | Acknowledges those event ids have failed to be processed successfully.-notifyEventsFailed :: Subscription Persistent- -> NakAction- -> Maybe Text- -> [UUID]- -> IO ()-notifyEventsFailed Subscription{..} act res evts = do- run <- atomically $ readTMVar subRun- subAckCmd subEnv (NakCmd act res) run evts
+ Database/EventStore/Internal/Subscription/Api.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Api+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+-- Main Subscription bookkeeping structure.+--------------------------------------------------------------------------------+module Database.EventStore.Internal.Subscription.Api where++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Callback+import Database.EventStore.Internal.Communication+import Database.EventStore.Internal.Control+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++ -- | Get subscription stream.+ subscriptionStream :: s -> StreamName++ -- | Asynchronously unsubscribe from the the stream.+ unsubscribe :: s -> IO ()++--------------------------------------------------------------------------------+-- | 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++--------------------------------------------------------------------------------+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)++--------------------------------------------------------------------------------+-- | If the subscription is on the $all stream.+isSubscribedToAll :: Subscription s => s -> Bool+isSubscribedToAll s =+ case subscriptionStream s of+ StreamName{} -> False+ _ -> True++--------------------------------------------------------------------------------+-- | 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
@@ -0,0 +1,197 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Catchup+-- 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.Subscription.Catchup where++--------------------------------------------------------------------------------+import Control.Monad.Fix++--------------------------------------------------------------------------------+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.Types++--------------------------------------------------------------------------------+data Phase+ = CatchingUp+ | Running SubDetails+ | Closed (Either SomeException SubDropReason)++--------------------------------------------------------------------------------+data CatchupTrack+ = CatchupRegular !(Maybe Int32)+ | CatchupAll !(Maybe Position)++--------------------------------------------------------------------------------+catchupTrack :: CatchupState -> CatchupTrack+catchupTrack RegularCatchup{} = CatchupRegular Nothing+catchupTrack AllCatchup{} = CatchupAll Nothing++--------------------------------------------------------------------------------+receivedAlready :: CatchupTrack -> ResolvedEvent -> Bool+receivedAlready (CatchupRegular old) e =+ maybe False (resolvedEventOriginalEventNumber e <=) old+receivedAlready (CatchupAll old) e =+ fromMaybe False ((<=) <$> resolvedEventPosition e <*> old)++--------------------------------------------------------------------------------+updateTrack :: ResolvedEvent -> CatchupTrack -> CatchupTrack+updateTrack e (CatchupRegular _) =+ CatchupRegular (Just $ resolvedEventOriginalEventNumber e)+updateTrack e (CatchupAll _) =+ CatchupAll (resolvedEventPosition e)++--------------------------------------------------------------------------------+-- | This kind of subscription specifies a starting point, in the form of an+-- event number or transaction file position. The given function will be+-- called for events from the starting point until the end of the stream, and+-- then for subsequently written events.+--+-- For example, if a starting point of 50 is specified when a stream has 100+-- events in it, the subscriber can expect to see events 51 through 100, and+-- then any events subsequently written until such time as the subscription is+-- dropped or closed.+data CatchupSubscription =+ CatchupSubscription { _catchupExec :: Exec+ , _catchupStream :: StreamName+ , _catchupPhase :: TVar Phase+ , _catchupTrack :: TVar CatchupTrack+ , _catchupNext :: STM (Maybe ResolvedEvent)+ }++--------------------------------------------------------------------------------+instance Subscription CatchupSubscription where+ nextEventMaybeSTM = _catchupNext++ getSubscriptionDetailsSTM s = do+ p <- readTVar (_catchupPhase s)+ case p of+ Running details -> return details+ Closed r -> throwClosed r+ _ -> retrySTM++ subscriptionStream = _catchupStream++ unsubscribe s = subUnsubscribe (_catchupExec s) s++--------------------------------------------------------------------------------+streamName :: CatchupState -> StreamName+streamName (RegularCatchup stream _) = StreamName stream+streamName _ = "$all"++--------------------------------------------------------------------------------+streamText :: StreamName -> Text+streamText (StreamName s) = s+streamText _ = ""++--------------------------------------------------------------------------------+newCatchupSubscription :: Exec+ -> Bool+ -> Maybe Int32+ -> CatchupState+ -> IO CatchupSubscription+newCatchupSubscription exec tos batch state = do+ phaseVar <- newTVarIO CatchingUp+ queue <- newTQueueIO+ track <- newTVarIO $ catchupTrack state++ let stream = streamName state+ sub = CatchupSubscription exec stream 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 (streamText stream) tos+ 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 tracker e) $ do+ writeTVar track (updateTrack e tracker)+ writeTQueue queue e+ ConnectionReset -> do+ tpe <- readTVarIO track+ let newState =+ case tpe of+ CatchupRegular old ->+ case old of+ Just n -> RegularCatchup (streamText stream) n+ _ -> state+ CatchupAll old ->+ case old of+ Just p -> AllCatchup p+ _ -> state++ newOp = catchup (execSettings exec) newState tos batch++ newCb <- mfix $ \self -> newCallback (callback self)+ publishWith exec (SubmitOperation newCb newOp)++ cb <- mfix $ \self -> newCallback (callback self)+ let op = catchup (execSettings exec) state tos batch+ 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 -> IO Bool+hasCaughtUp sub = atomically $ hasCaughtUpSTM sub++--------------------------------------------------------------------------------+-- | Waits until 'CatchupSubscription' subscription catch-up its stream.+waitTillCatchup :: CatchupSubscription -> IO ()+waitTillCatchup sub = atomically $ unlessM (hasCaughtUpSTM sub) retrySTM++--------------------------------------------------------------------------------+-- | Like 'hasCaughtUp' but lives in 'STM' monad.+hasCaughtUpSTM :: CatchupSubscription -> 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)
+ Database/EventStore/Internal/Subscription/Command.hs view
@@ -0,0 +1,113 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Command+-- Copyright : (C) 2016 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+--------------------------------------------------------------------------------+module Database.EventStore.Internal.Subscription.Command where++--------------------------------------------------------------------------------+import Data.ProtocolBuffers+import Data.Serialize++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Subscription.Types+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+data ServerMessage+ = LiveMsg !LiveMsg+ | ConfirmationMsg !ConfirmationMsg+ | ErrorMsg !ErrorMsg++--------------------------------------------------------------------------------+data LiveMsg+ = EventAppearedMsg !ResolvedEvent+ | PersistentEventAppearedMsg !ResolvedEvent+ | DroppedMsg !SubDropReason++--------------------------------------------------------------------------------+data ConfirmationMsg+ = RegularConfirmationMsg !Int64 !(Maybe Int32)+ | PersistentConfirmationMsg !Text !Int64 !(Maybe Int32)++--------------------------------------------------------------------------------+confirmationCommitPos :: ConfirmationMsg -> Int64+confirmationCommitPos (RegularConfirmationMsg pos _) = pos+confirmationCommitPos (PersistentConfirmationMsg _ pos _) = pos++--------------------------------------------------------------------------------+confirmationLastEventNum :: ConfirmationMsg -> Maybe Int32+confirmationLastEventNum (RegularConfirmationMsg _ num) = num+confirmationLastEventNum (PersistentConfirmationMsg _ _ num) = num++--------------------------------------------------------------------------------+confirmationPersistentSubId :: ConfirmationMsg -> Maybe Text+confirmationPersistentSubId (PersistentConfirmationMsg ident _ _) = Just ident+confirmationPersistentSubId _ = Nothing++--------------------------------------------------------------------------------+data ErrorMsg+ = BadRequestMsg !(Maybe Text)+ | NotHandledMsg !NotHandledReason !(Maybe MasterInfo)+ | NotAuthenticatedMsg !(Maybe Text)+ | UnknownMsg !(Maybe Command)++--------------------------------------------------------------------------------+decodeServerMessage :: Package -> ServerMessage+decodeServerMessage pkg = fromMaybe err go+ where+ err = ErrorMsg $ UnknownMsg $ Just $ packageCmd pkg+ go =+ case packageCmd pkg of+ cmd | cmd == streamEventAppearedCmd -> do+ msg <- maybeDecodeMessage $ packageData pkg+ let evt = newResolvedEventFromBuf $ getField+ $ streamResolvedEvent msg+ return $ LiveMsg $ EventAppearedMsg evt+ | cmd == persistentSubscriptionStreamEventAppearedCmd -> do+ msg <- maybeDecodeMessage $ packageData pkg+ let evt = newResolvedEvent $ getField $ psseaEvt msg+ return $ LiveMsg $ PersistentEventAppearedMsg evt+ | cmd == subscriptionConfirmationCmd -> do+ msg <- maybeDecodeMessage $ packageData pkg+ let lcp = getField $ subscribeLastCommitPos msg+ len = getField $ subscribeLastEventNumber msg+ return $ ConfirmationMsg $ RegularConfirmationMsg lcp len+ | cmd == persistentSubscriptionConfirmationCmd -> do+ msg <- maybeDecodeMessage $ packageData pkg+ let lcp = getField $ pscLastCommitPos msg+ sid = getField $ pscId msg+ len = getField $ pscLastEvtNumber msg+ return $ ConfirmationMsg $ PersistentConfirmationMsg sid lcp len+ | cmd == subscriptionDroppedCmd -> do+ msg <- maybeDecodeMessage $ packageData pkg+ let reason = fromMaybe D_Unsubscribed $ getField+ $ dropReason msg+ return $ LiveMsg $ DroppedMsg $ toSubDropReason reason+ | cmd == badRequestCmd ->+ return $ ErrorMsg $ BadRequestMsg $ packageDataAsText pkg+ | cmd == notAuthenticatedCmd ->+ return $ ErrorMsg $ NotAuthenticatedMsg $ packageDataAsText pkg+ | cmd == notHandledCmd -> do+ msg <- maybeDecodeMessage $ packageData pkg+ let info = fmap masterInfo $ getField+ $ notHandledAdditionalInfo msg+ reason = getField $ notHandledReason msg+ return $ ErrorMsg $ NotHandledMsg reason info+ | otherwise -> Nothing++--------------------------------------------------------------------------------+maybeDecodeMessage :: Decode a => ByteString -> Maybe a+maybeDecodeMessage bytes =+ case runGet decodeMessage bytes of+ Right a -> Just a+ _ -> Nothing
+ Database/EventStore/Internal/Subscription/Message.hs view
@@ -0,0 +1,413 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+#if __GLASGOW_HASKELL__ < 800+{-# OPTIONS_GHC -fcontext-stack=26 #-}+#else+{-# OPTIONS_GHC -freduction-depth=26 #-}+#endif+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Message+-- 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.Subscription.Message where++--------------------------------------------------------------------------------+import Data.Int++--------------------------------------------------------------------------------+import Data.DotNet.TimeSpan+import Data.ProtocolBuffers++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+-- | Stream subscription connection request.+data SubscribeToStream+ = SubscribeToStream+ { subscribeStreamId :: Required 1 (Value Text)+ , subscribeResolveLinkTos :: Required 2 (Value Bool)+ }+ deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Encode SubscribeToStream++--------------------------------------------------------------------------------+-- | 'SubscribeToStream' smart constructor.+subscribeToStream :: Text -> Bool -> SubscribeToStream+subscribeToStream stream_id res_link_tos =+ SubscribeToStream+ { subscribeStreamId = putField stream_id+ , subscribeResolveLinkTos = putField res_link_tos+ }++--------------------------------------------------------------------------------+-- | Stream subscription connection response.+data SubscriptionConfirmation+ = SubscriptionConfirmation+ { subscribeLastCommitPos :: Required 1 (Value Int64)+ , subscribeLastEventNumber :: Optional 2 (Value Int32)+ }+ deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode SubscriptionConfirmation++--------------------------------------------------------------------------------+-- | Serialized event sent by the server when a new event has been appended to a+-- stream.+data StreamEventAppeared+ = StreamEventAppeared+ { streamResolvedEvent :: Required 1 (Message ResolvedEventBuf) }+ deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode StreamEventAppeared++--------------------------------------------------------------------------------+-- | Represents the reason subscription drop happened.+data DropReason+ = D_Unsubscribed+ | D_AccessDenied+ | D_NotFound+ | D_PersistentSubscriptionDeleted+ | D_SubscriberMaxCountReached+ deriving (Enum, Eq, Show)++--------------------------------------------------------------------------------+-- | A message sent by the server when a subscription has been dropped.+data SubscriptionDropped+ = SubscriptionDropped+ { dropReason :: Optional 1 (Enumeration DropReason) }+ deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode SubscriptionDropped++--------------------------------------------------------------------------------+-- | A message sent to the server to indicate the user asked to end a+-- subscription.+data UnsubscribeFromStream = UnsubscribeFromStream deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Encode UnsubscribeFromStream++--------------------------------------------------------------------------------+-- | Create persistent subscription request.+data CreatePersistentSubscription =+ CreatePersistentSubscription+ { cpsGroupName :: Required 1 (Value Text)+ , cpsStreamId :: Required 2 (Value Text)+ , cpsResolveLinkTos :: Required 3 (Value Bool)+ , cpsStartFrom :: Required 4 (Value Int32)+ , cpsMsgTimeout :: Required 5 (Value Int32)+ , cpsRecordStats :: Required 6 (Value Bool)+ , cpsLiveBufSize :: Required 7 (Value Int32)+ , cpsReadBatchSize :: Required 8 (Value Int32)+ , cpsBufSize :: Required 9 (Value Int32)+ , cpsMaxRetryCount :: Required 10 (Value Int32)+ , cpsPreferRoundRobin :: Required 11 (Value Bool)+ , cpsChkPtAfterTime :: Required 12 (Value Int32)+ , cpsChkPtMaxCount :: Required 13 (Value Int32)+ , cpsChkPtMinCount :: Required 14 (Value Int32)+ , cpsSubMaxCount :: Required 15 (Value Int32)+ , cpsNamedConsStrategy :: Optional 16 (Value Text)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+-- | 'CreatePersistentSubscription' smart constructor.+_createPersistentSubscription :: Text+ -> Text+ -> PersistentSubscriptionSettings+ -> CreatePersistentSubscription+_createPersistentSubscription group stream sett =+ CreatePersistentSubscription+ { cpsGroupName = putField group+ , cpsStreamId = putField stream+ , cpsResolveLinkTos = putField $ psSettingsResolveLinkTos sett+ , cpsStartFrom = putField $ psSettingsStartFrom sett+ , cpsMsgTimeout = putField+ . fromIntegral+ . (truncate :: Double -> Int64)+ . totalMillis+ $ psSettingsMsgTimeout sett+ , cpsRecordStats = putField $ psSettingsExtraStats sett+ , cpsLiveBufSize = putField $ psSettingsLiveBufSize sett+ , cpsReadBatchSize = putField $ psSettingsReadBatchSize sett+ , cpsBufSize = putField $ psSettingsHistoryBufSize sett+ , cpsMaxRetryCount = putField $ psSettingsMaxRetryCount sett+ , cpsPreferRoundRobin = putField False+ , cpsChkPtAfterTime = putField+ . fromIntegral+ . (truncate :: Double -> Int64)+ . totalMillis+ $ psSettingsCheckPointAfter sett+ , cpsChkPtMaxCount = putField $ psSettingsMaxCheckPointCount sett+ , cpsChkPtMinCount = putField $ psSettingsMinCheckPointCount sett+ , cpsSubMaxCount = putField $ psSettingsMaxSubsCount sett+ , cpsNamedConsStrategy = putField $ Just strText+ }+ where+ strText = strategyText $ psSettingsNamedConsumerStrategy sett++--------------------------------------------------------------------------------+instance Encode CreatePersistentSubscription++--------------------------------------------------------------------------------+-- | Create persistent subscription outcome.+data CreatePersistentSubscriptionResult+ = CPS_Success+ | CPS_AlreadyExists+ | CPS_Fail+ | CPS_AccessDenied+ deriving (Enum, Eq, Show)++--------------------------------------------------------------------------------+-- | Create persistent subscription response.+data CreatePersistentSubscriptionCompleted =+ CreatePersistentSubscriptionCompleted+ { cpscResult :: Required 1 (Enumeration CreatePersistentSubscriptionResult)+ , cpscReason :: Optional 2 (Value Text)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode CreatePersistentSubscriptionCompleted++--------------------------------------------------------------------------------+-- | Delete persistent subscription request.+data DeletePersistentSubscription =+ DeletePersistentSubscription+ { dpsGroupName :: Required 1 (Value Text)+ , dpsStreamId :: Required 2 (Value Text)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Encode DeletePersistentSubscription++--------------------------------------------------------------------------------+-- | 'DeletePersistentSubscription' smart construction.+_deletePersistentSubscription :: Text -> Text -> DeletePersistentSubscription+_deletePersistentSubscription group_name stream_id =+ DeletePersistentSubscription+ { dpsGroupName = putField group_name+ , dpsStreamId = putField stream_id+ }++--------------------------------------------------------------------------------+-- | Delete persistent subscription outcome.+data DeletePersistentSubscriptionResult+ = DPS_Success+ | DPS_DoesNotExist+ | DPS_Fail+ | DPS_AccessDenied+ deriving (Enum, Eq, Show)++--------------------------------------------------------------------------------+-- | Delete persistent subscription response.+data DeletePersistentSubscriptionCompleted =+ DeletePersistentSubscriptionCompleted+ { dpscResult :: Required 1 (Enumeration DeletePersistentSubscriptionResult)+ , dpscReason :: Optional 2 (Value Text)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode DeletePersistentSubscriptionCompleted++--------------------------------------------------------------------------------+-- | Update persistent subscription request.+data UpdatePersistentSubscription =+ UpdatePersistentSubscription+ { upsGroupName :: Required 1 (Value Text)+ , upsStreamId :: Required 2 (Value Text)+ , upsResolveLinkTos :: Required 3 (Value Bool)+ , upsStartFrom :: Required 4 (Value Int32)+ , upsMsgTimeout :: Required 5 (Value Int32)+ , upsRecordStats :: Required 6 (Value Bool)+ , upsLiveBufSize :: Required 7 (Value Int32)+ , upsReadBatchSize :: Required 8 (Value Int32)+ , upsBufSize :: Required 9 (Value Int32)+ , upsMaxRetryCount :: Required 10 (Value Int32)+ , upsPreferRoundRobin :: Required 11 (Value Bool)+ , upsChkPtAfterTime :: Required 12 (Value Int32)+ , upsChkPtMaxCount :: Required 13 (Value Int32)+ , upsChkPtMinCount :: Required 14 (Value Int32)+ , upsSubMaxCount :: Required 15 (Value Int32)+ , upsNamedConsStrategy :: Optional 16 (Value Text)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+-- | 'UpdatePersistentSubscription' smart constructor.+_updatePersistentSubscription :: Text+ -> Text+ -> PersistentSubscriptionSettings+ -> UpdatePersistentSubscription+_updatePersistentSubscription group stream sett =+ UpdatePersistentSubscription+ { upsGroupName = putField group+ , upsStreamId = putField stream+ , upsResolveLinkTos = putField $ psSettingsResolveLinkTos sett+ , upsStartFrom = putField $ psSettingsStartFrom sett+ , upsMsgTimeout = putField+ . fromIntegral+ . (truncate :: Double -> Int64)+ . totalMillis+ $ psSettingsMsgTimeout sett+ , upsRecordStats = putField $ psSettingsExtraStats sett+ , upsLiveBufSize = putField $ psSettingsLiveBufSize sett+ , upsReadBatchSize = putField $ psSettingsReadBatchSize sett+ , upsBufSize = putField $ psSettingsHistoryBufSize sett+ , upsMaxRetryCount = putField $ psSettingsMaxRetryCount sett+ , upsPreferRoundRobin = putField False+ , upsChkPtAfterTime = putField+ . fromIntegral+ . (truncate :: Double -> Int64)+ . totalMillis+ $ psSettingsCheckPointAfter sett+ , upsChkPtMaxCount = putField $ psSettingsMaxCheckPointCount sett+ , upsChkPtMinCount = putField $ psSettingsMinCheckPointCount sett+ , upsSubMaxCount = putField $ psSettingsMaxSubsCount sett+ , upsNamedConsStrategy = putField $ Just strText+ }+ where+ strText = strategyText $ psSettingsNamedConsumerStrategy sett++--------------------------------------------------------------------------------+instance Encode UpdatePersistentSubscription++--------------------------------------------------------------------------------+-- | Update persistent subscription outcome.+data UpdatePersistentSubscriptionResult+ = UPS_Success+ | UPS_DoesNotExist+ | UPS_Fail+ | UPS_AccessDenied+ deriving (Enum, Eq, Show)++--------------------------------------------------------------------------------+-- | Update persistent subscription response.+data UpdatePersistentSubscriptionCompleted =+ UpdatePersistentSubscriptionCompleted+ { upscResult :: Required 1 (Enumeration UpdatePersistentSubscriptionResult)+ , upscReason :: Optional 2 (Value Text)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode UpdatePersistentSubscriptionCompleted++--------------------------------------------------------------------------------+-- | Connect to a persistent subscription request.+data ConnectToPersistentSubscription =+ ConnectToPersistentSubscription+ { ctsId :: Required 1 (Value Text)+ , ctsStreamId :: Required 2 (Value Text)+ , ctsAllowedInFlightMsgs :: Required 3 (Value Int32)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Encode ConnectToPersistentSubscription++--------------------------------------------------------------------------------+-- | 'ConnectToPersistentSubscription' smart constructor.+_connectToPersistentSubscription :: Text+ -> Text+ -> Int32+ -> ConnectToPersistentSubscription+_connectToPersistentSubscription sub_id stream_id all_fly_msgs =+ ConnectToPersistentSubscription+ { ctsId = putField sub_id+ , ctsStreamId = putField stream_id+ , ctsAllowedInFlightMsgs = putField all_fly_msgs+ }++--------------------------------------------------------------------------------+-- | Ack processed events request.+data PersistentSubscriptionAckEvents =+ PersistentSubscriptionAckEvents+ { psaeId :: Required 1 (Value Text)+ , psaeProcessedEvtIds :: Repeated 2 (Value ByteString)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Encode PersistentSubscriptionAckEvents++--------------------------------------------------------------------------------+-- | 'PersistentSubscriptionAckEvents' smart constructor.+persistentSubscriptionAckEvents :: Text+ -> [ByteString]+ -> PersistentSubscriptionAckEvents+persistentSubscriptionAckEvents sub_id evt_ids =+ PersistentSubscriptionAckEvents+ { psaeId = putField sub_id+ , psaeProcessedEvtIds = putField evt_ids+ }++--------------------------------------------------------------------------------+-- | Gathers every possible Nak actions.+data NakAction+ = NA_Unknown+ | NA_Park+ | NA_Retry+ | NA_Skip+ | NA_Stop+ deriving (Enum, Eq, Show)++--------------------------------------------------------------------------------+-- | Nak processed events request.+data PersistentSubscriptionNakEvents =+ PersistentSubscriptionNakEvents+ { psneId :: Required 1 (Value Text)+ , psneProcessedEvtIds :: Repeated 2 (Value ByteString)+ , psneMsg :: Optional 3 (Value Text)+ , psneAction :: Required 4 (Enumeration NakAction)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Encode PersistentSubscriptionNakEvents++--------------------------------------------------------------------------------+-- | 'PersistentSubscriptionNakEvents' smart constructor.+persistentSubscriptionNakEvents :: Text+ -> [ByteString]+ -> Maybe Text+ -> NakAction+ -> PersistentSubscriptionNakEvents+persistentSubscriptionNakEvents sub_id evt_ids msg action =+ PersistentSubscriptionNakEvents+ { psneId = putField sub_id+ , psneProcessedEvtIds = putField evt_ids+ , psneMsg = putField msg+ , psneAction = putField action+ }++--------------------------------------------------------------------------------+-- | Connection to persistent subscription response.+data PersistentSubscriptionConfirmation =+ PersistentSubscriptionConfirmation+ { pscLastCommitPos :: Required 1 (Value Int64)+ , pscId :: Required 2 (Value Text)+ , pscLastEvtNumber :: Optional 3 (Value Int32)+ } deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode PersistentSubscriptionConfirmation++--------------------------------------------------------------------------------+-- | Avalaible event sent by the server in the context of a persistent+-- subscription..+data PersistentSubscriptionStreamEventAppeared =+ PersistentSubscriptionStreamEventAppeared+ { psseaEvt :: Required 1 (Message ResolvedIndexedEvent) }+ deriving (Generic, Show)++--------------------------------------------------------------------------------+instance Decode PersistentSubscriptionStreamEventAppeared
+ Database/EventStore/Internal/Subscription/Packages.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE RecordWildCards #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Packages+-- 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.Subscription.Packages where++--------------------------------------------------------------------------------+import Data.Int++--------------------------------------------------------------------------------+import Data.ProtocolBuffers+import Data.Serialize+import Data.UUID++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Command+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Subscription.Types+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+-- | Creates a regular subscription connection 'Package'.+createConnectRegularPackage :: Settings -> UUID -> Text -> Bool -> Package+createConnectRegularPackage Settings{..} uuid stream tos =+ Package+ { packageCmd = subscribeToStreamCmd+ , packageCorrelation = uuid+ , packageData = runPut $ encodeMessage msg+ , packageCred = s_credentials+ }+ where+ msg = subscribeToStream stream tos++--------------------------------------------------------------------------------+-- | Creates a persistent subscription connection 'Package'.+createConnectPersistPackage :: Settings+ -> UUID+ -> Text+ -> Text+ -> Int32+ -> Package+createConnectPersistPackage Settings{..} uuid grp stream bufSize =+ Package+ { packageCmd = connectToPersistentSubscriptionCmd+ , packageCorrelation = uuid+ , packageData = runPut $ encodeMessage msg+ , packageCred = s_credentials+ }+ where+ msg = _connectToPersistentSubscription grp stream bufSize++--------------------------------------------------------------------------------+-- | Creates a persistent subscription 'Package'.+createPersistActionPackage :: Settings+ -> UUID+ -> Text+ -> Text+ -> PersistAction+ -> Package+createPersistActionPackage Settings{..} u grp strm tpe =+ Package+ { packageCmd = cmd+ , packageCorrelation = u+ , packageData = runPut msg+ , packageCred = s_credentials+ }+ where+ msg =+ case tpe of+ PersistCreate sett ->+ encodeMessage $ _createPersistentSubscription grp strm sett+ PersistUpdate sett ->+ encodeMessage $ _updatePersistentSubscription grp strm sett+ PersistDelete ->+ encodeMessage $ _deletePersistentSubscription grp strm+ cmd =+ case tpe of+ PersistCreate _ -> createPersistentSubscriptionCmd+ PersistUpdate _ -> updatePersistentSubscriptionCmd+ PersistDelete -> deletePersistentSubscriptionCmd++--------------------------------------------------------------------------------+-- | Creates Ack 'Package'.+createAckPackage :: Settings -> UUID -> Text -> [UUID] -> Package+createAckPackage Settings{..} corr sid eids =+ Package+ { packageCmd = persistentSubscriptionAckEventsCmd+ , packageCorrelation = corr+ , packageData = runPut $ encodeMessage msg+ , packageCred = s_credentials+ }+ where+ bytes = fmap (toStrict . toByteString) eids+ msg = persistentSubscriptionAckEvents sid bytes++--------------------------------------------------------------------------------+-- | Create Nak 'Package'.+createNakPackage :: Settings+ -> UUID+ -> Text+ -> NakAction+ -> Maybe Text+ -> [UUID]+ -> Package+createNakPackage Settings{..} corr sid act txt eids =+ Package+ { packageCmd = persistentSubscriptionNakEventsCmd+ , packageCorrelation = corr+ , packageData = runPut $ encodeMessage msg+ , packageCred = s_credentials+ }+ where+ bytes = fmap (toStrict . toByteString) eids+ msg = persistentSubscriptionNakEvents sid bytes txt act++--------------------------------------------------------------------------------+-- | Create an unsubscribe 'Package'.+createUnsubscribePackage :: UUID -> Package+createUnsubscribePackage uuid =+ Package+ { packageCmd = unsubscribeFromStreamCmd+ , packageCorrelation = uuid+ , packageData = runPut $ encodeMessage UnsubscribeFromStream+ , packageCred = Nothing+ }
+ Database/EventStore/Internal/Subscription/Persistent.hs view
@@ -0,0 +1,184 @@+{-# LANGUAGE RecordWildCards #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Persistent+-- 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.Subscription.Persistent where++--------------------------------------------------------------------------------+import Data.UUID++--------------------------------------------------------------------------------+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.Persist+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Stream+import Database.EventStore.Internal.Subscription.Api+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Subscription.Packages+import Database.EventStore.Internal.Subscription.Types+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+ , _perPhase :: TVar Phase+ , _perNext :: STM (Maybe ResolvedEvent)+ }++--------------------------------------------------------------------------------+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++ subscriptionStream = _perStream++ unsubscribe s = subUnsubscribe (_perExec s) s++--------------------------------------------------------------------------------+newPersistentSubscription :: Exec+ -> Text+ -> StreamName+ -> Int32+ -> IO PersistentSubscription+newPersistentSubscription exec grp stream bufSize = do+ phaseVar <- newTVarIO Pending+ queue <- newTQueueIO++ let name = streamNameRaw stream+ sub = PersistentSubscription exec stream 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++ 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)++ cb <- newCallback callback+ publishWith exec (SubmitOperation cb (persist grp name bufSize))+ return sub++--------------------------------------------------------------------------------+-- | 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 setts = execSettings _perExec+ uuid = subId details+ Just sid = subSubId details+ pkg = createAckPackage setts uuid sid evts+ publishWith _perExec (SendPackage pkg)++--------------------------------------------------------------------------------+-- | Acknowledges that 'ResolvedEvent' has been successfully processed.+acknowledge :: PersistentSubscription -> ResolvedEvent -> IO ()+acknowledge sub e = notifyEventsProcessed sub [resolvedEventOriginalId e]++--------------------------------------------------------------------------------+-- | Acknowledges those 'ResolvedEvent's have been successfully processed.+acknowledgeEvents :: PersistentSubscription -> [ResolvedEvent] -> IO ()+acknowledgeEvents sub = notifyEventsProcessed sub . fmap resolvedEventOriginalId++--------------------------------------------------------------------------------+-- | Mark a message that has failed processing. The server will take action+-- based upon the action parameter.+failed :: PersistentSubscription+ -> ResolvedEvent+ -> NakAction+ -> Maybe Text+ -> IO ()+failed sub e a r = notifyEventsFailed sub a r [resolvedEventOriginalId e]++--------------------------------------------------------------------------------+-- | Mark messages that have failed processing. The server will take action+-- based upon the action parameter.+eventsFailed :: PersistentSubscription+ -> [ResolvedEvent]+ -> NakAction+ -> Maybe Text+ -> IO ()+eventsFailed sub evts a r =+ notifyEventsFailed sub a r $ fmap resolvedEventOriginalId evts++--------------------------------------------------------------------------------+-- | 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 setts = execSettings _perExec+ uuid = subId details+ Just sid = subSubId details+ pkg = createNakPackage setts uuid sid act res evts+ publishWith _perExec (SendPackage pkg)
+ Database/EventStore/Internal/Subscription/Regular.hs view
@@ -0,0 +1,102 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Regular+-- 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.Subscription.Regular where++--------------------------------------------------------------------------------+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.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.Types++--------------------------------------------------------------------------------+data Phase+ = Pending+ | Running SubDetails+ | Closed (Either SomeException SubDropReason)++--------------------------------------------------------------------------------+-- | Also referred as volatile subscription. For example, if a stream has 100+-- events in it when a subscriber connects, the subscriber can expect to see+-- event number 101 onwards until the time the subscription is closed or+-- dropped.+data RegularSubscription =+ RegularSubscription { _regExec :: Exec+ , _regStream :: StreamName+ , _regPhase :: TVar Phase+ , _regNext :: STM (Maybe ResolvedEvent)+ }++--------------------------------------------------------------------------------+instance Subscription RegularSubscription 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++ subscriptionStream = _regStream++ unsubscribe s = subUnsubscribe (_regExec s) s++--------------------------------------------------------------------------------+newRegularSubscription :: Exec+ -> StreamName+ -> Bool+ -> IO RegularSubscription+newRegularSubscription exec stream tos = do+ phaseVar <- newTVarIO Pending+ queue <- newTQueueIO++ let name = streamNameRaw stream+ sub = RegularSubscription exec stream 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++ 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)++ cb <- newCallback callback+ publishWith exec (SubmitOperation cb (volatile name tos))+ return sub
+ Database/EventStore/Internal/Subscription/Types.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE DeriveDataTypeable #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Subscription.Types+-- Copyright : (C) 2016 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+--------------------------------------------------------------------------------+module Database.EventStore.Internal.Subscription.Types where++--------------------------------------------------------------------------------+import Data.UUID++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Subscription.Message+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+-- | Indicates why a subscription has been dropped.+data SubDropReason+ = SubUnsubscribed+ -- ^ Subscription connection has been closed by the user.+ | SubAccessDenied+ -- ^ The current user is not allowed to operate on the supplied stream.+ | SubNotFound+ -- ^ Given stream name doesn't exist.+ | SubPersistDeleted+ -- ^ Given stream is deleted.+ | SubAborted+ -- ^ Occurs when the user shutdown the connection from the server or if+ -- the connection to the server is no longer possible.+ | SubNotAuthenticated (Maybe Text)+ | SubServerError (Maybe Text)+ -- ^ Unexpected error from the server.+ | SubNotHandled !NotHandledReason !(Maybe MasterInfo)+ | SubClientError !Text+ | SubSubscriberMaxCountReached+ deriving (Show, Eq)++--------------------------------------------------------------------------------+toSubDropReason :: DropReason -> SubDropReason+toSubDropReason D_Unsubscribed = SubUnsubscribed+toSubDropReason D_NotFound = SubNotFound+toSubDropReason D_AccessDenied = SubAccessDenied+toSubDropReason D_PersistentSubscriptionDeleted = SubPersistDeleted+toSubDropReason D_SubscriberMaxCountReached = SubSubscriberMaxCountReached++--------------------------------------------------------------------------------+data SubscriptionClosed = SubscriptionClosed (Maybe SubDropReason)+ deriving (Show, Typeable)++--------------------------------------------------------------------------------+instance Exception SubscriptionClosed++--------------------------------------------------------------------------------+-- | Represents a subscription id.+newtype SubscriptionId = SubscriptionId UUID deriving (Eq, Ord, Show)++--------------------------------------------------------------------------------+-- | Subscription runtime details. Not useful for the user but at least it makes+-- Haddock documentation generation less ugly.+data SubDetails =+ SubDetails { subId :: !UUID+ , subCommitPos :: !Int64+ , subLastEventNum :: !(Maybe Int32)+ , subSubId :: !(Maybe Text)+ }++--------------------------------------------------------------------------------+-- | Type of persistent action.+data PersistAction+ = PersistCreate PersistentSubscriptionSettings+ | PersistUpdate PersistentSubscriptionSettings+ | PersistDelete++--------------------------------------------------------------------------------+-- | Enumerates all persistent action exceptions.+data PersistActionException+ = PersistActionFail+ -- ^ The action failed.+ | PersistActionAlreadyExist+ -- ^ Happens when creating a persistent subscription on a stream with a+ -- group name already taken.+ | PersistActionDoesNotExist+ -- ^ An operation tried to do something on a persistent subscription or a+ -- stream that don't exist.+ | PersistActionAccessDenied+ -- ^ The current user is not allowed to operate on the supplied stream or+ -- persistent subscription.+ | PersistActionAborted+ -- ^ That action has been aborted because the user shutdown the connection+ -- to the server or the connection to the server is no longer possible.+ deriving (Show, Typeable)++--------------------------------------------------------------------------------+instance Exception PersistActionException++--------------------------------------------------------------------------------+-- EventStore result mappers:+-- =========================+-- EventStore protocol has several values that means the exact same thing. Those+-- functions convert a specific EventStore to uniform result type common to all+-- persistent actions.+--------------------------------------------------------------------------------+createRException :: CreatePersistentSubscriptionResult+ -> Maybe PersistActionException+createRException CPS_Success = Nothing+createRException CPS_AlreadyExists = Just PersistActionAlreadyExist+createRException CPS_Fail = Just PersistActionFail+createRException CPS_AccessDenied = Just PersistActionAccessDenied++--------------------------------------------------------------------------------+deleteRException :: DeletePersistentSubscriptionResult+ -> Maybe PersistActionException+deleteRException DPS_Success = Nothing+deleteRException DPS_DoesNotExist = Just PersistActionDoesNotExist+deleteRException DPS_Fail = Just PersistActionFail+deleteRException DPS_AccessDenied = Just PersistActionAccessDenied++--------------------------------------------------------------------------------+updateRException :: UpdatePersistentSubscriptionResult+ -> Maybe PersistActionException+updateRException UPS_Success = Nothing+updateRException UPS_DoesNotExist = Just PersistActionDoesNotExist+updateRException UPS_Fail = Just PersistActionFail+updateRException UPS_AccessDenied = Just PersistActionAccessDenied++--------------------------------------------------------------------------------+data SubAction+ = Submit ResolvedEvent+ | Dropped SubDropReason+ | Confirmed SubDetails+ | ConnectionReset
+ Database/EventStore/Internal/Test.hs view
@@ -0,0 +1,41 @@+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.Test+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+-- 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.Communication+ , module Database.EventStore.Internal.Connection+ , module Database.EventStore.Internal.Control+ , module Database.EventStore.Internal.Discovery+ , module Database.EventStore.Internal.EndPoint+ , module Database.EventStore.Internal.Exec+ , module Database.EventStore.Internal.Logger+ , module Database.EventStore.Internal.Operations+ , module Database.EventStore.Internal.Prelude+ , module Database.EventStore.Internal.Settings+ , 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+import Database.EventStore.Internal.Control+import Database.EventStore.Internal.Discovery+import Database.EventStore.Internal.EndPoint+import Database.EventStore.Internal.Exec+import Database.EventStore.Internal.Logger+import Database.EventStore.Internal.Operations+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings+import Database.EventStore.Internal.Types
+ Database/EventStore/Internal/TimerService.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+--------------------------------------------------------------------------------+-- |+-- Module : Database.EventStore.Internal.TimerService+-- 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.TimerService+ ( timerService ) where++--------------------------------------------------------------------------------+import Data.Typeable++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Communication+import Database.EventStore.Internal.Control+import Database.EventStore.Internal.Logger+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Types++--------------------------------------------------------------------------------+data Internal =+ Internal { _stopped :: IORef Bool }++--------------------------------------------------------------------------------+timerService :: Hub -> IO ()+timerService mainBus = do++ internal <- Internal <$> newIORef False++ subscribe mainBus (onInit internal)+ subscribe mainBus (onShutdown internal)+ subscribe mainBus (onNew internal)++--------------------------------------------------------------------------------+delayed :: Typeable e+ => Internal+ -> e+ -> Duration+ -> Bool+ -> EventStore ()+delayed Internal{..} msg (Duration timespan) oneOff = () <$ fork (go timespan)+ where+ go n = do+ when (n > 0) $ do+ let waiting = min n (fromIntegral (maxBound :: Int))+ threadDelay $ fromIntegral waiting+ go (timespan - waiting)++ publish msg+ stopped <- readIORef _stopped+ unless (oneOff || stopped) $ go timespan++--------------------------------------------------------------------------------+onInit ::Internal -> SystemInit -> EventStore ()+onInit Internal{..} _ = publish (Initialized TimerService)++--------------------------------------------------------------------------------+onShutdown :: Internal -> SystemShutdown -> EventStore ()+onShutdown Internal{..} _ = do+ $logInfo "Shutting down..."+ atomicWriteIORef _stopped True+ publish (ServiceTerminated TimerService)++--------------------------------------------------------------------------------+onNew :: Internal -> NewTimer -> EventStore ()+onNew self (NewTimer msg duration oneOff) = delayed self msg duration oneOff
Database/EventStore/Internal/Types.hs view
@@ -1,9 +1,11 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-} -------------------------------------------------------------------------------- -- | -- Module : Database.EventStore.Internal.Types@@ -18,12 +20,13 @@ module Database.EventStore.Internal.Types where --------------------------------------------------------------------------------+import Prelude (String) import Data.Maybe import Data.Monoid (Endo(..)) import Foreign.C.Types (CTime(..)) ---------------------------------------------------------------------------------import ClassyPrelude hiding (Builder)+import Control.Monad.Reader import qualified Data.Aeson as A import Data.Aeson.Types (Object, ToJSON(..), Pair, Parser, (.=)) import Data.DotNet.TimeSpan@@ -32,12 +35,12 @@ import Data.Time (NominalDiffTime) import Data.Time.Clock.POSIX import Data.UUID (UUID, fromByteString, toByteString)-import Network.Connection (TLSSettings) -------------------------------------------------------------------------------- import Database.EventStore.Internal.Command import Database.EventStore.Internal.EndPoint-import Database.EventStore.Logging+import Database.EventStore.Internal.Prelude+import Database.EventStore.Internal.Settings -------------------------------------------------------------------------------- -- Exceptions@@ -196,7 +199,7 @@ expVersionInt32 Any = -2 expVersionInt32 NoStream = -1 expVersionInt32 EmptyStream = -1-expVersionInt32 (Exact i) = i+expVersionInt32 (Exact n) = n expVersionInt32 StreamExists = -4 --------------------------------------------------------------------------------@@ -220,9 +223,9 @@ -- | States that the last event written to the stream should have a -- sequence number matching your expected value. exactEventVersion :: Int32 -> ExpectedVersion-exactEventVersion i- | i < 0 = error $ "expected version must be >= 0, but is " ++ show i- | otherwise = Exact i+exactEventVersion n+ | n < 0 = error $ "expected version must be >= 0, but is " <> show n+ | otherwise = Exact n -------------------------------------------------------------------------------- -- | The stream should exist. If it or a metadata stream does not exist treat@@ -288,6 +291,7 @@ -------------------------------------------------------------------------------- instance Decode EventRecord+instance Encode EventRecord -------------------------------------------------------------------------------- -- | Represents a serialized event representiong either an event or a link@@ -301,6 +305,7 @@ -------------------------------------------------------------------------------- instance Decode ResolvedIndexedEvent+instance Encode ResolvedIndexedEvent -------------------------------------------------------------------------------- data NotHandledReason@@ -322,6 +327,8 @@ -------------------------------------------------------------------------------- instance Decode MasterInfoBuf+-- For testing purpose.+instance Encode MasterInfoBuf -------------------------------------------------------------------------------- data MasterInfo@@ -373,6 +380,8 @@ -------------------------------------------------------------------------------- instance Decode NotHandledBuf+-- Testing purpose only+instance Encode NotHandledBuf -------------------------------------------------------------------------------- -- | Represents a serialized event sent by the server in a subscription context.@@ -500,10 +509,10 @@ newResolvedEvent rie = re where record = getField $ resolvedIndexedRecord rie- link = getField $ resolvedIndexedLink rie+ lnk = getField $ resolvedIndexedLink rie re = ResolvedEvent { resolvedEventRecord = fmap newRecordedEvent record- , resolvedEventLink = fmap newRecordedEvent link+ , resolvedEventLink = fmap newRecordedEvent lnk , resolvedEventPosition = Nothing } @@ -513,13 +522,13 @@ newResolvedEventFromBuf reb = re where record = Just $ newRecordedEvent $ getField $ resolvedEventBufEvent reb- link = getField $ resolvedEventBufLink reb+ lnk = getField $ resolvedEventBufLink reb com = getField $ resolvedEventBufCommitPosition reb pre = getField $ resolvedEventBufPreparePosition reb pos = Position com pre re = ResolvedEvent { resolvedEventRecord = record- , resolvedEventLink = fmap newRecordedEvent link+ , resolvedEventLink = fmap newRecordedEvent lnk , resolvedEventPosition = Just pos } @@ -529,8 +538,8 @@ -- If this 'ResolvedEvent' represents a link event, the link will be the -- original event, otherwise it will be the event. resolvedEventOriginal :: ResolvedEvent -> RecordedEvent-resolvedEventOriginal (ResolvedEvent record link _) =- let Just evt = link <|> record in evt+resolvedEventOriginal (ResolvedEvent record lnk _) =+ let Just evt = lnk <|> record in evt -------------------------------------------------------------------------------- -- | Tries to desarialize 'resolvedEventOriginal' data as JSON.@@ -553,6 +562,11 @@ resolvedEventOriginalId = recordedEventId . resolvedEventOriginal --------------------------------------------------------------------------------+-- | The event number of the original event.+resolvedEventOriginalEventNumber :: ResolvedEvent -> Int32+resolvedEventOriginalEventNumber = recordedEventNumber . resolvedEventOriginal++-------------------------------------------------------------------------------- -- | Represents the direction of read operation (both from $all an usual -- streams). data ReadDirection@@ -561,39 +575,6 @@ deriving (Eq, Show) ----------------------------------------------------------------------------------- Flag------------------------------------------------------------------------------------ | Indicates either a 'Package' contains 'Credentials' data or not.-data Flag- = None- | Authenticated- deriving Show------------------------------------------------------------------------------------- | Maps a 'Flag' into a 'Word8' understandable by the server.-flagWord8 :: Flag -> Word8-flagWord8 None = 0x00-flagWord8 Authenticated = 0x01------------------------------------------------------------------------------------- Credentials------------------------------------------------------------------------------------ | Holds login and password information.-data Credentials- = Credentials- { credLogin :: !ByteString- , credPassword :: !ByteString- }- deriving (Eq, Show)------------------------------------------------------------------------------------- | Creates a 'Credentials' given a login and a password.-credentials :: ByteString -- ^ Login- -> ByteString -- ^ Password- -> Credentials-credentials = Credentials---------------------------------------------------------------------------------- -- Package -------------------------------------------------------------------------------- -- | Represents a package exchanged between the client and the server.@@ -604,9 +585,15 @@ , packageData :: !ByteString , packageCred :: !(Maybe Credentials) }- deriving Show --------------------------------------------------------------------------------+instance Show Package where+ show Package{..} =+ "Package [" <> show packageCorrelation+ <> "], command: "+ <> show packageCmd++-------------------------------------------------------------------------------- packageDataAsText :: Package -> Maybe Text packageDataAsText = go . decodeUtf8 . packageData where@@ -614,90 +601,27 @@ go t = Just t --------------------------------------------------------------------------------+heartbeatRequestPackage :: UUID -> Package+heartbeatRequestPackage uuid =+ Package+ { packageCmd = heartbeatRequestCmd+ , packageCorrelation = uuid+ , packageData = ""+ , packageCred = Nothing+ }++-------------------------------------------------------------------------------- -- | Constructs a heartbeat response given the 'UUID' of heartbeat request. heartbeatResponsePackage :: UUID -> Package heartbeatResponsePackage uuid = Package- { packageCmd = 0x02+ { packageCmd = heartbeatResponseCmd , packageCorrelation = uuid , packageData = "" , packageCred = Nothing } ----------------------------------------------------------------------------------- Settings------------------------------------------------------------------------------------ | Represents reconnection strategy.-data Retry- = AtMost Int- | KeepRetrying------------------------------------------------------------------------------------- | Indicates how many times we should try to reconnect to the server. A value--- less than or equal to 0 means no retry.-atMost :: Int -> Retry-atMost = AtMost------------------------------------------------------------------------------------- | Indicates we should try to reconnect to the server until the end of the--- Universe.-keepRetrying :: Retry-keepRetrying = KeepRetrying------------------------------------------------------------------------------------- | Global 'Connection' settings-data Settings- = Settings- { s_heartbeatInterval :: NominalDiffTime- , s_heartbeatTimeout :: NominalDiffTime- , s_requireMaster :: Bool- , s_credentials :: Maybe Credentials- , s_retry :: Retry- , s_reconnect_delay_secs :: Int -- ^ In seconds- , s_logger :: Maybe (Log -> IO ())- , s_ssl :: Maybe TLSSettings- }------------------------------------------------------------------------------------- | Default global settings.--- s_heartbeatInterval = 750 ms--- s_heartbeatTimeout = 1500 ms--- s_requireMaster = True--- s_credentials = Nothing--- s_retry = 'atMost' 3--- s_reconnect_delay_secs = 3--- s_logger = Nothing-defaultSettings :: Settings-defaultSettings = Settings- { s_heartbeatInterval = msDiffTime 750 -- 750ms- , s_heartbeatTimeout = msDiffTime 1500 -- 1500ms- , s_requireMaster = True- , s_credentials = Nothing- , s_retry = atMost 3- , s_reconnect_delay_secs = 3- , s_logger = Nothing- , s_ssl = Nothing- }------------------------------------------------------------------------------------- | Default SSL settings based on 'defaultSettings'.-defaultSSLSettings :: TLSSettings -> Settings-defaultSSLSettings tls = defaultSettings { s_ssl = Just tls }------------------------------------------------------------------------------------- | Triggers the logger callback if it has been set.-_settingsLog :: Settings -> Log -> IO ()-_settingsLog Settings{..} l =- case s_logger of- Just k -> k l- _ -> return ()------------------------------------------------------------------------------------- | Millisecond timespan-msDiffTime :: Float -> NominalDiffTime-msDiffTime i = fromRational $ toRational (i / 1000)---------------------------------------------------------------------------------- -- | Represents an access control list for a stream. data StreamACL = StreamACL@@ -817,7 +741,7 @@ , p_truncateBefore .= streamMetadataTruncateBefore , p_cacheControl .= fmap toInt64 streamMetadataCacheControl , p_acl .= streamACLJSON streamMetadataACL- ] ++ custPairs+ ] <> custPairs where custPairs = customMetaToPairs streamMetadataCustom @@ -902,7 +826,7 @@ parseNominalDiffTime :: Text -> Object -> Parser (Maybe NominalDiffTime) parseNominalDiffTime k m = fmap (fmap go) (m A..: k) where- go i = (realToFrac $ CTime i)+ go n = (realToFrac $ CTime n) -------------------------------------------------------------------------------- -- | Parses 'StreamACL'.@@ -1016,7 +940,7 @@ -------------------------------------------------------------------------------- -- | Sets the maximum number of events allowed in the stream. setMaxCount :: Int32 -> StreamMetadataBuilder-setMaxCount i = Endo $ \s -> s { streamMetadataMaxCount = Just i }+setMaxCount n = Endo $ \s -> s { streamMetadataMaxCount = Just n } -------------------------------------------------------------------------------- -- | Sets the maximum age of events allowed in the stream.@@ -1026,7 +950,7 @@ -------------------------------------------------------------------------------- -- | Sets the event number from which previous events can be scavenged. setTruncateBefore :: Int32 -> StreamMetadataBuilder-setTruncateBefore i = Endo $ \s -> s { streamMetadataTruncateBefore = Just i }+setTruncateBefore n = Endo $ \s -> s { streamMetadataTruncateBefore = Just n } -------------------------------------------------------------------------------- -- | Sets the amount of time for which the stream head is cachable.@@ -1163,3 +1087,14 @@ , psSettingsMaxSubsCount = 0 , psSettingsNamedConsumerStrategy = RoundRobin }++--------------------------------------------------------------------------------+newtype Duration = Duration Int64 deriving Show++--------------------------------------------------------------------------------+msDuration :: Int64 -> Duration+msDuration = Duration . (1000 *)++--------------------------------------------------------------------------------+secsDuration :: Int64 -> Duration+secsDuration = msDuration . (1000 *)
Database/EventStore/Internal/Utils.hs view
@@ -12,14 +12,15 @@ module Database.EventStore.Internal.Utils (prettyWord8) where --------------------------------------------------------------------------------+import Prelude (String) import Numeric ---------------------------------------------------------------------------------import ClassyPrelude+import Database.EventStore.Internal.Prelude -------------------------------------------------------------------------------- prettyWord8 :: Word8 -> String-prettyWord8 w = "0x" ++ padding (showHex w "")+prettyWord8 w = "0x" <> padding (showHex w "") -------------------------------------------------------------------------------- padding :: String -> String
− Database/EventStore/Logging.hs
@@ -1,68 +0,0 @@------------------------------------------------------------------------------------ |--- Module : Database.EventStore.Logging--- 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.Logging- ( Log(..)- , ErrorMessage(..)- , InfoMessage(..)- ) where-----------------------------------------------------------------------------------import Control.Exception-----------------------------------------------------------------------------------import ClassyPrelude-import Data.UUID-----------------------------------------------------------------------------------import Database.EventStore.Internal.Command------------------------------------------------------------------------------------- | Logging main data structure.-data Log- = Error ErrorMessage- | Info InfoMessage- deriving Show------------------------------------------------------------------------------------- | Classifies error-like log messages.-data ErrorMessage = UnexpectedException SomeException deriving Show------------------------------------------------------------------------------------- | Classifies info-like log messages.-data InfoMessage- = Connecting Int- -- ^ Indicates current attempt.- | ConnectionClosed UUID- -- ^ Indicates connection 'UUID'.- | Connected UUID- -- ^ Indicates connection 'UUID'.- | Disconnected UUID- -- ^ Indicates connection 'UUID'- | PackageSent Command UUID- -- ^ Indicates a package has been sent.- | PackageReceived Command UUID- -- ^ Indicates the client's received a package from the server.-----------------------------------------------------------------------------------instance Show InfoMessage where- show (Connecting i) =- "Connexion attempt n°" ++ show i- show (ConnectionClosed u) =- "Connection [" ++ show u ++ "] closed"- show (Connected u) =- "Connected [" ++ show u ++ "]"- show (Disconnected u) =- "Disconnected [" ++ show u ++ "]"- show (PackageSent cmd u) =- "Package send " ++ show cmd ++ " [" ++ show u ++ "]"- show (PackageReceived cmd u) =- "Package received " ++ show cmd ++ " [" ++ show u ++ "]"
README.md view
@@ -2,7 +2,7 @@ ============================= [](https://gitter.im/YoEight/eventstore?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)-[](https://travis-ci.org/YoEight/eventstore)+[](https://travis-ci.org/YoEight/eventstore) That driver supports 100% of EventStore features ! More information about the GetEventStore database can be found there: https://geteventstore.com/@@ -49,14 +49,14 @@ -- String literal when a Text is needed. module Main where +import Control.Concurrent.Async (wait) import Data.Aeson -- It requires to have `aeson` package installed. Note that EventStore doesn't constraint you to JSON -- format but putting common use aside, by doing so you'll be able to use some interesting EventStore -- features like its Complex Event Processing (CEP) capabality. import Database.EventStore--- Note that import also re-exports 'waitAsync'--- function for instance. There are also 'NonEmpty' data constructor and 'nonEmpty' function from+-- Note that imports 'NonEmpty' data constructor and 'nonEmpty' function from -- 'Data.List.NonEmpty'. main :: IO ()@@ -67,7 +67,7 @@ -- automatically to the server if the connection dropped. Of course that behavior can be tuned -- through some settings. conn <- connect defaultSettings (Static "127.0.0.1" 1113)- let js = "isHaskellTheBest" .= True -- (.=) comes from Data.Aeson module.+ let js = object ["isHaskellTheBest" .= True] -- (.=) comes from Data.Aeson module. evt = createEvent "programming" Nothing (withJson js) -- Appends an event to a stream named `languages`.@@ -75,7 +75,7 @@ -- EventStore interactions are fundamentally asynchronous. Nothing requires you to wait -- for the completion of an operation, but it's good to know if something went wrong.- _ <- waitAsync as+ _ <- wait as -- Again, if you decide to `shutdown` an EventStore connection, it means your application is -- about to terminate.
+ eventstore-test-setup-osx.sh view
@@ -0,0 +1,5 @@+#!/bin/sh++curl -o EventStore-OSS-MacOSX-v3.9.3.tar.gz http://download.geteventstore.com/binaries/EventStore-OSS-MacOSX-v3.9.3.tar.gz+tar -xf EventStore-OSS-MacOSX-v3.9.3.tar.gz+EventStore-OSS-MacOSX-v3.9.3/run-node.sh --mem-db &
eventstore-test-setup.sh view
@@ -1,5 +1,5 @@ #!/bin/sh -wget http://download.geteventstore.com/binaries/EventStore-OSS-Ubuntu-v3.3.0.tar.gz-tar -xf EventStore-OSS-Ubuntu-v3.3.0.tar.gz-EventStore-OSS-Ubuntu-v3.3.0/run-node.sh --mem-db &+wget http://download.geteventstore.com/binaries/EventStore-OSS-Ubuntu-14.04-v3.9.3.tar.gz+tar -xf EventStore-OSS-Ubuntu-14.04-v3.9.3.tar.gz+EventStore-OSS-Ubuntu-14.04-v3.9.3/run-node.sh --mem-db &
eventstore.cabal view
@@ -1,158 +1,182 @@--- Initial eventstore.cabal generated by cabal init. For further--- documentation, see http://haskell.org/cabal/users-guide/---- The name of the package.-name: eventstore---- The package version. See the Haskell package versioning policy (PVP)--- for standards guiding when and how versions should be incremented.--- http://www.haskell.org/haskellwiki/Package_versioning_policy--- PVP summary: +-+------- breaking API changes--- | | +----- non-breaking API additions--- | | | +--- code changes with no API change-version: 0.14.0.2--tested-with: GHC >= 7.8.3 && < 8.0.1---- A short (one-line) description of the package.-synopsis: EventStore TCP Client---- A longer description of the package.-description: EventStore TCP Client <http://geteventstore.com>---- The license under which the package is released.-license: BSD3---- The file containing the license text.-license-file: LICENSE---- The package author(s).-author: Yorick Laupa---- An email address to which users can send suggestions, bug reports, and--- patches.-maintainer: yo.eight@gmail.com---- A copyright notice.--- copyright:--homepage: http://github.com/YoEight/eventstore-bug-reports: http://github.com/YoEight/eventstore/issues-category: Database--build-type: Simple+-- This file has been generated from package.yaml by hpack version 0.17.0.+--+-- see: https://github.com/sol/hpack --- Extra files to be distributed with the package, such as examples or a--- README.-extra-source-files: README.md- CHANGELOG.markdown- .gitignore- .travis.yml- eventstore-test-setup.sh+name: eventstore+version: 0.15.0.0+synopsis: EventStore TCP Client+description: EventStore TCP Client <http://geteventstore.com>+category: Database+homepage: https://github.com/YoEight/eventstore#readme+bug-reports: https://github.com/YoEight/eventstore/issues+author: Yorick Laupa+maintainer: yo.eight@gmail.com+copyright: Yorick Laupa+license: BSD3+license-file: LICENSE+tested-with: GHC >= 7.8.3 && < 8.0.3+build-type: Simple+cabal-version: >= 1.10 --- Constraint on the version of Cabal needed to build this package.-cabal-version: >=1.10+extra-source-files:+ .gitignore+ .travis.yml+ CHANGELOG.markdown+ eventstore-test-setup-osx.sh+ eventstore-test-setup.sh+ README.md source-repository head type: git- location: git://github.com/YoEight/eventstore.git+ location: https://github.com/YoEight/eventstore library- -- Modules exported by the library.- exposed-modules: Database.EventStore- Database.EventStore.Logging- -- Modules included in this library but not exported.- other-modules: Database.EventStore.Internal.Command- Database.EventStore.Internal.Connection- Database.EventStore.Internal.Discovery- Database.EventStore.Internal.EndPoint- Database.EventStore.Internal.Execution.Production- Database.EventStore.Internal.Generator- Database.EventStore.Internal.Operation- Database.EventStore.Internal.Processor- Database.EventStore.Internal.Stream- Database.EventStore.Internal.Subscription- Database.EventStore.Internal.Types- Database.EventStore.Internal.Utils- Database.EventStore.Internal.Manager.Operation.Model- Database.EventStore.Internal.Manager.Subscription.Command- Database.EventStore.Internal.Manager.Subscription.Driver- Database.EventStore.Internal.Manager.Subscription.Message- Database.EventStore.Internal.Manager.Subscription.Model- Database.EventStore.Internal.Manager.Subscription.Packages- Database.EventStore.Internal.Manager.Subscription.Types- Database.EventStore.Internal.Operations- Database.EventStore.Internal.Operation.Catchup- Database.EventStore.Internal.Operation.DeleteStream- Database.EventStore.Internal.Operation.DeleteStream.Message- Database.EventStore.Internal.Operation.ReadAllEvents- Database.EventStore.Internal.Operation.ReadAllEvents.Message- Database.EventStore.Internal.Operation.ReadEvent- Database.EventStore.Internal.Operation.ReadEvent.Message- Database.EventStore.Internal.Operation.ReadStreamEvents- Database.EventStore.Internal.Operation.ReadStreamEvents.Message- Database.EventStore.Internal.Operation.Read.Common- Database.EventStore.Internal.Operation.StreamMetadata- Database.EventStore.Internal.Operation.Transaction- Database.EventStore.Internal.Operation.Transaction.Message- Database.EventStore.Internal.Operation.WriteEvents- Database.EventStore.Internal.Operation.WriteEvents.Message- Database.EventStore.Internal.Operation.Write.Common-- -- LANGUAGE extensions used by modules in this package.- -- other-extensions:-- default-extensions: NoImplicitPrelude-- -- Other library packages from which modules are imported.- build-depends: base >=4.7 && <5- , aeson >=0.8 && <1.3- , cereal >=0.4 && <0.6- , containers ==0.5.*- , protobuf >=0.2.1.1 && <0.3- , random ==1.*- , time >=1.4 && <1.7- , uuid ==1.3.*- , unordered-containers- , stm- , semigroups >=0.5- , dns- , array- , http-client== 0.5.*- , dotnet-timespan- , connection ==0.2.*- , classy-prelude ==1.*- , mtl-- -- Directories containing source files.- -- hs-source-dirs:-- -- Base language which the package is written in.- ghc-options: -Wall-- default-language: Haskell2010--test-suite integration-tests- type: exitcode-stdio-1.0- default-language: Haskell2010- hs-source-dirs: tests- main-is: integration.hs-- default-extensions: NoImplicitPrelude-- other-modules: Tests-- ghc-options: -Wall+ hs-source-dirs:+ ./.+ default-extensions: NoImplicitPrelude OverloadedStrings LambdaCase RecordWildCards RankNTypes TemplateHaskell QuasiQuotes FlexibleContexts MultiParamTypeClasses TypeFamilies ConstraintKinds+ build-depends:+ base >=4.7 && <5+ , aeson >=0.8 && <1.2+ , mono-traversable ==1.*+ , connection ==0.2.*+ , dotnet-timespan+ , stm+ , time >=1.4 && <1.7+ , uuid ==1.3.*+ , lifted-base+ , text+ , bytestring+ , semigroups+ , hashable ==1.2.6.0+ , containers+ , unordered-containers+ , stm-chans+ , stm+ , lifted-async+ , safe-exceptions+ , monad-control+ , exceptions+ , transformers-base+ , cereal >=0.4 && <0.6+ , containers ==0.5.*+ , protobuf >=0.2.1.1 && <0.3+ , random ==1.*+ , unordered-containers+ , semigroups >=0.5+ , dns+ , array+ , http-client == 0.5.*+ , mtl+ , fast-logger+ , monad-logger >= 0.3.20+ , text-format+ , clock+ , machines >= 0.6+ , bifunctors+ , interpolate+ , ekg-core+ exposed-modules:+ Database.EventStore+ Database.EventStore.Internal.Test+ other-modules:+ Database.EventStore.Internal.Callback+ Database.EventStore.Internal.Command+ Database.EventStore.Internal.Communication+ Database.EventStore.Internal.Connection+ Database.EventStore.Internal.ConnectionManager+ Database.EventStore.Internal.Control+ Database.EventStore.Internal.Discovery+ Database.EventStore.Internal.EndPoint+ Database.EventStore.Internal.Exec+ Database.EventStore.Internal.Logger+ Database.EventStore.Internal.Manager.Operation.Registry+ Database.EventStore.Internal.Operation+ Database.EventStore.Internal.Operation.Catchup+ Database.EventStore.Internal.Operation.DeleteStream+ Database.EventStore.Internal.Operation.DeleteStream.Message+ Database.EventStore.Internal.Operation.Persist+ Database.EventStore.Internal.Operation.PersistOperations+ Database.EventStore.Internal.Operation.Read.Common+ Database.EventStore.Internal.Operation.ReadAllEvents+ Database.EventStore.Internal.Operation.ReadAllEvents.Message+ Database.EventStore.Internal.Operation.ReadEvent+ Database.EventStore.Internal.Operation.ReadEvent.Message+ Database.EventStore.Internal.Operation.ReadStreamEvents+ Database.EventStore.Internal.Operation.ReadStreamEvents.Message+ Database.EventStore.Internal.Operation.StreamMetadata+ Database.EventStore.Internal.Operation.Transaction+ Database.EventStore.Internal.Operation.Transaction.Message+ Database.EventStore.Internal.Operation.Volatile+ 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+ Database.EventStore.Internal.Stopwatch+ Database.EventStore.Internal.Stream+ Database.EventStore.Internal.Subscription.Api+ Database.EventStore.Internal.Subscription.Catchup+ Database.EventStore.Internal.Subscription.Command+ Database.EventStore.Internal.Subscription.Message+ Database.EventStore.Internal.Subscription.Packages+ Database.EventStore.Internal.Subscription.Persistent+ Database.EventStore.Internal.Subscription.Regular+ Database.EventStore.Internal.Subscription.Types+ Database.EventStore.Internal.TimerService+ Database.EventStore.Internal.Types+ Database.EventStore.Internal.Utils+ Paths_eventstore+ default-language: Haskell2010 - build-depends: base,- eventstore,- tasty,- tasty-hunit,- aeson,- text,- stm,- time,- dotnet-timespan,- connection,- classy-prelude,- uuid+test-suite eventstore-tests+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs:+ tests+ default-extensions: NoImplicitPrelude OverloadedStrings LambdaCase RecordWildCards RankNTypes TemplateHaskell QuasiQuotes FlexibleContexts MultiParamTypeClasses TypeFamilies ConstraintKinds+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , aeson >=0.8 && <1.2+ , mono-traversable ==1.*+ , connection ==0.2.*+ , dotnet-timespan+ , stm+ , time >=1.4 && <1.7+ , uuid ==1.3.*+ , lifted-base+ , text+ , bytestring+ , semigroups+ , hashable ==1.2.6.0+ , containers+ , unordered-containers+ , stm-chans+ , stm+ , lifted-async+ , safe-exceptions+ , monad-control+ , exceptions+ , transformers-base+ , eventstore+ , tasty+ , tasty-hunit+ , tasty-hspec+ , aeson+ , text+ , protobuf+ , cereal+ , uuid+ , fast-logger+ , async+ other-modules:+ Test.Bogus.Connection+ Test.Bus+ Test.Common+ Test.Connection+ Test.Integration+ Test.Integration.Tests+ Test.Operation+ default-language: Haskell2010
+ tests/Main.hs view
@@ -0,0 +1,40 @@+--------------------------------------------------------------------------------+-- |+-- Module : Main+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+-- Main integration entry point.+--------------------------------------------------------------------------------+module Main where++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Test+import Test.Tasty+import Test.Tasty.Hspec++--------------------------------------------------------------------------------+import qualified Test.Bus as Bus+import qualified Test.Connection as Connection+import qualified Test.Integration as Integration+import qualified Test.Operation as Operation++--------------------------------------------------------------------------------+main :: IO ()+main = do+ internal <- sequence [ testSpec "Bus" Bus.spec+ , testSpec "Connection" Connection.spec+ , testSpec "Operation" Operation.spec+ ]++ integration <- Integration.tests++ let tree = [ testGroup "Internal" internal+ , testGroup "Integration" integration+ ]++ defaultMain (testGroup "EventStore tests" tree)
+ tests/Test/Bogus/Connection.hs view
@@ -0,0 +1,79 @@+--------------------------------------------------------------------------------+-- |+-- Module : Test.Bogus.Connection+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+-- This module hosts unreliable 'ConnectionBuilder' implementation+-- for testing purpose.+--------------------------------------------------------------------------------+module Test.Bogus.Connection where++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Test++--------------------------------------------------------------------------------+-- | Creates a 'ConnectionBuilder' that always fails. The first parameter is an+-- action that will be executed every time the builder is ask to create a+-- connection. In this case, it fails because it never sends to the+-- connection manager 'ConnectionEstablished' event.+alwaysFailConnectionBuilder :: IO () -> ConnectionBuilder+alwaysFailConnectionBuilder onConnect = ConnectionBuilder $ \ept -> do+ liftIO onConnect+ uuid <- freshUUID+ return Connection { connectionId = uuid+ , connectionEndPoint = ept+ , enqueuePackage = \_ -> return ()+ , dispose = return ()+ }++--------------------------------------------------------------------------------+-- | Creates a 'ConnectionBuilder' that allows to respond to 'Package' different+-- from heartbeat request.+respondWithConnectionBuilder :: (Package -> Package) -> ConnectionBuilder+respondWithConnectionBuilder resp =+ respondMWithConnectionBuilder (\_ -> return . resp)++--------------------------------------------------------------------------------+-- | Creates a 'ConnectionBuilder' that allows to respond to 'Package' different+-- from heartbeat request.+respondMWithConnectionBuilder :: (EndPoint -> Package -> IO Package)+ -> ConnectionBuilder+respondMWithConnectionBuilder resp = ConnectionBuilder $ \ept -> do+ uuid <- freshUUID+ let conn = Connection+ { connectionId = uuid+ , connectionEndPoint = ept+ , enqueuePackage = \pkg ->+ case packageCmd pkg of+ cmd | cmd == getCommand 0x01 -> do+ let rpkg = pkg { packageCmd = getCommand 0x02 }+ publish (PackageArrived conn rpkg)+ | otherwise -> do+ rpkg <- liftIO $ resp ept pkg+ publish (PackageArrived conn rpkg)+ , dispose = return ()+ }++ publish (ConnectionEstablished conn)+ return conn++--------------------------------------------------------------------------------+-- | Silent 'ConnectionBuilder'. It never publishes new 'Package's.+silentConnectionBuilder :: IO () -> ConnectionBuilder+silentConnectionBuilder onConnect = ConnectionBuilder $ \ept -> do+ uuid <- freshUUID+ liftIO onConnect+ let conn = Connection+ { connectionId = uuid+ , connectionEndPoint = ept+ , enqueuePackage = \_ -> return ()+ , dispose = return ()+ }++ publish (ConnectionEstablished conn)+ return conn
+ tests/Test/Bus.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Test.Bus+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+--------------------------------------------------------------------------------+module Test.Bus where++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Test hiding (i)++--------------------------------------------------------------------------------+import Test.Common+import Test.Tasty.Hspec++--------------------------------------------------------------------------------+spec :: Spec+spec = beforeAll (createLoggerRef testGlobalLog) $ do+ specify "Bus dispatches only one time" $ \logRef -> do+ bus <- newBus logRef testSettings++ ref <- newIORef (0 :: Int)+ subscribe bus $ \Foo ->+ atomicModifyIORef' ref $ \i -> (i+1, ())++ publishWith bus Foo+ busStop bus+ busProcessedEverything bus++ cnt <- readIORef ref++ cnt `shouldBe` 1++ specify "Bus dispatches given and parent message type" $ \logRef -> do+ bus <- newBus logRef testSettings++ ref <- newIORef (0 :: Int)+ subscribe bus $ \Foo ->+ atomicModifyIORef' ref $ \i -> (i+1, ())++ subscribe bus $ \(_ :: Message) ->+ atomicModifyIORef' ref $ \i -> (i+1, ())++ publishWith bus Foo+ busStop bus+ busProcessedEverything bus++ cnt <- readIORef ref+ cnt `shouldBe` 2
+ tests/Test/Common.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE DeriveDataTypeable #-}+--------------------------------------------------------------------------------+-- |+-- Module : Test.Common+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+--------------------------------------------------------------------------------+module Test.Common where++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Test++--------------------------------------------------------------------------------+data Foo = Foo deriving Typeable++--------------------------------------------------------------------------------+newtype Counter = Counter (TVar Int)++--------------------------------------------------------------------------------+newCounter :: IO Counter+newCounter = Counter <$> newTVarIO 0++--------------------------------------------------------------------------------+incrCounter :: Counter -> IO ()+incrCounter (Counter var) = atomically $ modifyTVar' var (+1)++--------------------------------------------------------------------------------+readCounterSTM :: Counter -> STM Int+readCounterSTM (Counter var) = readTVar var++--------------------------------------------------------------------------------+testDisc :: Discovery+testDisc = staticEndPointDiscovery "localhost" 1234++--------------------------------------------------------------------------------+testSettings :: Settings+testSettings =+ defaultSettings { s_loggerType = testGlobalLog+ , s_loggerFilter = LoggerLevel LevelDebug+ }++--------------------------------------------------------------------------------+secs :: Int+secs = 1000 * 1000++--------------------------------------------------------------------------------+testStdout :: LogType+testStdout = LogStdout 0++--------------------------------------------------------------------------------+testFile :: FilePath -> LogType+testFile path = LogFileNoRotate path 0++--------------------------------------------------------------------------------+testGlobalLog :: LogType+testGlobalLog = LogNone++--------------------------------------------------------------------------------+createLoggerRef :: LogType -> IO LoggerRef+createLoggerRef tpe = newLoggerRef tpe (LoggerLevel LevelDebug) False
+ tests/Test/Connection.hs view
@@ -0,0 +1,52 @@+--------------------------------------------------------------------------------+-- |+-- Module : Test.Connection+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+--------------------------------------------------------------------------------+module Test.Connection (spec) where++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Test hiding (i)++--------------------------------------------------------------------------------+import Test.Bogus.Connection+import Test.Common+import Test.Tasty.Hspec++spec :: Spec+spec = beforeAll (createLoggerRef testGlobalLog) $ do+ specify "Connection should retry on connection failure" $ \logRef -> do+ counter <- newCounter+ bus <- newBus logRef testSettings+ let builder = alwaysFailConnectionBuilder $ incrCounter counter+ disc = staticEndPointDiscovery "localhost" 2000++ exec <- newExec testSettings bus builder disc++ atomically $ do+ i <- readCounterSTM counter+ when (i /= 3) retrySTM++ publishWith exec SystemShutdown+ execWaitTillClosed exec++ specify "Connection should close on heartbeat timeout" $ \logRef -> do+ counter <- newCounter+ bus <- newBus logRef testSettings+ let builder = silentConnectionBuilder $ incrCounter counter+ disc = staticEndPointDiscovery "localhost" 2000++ exec <- newExec testSettings bus builder disc++ atomically $ do+ i <- readCounterSTM counter+ unless (i > 1) retrySTM++ publishWith exec SystemShutdown+ execWaitTillClosed exec
+ tests/Test/Integration.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE OverloadedStrings #-}+--------------------------------------------------------------------------------+-- |+-- Module : Test.Integration+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+-- Main integration entry point.+--------------------------------------------------------------------------------+module Test.Integration (tests) where++--------------------------------------------------------------------------------+import Database.EventStore.Internal.Test+import Test.Tasty+import Test.Tasty.Hspec++--------------------------------------------------------------------------------+import qualified Test.Integration.Tests as Tests++--------------------------------------------------------------------------------+tests :: IO [TestTree]+tests = testSpecs Tests.spec
+ tests/Test/Integration/Tests.hs view
@@ -0,0 +1,454 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+--------------------------------------------------------------------------------+-- |+-- Module : Test.Integration.Tests+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+-- Gathers all EventStore operations tests.+--------------------------------------------------------------------------------+module Test.Integration.Tests (spec) where++--------------------------------------------------------------------------------+import Control.Concurrent.Async (wait)+import Data.Aeson+import Data.DotNet.TimeSpan+import Data.UUID hiding (null)+import Data.UUID.V4+import Test.Tasty.HUnit+import Test.Tasty.Hspec++--------------------------------------------------------------------------------+import Database.EventStore+ ( SubscriptionClosed(..)+ , ReadResult(..)+ , StreamName(..)+ , Slice(..)+ , ConnectionType(..)+ , Connection+ , getStreamMetadata+ , sendEvent+ , sendEvents+ , setStreamMetadata+ , nextEvent+ , notifyEventsProcessed+ , waitConfirmation+ , connectToPersistentSubscription+ , unsubscribe+ , createPersistentSubscription+ , deletePersistentSubscription+ , updatePersistentSubscription+ , nextEventMaybe+ , waitUnsubscribeConfirmed+ , subscribeFrom+ , waitTillCatchup+ , readAllEventsBackward+ , readAllEventsForward+ , readStreamEventsBackward+ , readStreamEventsForward+ , subscribe+ , connect+ , waitTillClosed+ , shutdown+ , readEvent+ , startTransaction+ , transactionWrite+ , deleteStream+ , transactionCommit+ )+import Database.EventStore.Internal.Test hiding+ ( Connection(..)+ , subscribe+ , wait+ , connect+ , readEvent+ , transactionWrite+ , deleteStream+ , transactionCommit+ , i+ )+import Test.Common++--------------------------------------------------------------------------------+createConnection :: IO Connection+createConnection = do+ let setts = testSettings+ { s_credentials = Just $ credentials "admin" "changeit"+ , s_reconnect_delay = 3+ }++ connect setts (Static "127.0.0.1" 1113)++--------------------------------------------------------------------------------+shuttingDown :: Connection -> IO ()+shuttingDown conn = do+ shutdown conn+ waitTillClosed conn++--------------------------------------------------------------------------------+spec :: Spec+spec = beforeAll createConnection $ afterAll shuttingDown $ describe "Features" $ parallel $ do+ it "writes events" writeEventTest+ it "reads events" readEventTest+ it "deletes stream" deleteStreamTest+ it "uses transactions" transactionTest+ it "reads forward" readStreamEventForwardTest+ it "reads backward" readStreamEventBackwardTest+ it "reads $all forward" readAllEventsForwardTest+ it "reads $all backward" readAllEventsBackwardTest+ it "creates a volatile subscription" subscribeTest+ it "creates a catchup subscription" subscribeFromTest+ it "proves catchup subscriptions don't deadlock on non existant streams" subscribeFromNoStreamTest+ it "sets stream metadata" setStreamMetadataTest+ it "gets stream metadata" getStreamMetadataTest+ it "creates a persistent subscription" createPersistentTest+ it "updates a persistent subscription" updatePersistentTest+ it "deletes a persistent subscription" deletePersistentTest+ it "connects to a persistent subscription" connectToPersistentTest+ it "set MaxAge metadata correctly" maxAgeTest++--------------------------------------------------------------------------------+freshStreamId :: IO StreamName+freshStreamId = fmap (StreamName . toText) nextRandom++--------------------------------------------------------------------------------+writeEventTest :: Connection -> IO ()+writeEventTest conn = do+ let js = object [ "baz" .= True ]+ evt = createEvent "foo" Nothing $ withJson js++ stream <- freshStreamId+ as <- sendEvent conn stream anyVersion evt+ _ <- wait as+ return ()++--------------------------------------------------------------------------------+readEventTest :: Connection -> IO ()+readEventTest conn = do+ stream <- freshStreamId++ let js = object [ "baz" .= True ]+ evt = createEvent "foo" Nothing $ withJson js+ as <- sendEvent conn stream anyVersion evt+ _ <- wait as+ bs <- readEvent conn stream 0 False+ rs <- wait bs+ case rs of+ ReadSuccess re ->+ case re of+ ReadEvent _ _ revt ->+ case resolvedEventDataAsJson revt of+ Just js_evt ->+ assertEqual "event should match" js js_evt+ Nothing -> fail "Error when retrieving recorded data"+ _ -> fail "Event not found"+ e -> fail $ "Read failure: " <> show e++--------------------------------------------------------------------------------+deleteStreamTest :: Connection -> IO ()+deleteStreamTest conn = do+ stream <- freshStreamId+ let js = object [ "baz" .= True ]+ evt = createEvent "foo" Nothing $ withJson js+ _ <- sendEvent conn stream anyVersion evt >>= wait+ _ <- deleteStream conn stream anyVersion Nothing+ return ()++--------------------------------------------------------------------------------+transactionTest :: Connection -> IO ()+transactionTest conn = do+ stream <- freshStreamId+ let js = object [ "baz" .= True ]+ evt = createEvent "foo" Nothing $ withJson js+ t <- startTransaction conn stream anyVersion >>= wait+ _ <- transactionWrite t [evt] >>= wait+ rs <- readEvent conn stream 0 False >>= wait+ case rs of+ ReadNoStream -> return ()+ e -> fail $ "transaction-test stream is supposed to not exist "+ <> show e+ _ <- transactionCommit t >>= wait+ rs2 <- readEvent conn stream 0 False >>= wait+ case rs2 of+ ReadSuccess re ->+ case re of+ ReadEvent _ _ revt ->+ case resolvedEventDataAsJson revt of+ Just js_evt ->+ assertEqual "event should match" js js_evt+ Nothing -> fail "Error when retrieving recorded data"+ _ -> fail "Event not found"+ e -> fail $ "Read failure: " <> show e++--------------------------------------------------------------------------------+readStreamEventForwardTest :: Connection -> IO ()+readStreamEventForwardTest conn = do+ stream <- freshStreamId+ let jss = [ object [ "baz" .= True]+ , object [ "foo" .= False]+ , object [ "bar" .= True]+ ]+ evts = fmap (createEvent "foo" Nothing . withJson) jss+ _ <- sendEvents conn stream anyVersion evts >>= wait+ rs <- readStreamEventsForward conn stream 0 10 False >>= wait+ case rs of+ ReadSuccess sl -> do+ let jss_evts = catMaybes $ fmap resolvedEventDataAsJson+ $ sliceEvents sl+ assertEqual "Events should be equal" jss jss_evts+ e -> fail $ "Read failure: " <> show e++--------------------------------------------------------------------------------+readStreamEventBackwardTest :: Connection -> IO ()+readStreamEventBackwardTest conn = do+ let jss = [ object [ "baz" .= True]+ , object [ "foo" .= False]+ , object [ "bar" .= True]+ ]+ evts = fmap (createEvent "foo" Nothing . withJson) jss+ _ <- sendEvents conn "read-backward-test" anyVersion evts >>= wait+ rs <- readStreamEventsBackward conn "read-backward-test" 2 10 False >>= wait+ case rs of+ ReadSuccess sl -> do+ let jss_evts = catMaybes $ fmap resolvedEventDataAsJson+ $ sliceEvents sl+ assertEqual "Events should be equal" (reverse jss) jss_evts+ e -> fail $ "Read failure: " <> show e++--------------------------------------------------------------------------------+readAllEventsForwardTest :: Connection -> IO ()+readAllEventsForwardTest conn = do+ sl <- readAllEventsForward conn positionStart 3 False >>= wait+ assertEqual "Events is not empty" False (null $ sliceEvents sl)++--------------------------------------------------------------------------------+readAllEventsBackwardTest :: Connection -> IO ()+readAllEventsBackwardTest conn = do+ sl <- readAllEventsBackward conn positionEnd 3 False >>= wait+ assertEqual "Events is not empty" False (null $ sliceEvents sl)++--------------------------------------------------------------------------------+subscribeTest :: Connection -> IO ()+subscribeTest conn = do+ stream <- freshStreamId++ let jss = [ object [ "baz" .= True]+ , object [ "foo" .= False]+ , object [ "bar" .= True]+ ]+ evts = fmap (createEvent "foo" Nothing . withJson) jss+ sub <- subscribe conn stream False+ _ <- waitConfirmation sub+ _ <- sendEvents conn stream anyVersion evts >>= wait+ let loop 3 = return []+ loop i = do+ e <- nextEvent sub+ fmap (resolvedEventDataAsJson e:) $ loop (i+1)++ 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++--------------------------------------------------------------------------------+subscribeFromTest :: Connection -> IO ()+subscribeFromTest conn = do+ stream <- freshStreamId++ let jss = [ object [ "1" .= (1 :: Int)]+ , object [ "2" .= (2 :: Int)]+ , object [ "3" .= (3 :: Int)]+ ]+ jss2 = [ object [ "4" .= (4 :: Int)]+ , object [ "5" .= (5 :: Int)]+ , object [ "6" .= (6 :: Int)]+ ]+ alljss = jss <> jss2+ evts = fmap (createEvent "foo" Nothing . withJson) jss+ evts2 = fmap (createEvent "foo" Nothing . withJson) jss2+ _ <- sendEvents conn stream anyVersion evts >>= wait+ sub <- subscribeFrom conn stream False Nothing (Just 1)+ _ <- waitConfirmation sub+ _ <- sendEvents conn stream anyVersion evts2 >>= wait++ 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++--------------------------------------------------------------------------------+data SubNoStreamTest+ = SubNoStreamTestSuccess+ | SubNoStreamTestTimeout+ deriving (Eq, Show)++--------------------------------------------------------------------------------+subscribeFromNoStreamTest :: Connection -> IO ()+subscribeFromNoStreamTest conn = do+ stream <- freshStreamId+ sub <- subscribeFrom conn stream False 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"++ 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++ _ <- sendEvents conn stream anyVersion evts >>= wait+ loop jss+ return SubNoStreamTestSuccess+ timeout = do+ threadDelay (12 * secs)+ return SubNoStreamTestTimeout++ res <- race subAction timeout+ case res of+ Left r -> assertEqual "Wrong test result" SubNoStreamTestSuccess r+ Right r -> assertEqual "Wrong test result" SubNoStreamTestSuccess r++--------------------------------------------------------------------------------+setStreamMetadataTest :: Connection -> IO ()+setStreamMetadataTest conn = do+ stream <- freshStreamId+ let metadata = buildStreamMetadata $ setCustomProperty "foo" (1 :: Int)+ _ <- setStreamMetadata conn stream anyVersion metadata >>= wait+ return ()++--------------------------------------------------------------------------------+getStreamMetadataTest :: Connection -> IO ()+getStreamMetadataTest conn = do+ stream <- freshStreamId+ let metadata = buildStreamMetadata $ setCustomProperty "foo" (1 :: Int)+ _ <- setStreamMetadata conn stream anyVersion metadata >>= wait+ r <- getStreamMetadata conn stream >>= wait+ case r of+ StreamMetadataResult _ _ m ->+ case getCustomProperty m "foo" of+ Just i -> assertEqual "Should have equal value" (1 :: Int) i+ _ -> fail "Can't find foo property"+ _ -> fail $ "Stream " <> show stream <> " doesn't exist"++--------------------------------------------------------------------------------+createPersistentTest :: Connection -> IO ()+createPersistentTest conn = do+ let def = defaultPersistentSubscriptionSettings+ stream <- freshStreamId+ r <- createPersistentSubscription conn "group" stream def >>= wait+ case r of+ Nothing -> return ()+ Just e -> fail $ "Exception arised: " <> show e++--------------------------------------------------------------------------------+updatePersistentTest :: Connection -> IO ()+updatePersistentTest conn = do+ let def = defaultPersistentSubscriptionSettings+ stream <- freshStreamId+ _ <- createPersistentSubscription conn "group" stream def >>= wait+ r <- updatePersistentSubscription conn "group" stream def >>= wait+ case r of+ Nothing -> return ()+ Just e -> fail $ "Exception arised: " <> show e++--------------------------------------------------------------------------------+deletePersistentTest :: Connection -> IO ()+deletePersistentTest conn = do+ let def = defaultPersistentSubscriptionSettings+ stream <- freshStreamId+ _ <- createPersistentSubscription conn "group" stream def >>= wait+ r <- deletePersistentSubscription conn "group" stream >>= wait+ case r of+ Nothing -> return ()+ Just e -> fail $ "Exception arised: " <> show e++--------------------------------------------------------------------------------+connectToPersistentTest :: Connection -> IO ()+connectToPersistentTest conn = do+ let def = defaultPersistentSubscriptionSettings+ js1 = object ["baz" .= True]+ js2 = object ["foo" .= True]+ jss = [ js1+ , js2+ ]+ evts = fmap (createEvent "foo" Nothing . withJson) jss+ stream <- freshStreamId+ _ <- createPersistentSubscription conn "group" stream def >>= wait+ _ <- sendEvents conn stream anyVersion evts >>= wait+ sub <- connectToPersistentSubscription conn "group" stream 1+ _ <- waitConfirmation sub+ r <- nextEvent sub+ case resolvedEventDataAsJson r of+ Just js_evt -> assertEqual "event 1 should match" js1 js_evt+ _ -> fail "Deserialization error"++ notifyEventsProcessed sub [resolvedEventOriginalId r]++ r2 <- nextEvent sub+ case resolvedEventDataAsJson r2 of+ Just js_evt -> assertEqual "event 2 should match" js2 js_evt+ _ -> fail "Deserialization error"++ notifyEventsProcessed sub [resolvedEventOriginalId r2]++ unsubscribe sub+ let action = do+ _ <- nextEvent sub+ return False+ res <- catch action $ \(_ :: SubscriptionClosed) -> return True+ assertBool "Should have raised an exception" res++--------------------------------------------------------------------------------+maxAgeTest :: Connection -> IO ()+maxAgeTest conn = do+ let timespan = fromDays 1+ metadata = buildStreamMetadata $ setMaxAge timespan+ evt = createEvent "foo" Nothing+ $ withJson (object ["type" .= (3 :: Int)])+ stream <- freshStreamId+ _ <- sendEvent conn stream anyVersion evt >>= wait+ _ <- setStreamMetadata conn stream anyVersion metadata >>= wait+ r <- getStreamMetadata conn stream >>= wait+ case r of+ StreamMetadataResult _ _ m ->+ assertEqual "Should have equal timespan" (Just timespan)+ (streamMetadataMaxAge m)+ _ -> fail $ "Stream " <> show stream <> " doesn't exist"
+ tests/Test/Operation.hs view
@@ -0,0 +1,69 @@+--------------------------------------------------------------------------------+-- |+-- Module : Test.Operation+-- Copyright : (C) 2017 Yorick Laupa+-- License : (see the file LICENSE)+--+-- Maintainer : Yorick Laupa <yo.eight@gmail.com>+-- Stability : provisional+-- Portability : non-portable+--+--------------------------------------------------------------------------------+module Test.Operation (spec) where++--------------------------------------------------------------------------------+import Data.ProtocolBuffers+import Data.Serialize+import Database.EventStore.Internal.Test hiding (i)++--------------------------------------------------------------------------------+import Test.Bogus.Connection+import Test.Common+import Test.Tasty.Hspec++--------------------------------------------------------------------------------+alwaysNotHandled :: Package -> Package+alwaysNotHandled pkg =+ pkg { packageCmd = notHandledCmd+ , packageData = runPut $ encodeMessage msg+ }+ where+ msg = NotHandledBuf+ { notHandledReason = putField N_NotMaster+ , notHandledAdditionalInfo = putField $ Just info+ }++ info = MasterInfoBuf+ { bufMasterExternalTcpAddr = putField "addr"+ , bufMasterExternalTcpPort = putField 1+ , bufMasterExternalHttpAddr = putField "http"+ , bufMasterExternalHttpPort = putField 1+ , bufMasterExternalSecureTcpAddr = putField Nothing+ , bufMasterExternalSecureTcpPort = putField Nothing+ }++--------------------------------------------------------------------------------+spec :: Spec+spec = beforeAll (createLoggerRef testGlobalLog) $ do+ specify "Operation manager should behave on not handled [not-master]" $ \logRef -> do+ bus <- newBus logRef testSettings+ var <- newEmptyMVar+ let builder = respondMWithConnectionBuilder $ \ept pkg -> do+ emptyVar <- isEmptyMVar var+ when (ept == EndPoint "addr" 1 && emptyVar) $+ putMVar var ()++ return $ alwaysNotHandled pkg++ exec <- newExec testSettings bus builder testDisc++ p <- newPromise+ let op = readEvent testSettings "foo" 1 True+ publishWith exec (SubmitOperation p op)++ res <- takeMVar var++ publishWith exec SystemShutdown+ execWaitTillClosed exec++ res `shouldBe` ()
− tests/Tests.hs
@@ -1,397 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}------------------------------------------------------------------------------------ |--- Module : Tests--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Gathers all EventStore operations tests.----------------------------------------------------------------------------------module Tests where-----------------------------------------------------------------------------------import ClassyPrelude-----------------------------------------------------------------------------------import Data.Aeson-import Data.DotNet.TimeSpan-import Data.UUID hiding (null)-import Data.UUID.V4-import Test.Tasty-import Test.Tasty.HUnit-----------------------------------------------------------------------------------import Database.EventStore-----------------------------------------------------------------------------------tests :: Connection -> TestTree-tests conn = testGroup "EventStore actions tests"- [ testCase "Write event" $ writeEventTest conn- , testCase "Read event" $ readEventTest conn- , testCase "Delete stream" $ deleteStreamTest conn- , testCase "Transaction" $ transactionTest conn- , testCase "Read forward" $ readStreamEventForwardTest conn- , testCase "Read backward" $ readStreamEventBackwardTest conn- , testCase "Real $all forward" $ readAllEventsForwardTest conn- , testCase "Real $all backward" $ readAllEventsBackwardTest conn- , testCase "Subscription test" $ subscribeTest conn- , testCase "Subscription from test" $ subscribeFromTest conn- , testCase "Subscription from catchup not blocking" $- subscribeFromNoStreamTest conn- , testCase "Set Stream Metadata" $ setStreamMetadataTest conn- , testCase "Get Stream Metadata" $ getStreamMetadataTest conn- , testCase "Create persistent sub" $ createPersistentTest conn- , testCase "Update persistent sub" $ updatePersistentTest conn- , testCase "Delete persistent sub" $ deletePersistentTest conn- , testCase "Connect persistent sub" $ connectToPersistentTest conn- , testCase "MaxAge metadata test" $ maxAgeTest conn- , testCase "Shutdown connection" $ shutdownTest conn- ]------------------------------------------------------------------------------------freshStreamId :: IO Text-freshStreamId = fmap toText nextRandom-----------------------------------------------------------------------------------writeEventTest :: Connection -> IO ()-writeEventTest conn = do- let js = object [ "baz" .= True ]- evt = createEvent "foo" Nothing $ withJson js-- stream <- freshStreamId- as <- sendEvent conn stream anyVersion evt- _ <- waitAsync as- return ()-----------------------------------------------------------------------------------readEventTest :: Connection -> IO ()-readEventTest conn = do- stream <- freshStreamId-- let js = object [ "baz" .= True ]- evt = createEvent "foo" Nothing $ withJson js- as <- sendEvent conn stream anyVersion evt- _ <- waitAsync as- bs <- readEvent conn stream 0 False- rs <- waitAsync bs- case rs of- ReadSuccess re ->- case re of- ReadEvent _ _ revt ->- case resolvedEventDataAsJson revt of- Just js_evt ->- assertEqual "event should match" js js_evt- Nothing -> fail "Error when retrieving recorded data"- _ -> fail "Event not found"- e -> fail $ "Read failure: " ++ show e-----------------------------------------------------------------------------------deleteStreamTest :: Connection -> IO ()-deleteStreamTest conn = do- stream <- freshStreamId- let js = object [ "baz" .= True ]- evt = createEvent "foo" Nothing $ withJson js- _ <- sendEvent conn stream anyVersion evt >>= waitAsync- _ <- deleteStream conn stream anyVersion Nothing- return ()-----------------------------------------------------------------------------------transactionTest :: Connection -> IO ()-transactionTest conn = do- stream <- freshStreamId- let js = object [ "baz" .= True ]- evt = createEvent "foo" Nothing $ withJson js- t <- startTransaction conn stream anyVersion >>= waitAsync- _ <- transactionWrite t [evt] >>= waitAsync- rs <- readEvent conn stream 0 False >>= waitAsync- case rs of- ReadNoStream -> return ()- e -> fail $ "transaction-test stream is supposed to not exist "- ++ show e- _ <- transactionCommit t >>= waitAsync- rs2 <- readEvent conn stream 0 False >>= waitAsync- case rs2 of- ReadSuccess re ->- case re of- ReadEvent _ _ revt ->- case resolvedEventDataAsJson revt of- Just js_evt ->- assertEqual "event should match" js js_evt- Nothing -> fail "Error when retrieving recorded data"- _ -> fail "Event not found"- e -> fail $ "Read failure: " ++ show e-----------------------------------------------------------------------------------readStreamEventForwardTest :: Connection -> IO ()-readStreamEventForwardTest conn = do- stream <- freshStreamId- let jss = [ object [ "baz" .= True]- , object [ "foo" .= False]- , object [ "bar" .= True]- ]- evts = fmap (createEvent "foo" Nothing . withJson) jss- _ <- sendEvents conn stream anyVersion evts >>= waitAsync- rs <- readStreamEventsForward conn stream 0 10 False >>= waitAsync- case rs of- ReadSuccess sl -> do- let jss_evts = catMaybes $ fmap resolvedEventDataAsJson- $ sliceEvents sl- assertEqual "Events should be equal" jss jss_evts- e -> fail $ "Read failure: " ++ show e-----------------------------------------------------------------------------------readStreamEventBackwardTest :: Connection -> IO ()-readStreamEventBackwardTest conn = do- let jss = [ object [ "baz" .= True]- , object [ "foo" .= False]- , object [ "bar" .= True]- ]- evts = fmap (createEvent "foo" Nothing . withJson) jss- _ <- sendEvents conn "read-backward-test" anyVersion evts >>= waitAsync- rs <- readStreamEventsBackward conn "read-backward-test" 2 10 False >>= waitAsync- case rs of- ReadSuccess sl -> do- let jss_evts = catMaybes $ fmap resolvedEventDataAsJson- $ sliceEvents sl- assertEqual "Events should be equal" (reverse jss) jss_evts- e -> fail $ "Read failure: " ++ show e-----------------------------------------------------------------------------------readAllEventsForwardTest :: Connection -> IO ()-readAllEventsForwardTest conn = do- sl <- readAllEventsForward conn positionStart 3 False >>= waitAsync- assertEqual "Events is not empty" False (null $ sliceEvents sl)-----------------------------------------------------------------------------------readAllEventsBackwardTest :: Connection -> IO ()-readAllEventsBackwardTest conn = do- sl <- readAllEventsBackward conn positionEnd 3 False >>= waitAsync- assertEqual "Events is not empty" False (null $ sliceEvents sl)-----------------------------------------------------------------------------------subscribeTest :: Connection -> IO ()-subscribeTest conn = do- stream <- freshStreamId-- let jss = [ object [ "baz" .= True]- , object [ "foo" .= False]- , object [ "bar" .= True]- ]- evts = fmap (createEvent "foo" Nothing . withJson) jss- sub <- subscribe conn stream False- _ <- waitConfirmation sub- _ <- sendEvents conn stream anyVersion evts >>= waitAsync- let loop 3 = return []- loop i = do- e <- nextEvent sub- fmap (resolvedEventDataAsJson e:) $ loop (i+1)-- 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-----------------------------------------------------------------------------------subscribeFromTest :: Connection -> IO ()-subscribeFromTest conn = do- stream <- freshStreamId-- let jss = [ object [ "1" .= (1 :: Int)]- , object [ "2" .= (2 :: Int)]- , object [ "3" .= (3 :: Int)]- ]- jss2 = [ object [ "4" .= (4 :: Int)]- , object [ "5" .= (5 :: Int)]- , object [ "6" .= (6 :: Int)]- ]- alljss = jss ++ jss2- evts = fmap (createEvent "foo" Nothing . withJson) jss- evts2 = fmap (createEvent "foo" Nothing . withJson) jss2- _ <- sendEvents conn stream anyVersion evts >>= waitAsync- sub <- subscribeFrom conn stream False Nothing (Just 1)- _ <- waitConfirmation sub- _ <- sendEvents conn stream anyVersion evts2 >>= waitAsync-- 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-----------------------------------------------------------------------------------data SubNoStreamTest- = SubNoStreamTestSuccess- | SubNoStreamTestWrongException- | SubNoStreamTestTimeout- deriving (Eq, Show)-----------------------------------------------------------------------------------secs :: Int-secs = 1000 * 1000-----------------------------------------------------------------------------------subscribeFromNoStreamTest :: Connection -> IO ()-subscribeFromNoStreamTest conn = do- stream <- freshStreamId- sub <- subscribeFrom conn stream False Nothing Nothing- let subAction = do- res <- try $ waitTillCatchup sub- case res of- Left InvalidOperation{} -> return SubNoStreamTestSuccess- _ -> return SubNoStreamTestWrongException- timeout = do- threadDelay (10 * secs)- return SubNoStreamTestTimeout-- res <- race subAction timeout- case res of- Left r -> assertEqual "Wrong test result" SubNoStreamTestSuccess r- Right r -> assertEqual "Wrong test result" SubNoStreamTestSuccess r-----------------------------------------------------------------------------------setStreamMetadataTest :: Connection -> IO ()-setStreamMetadataTest conn = do- stream <- freshStreamId- let metadata = buildStreamMetadata $ setCustomProperty "foo" (1 :: Int)- _ <- setStreamMetadata conn stream anyVersion metadata >>= waitAsync- return ()-----------------------------------------------------------------------------------getStreamMetadataTest :: Connection -> IO ()-getStreamMetadataTest conn = do- stream <- freshStreamId- let metadata = buildStreamMetadata $ setCustomProperty "foo" (1 :: Int)- _ <- setStreamMetadata conn stream anyVersion metadata >>= waitAsync- r <- getStreamMetadata conn stream >>= waitAsync- case r of- StreamMetadataResult _ _ m ->- case getCustomProperty m "foo" of- Just i -> assertEqual "Should have equal value" (1 :: Int) i- _ -> fail "Can't find foo property"- _ -> fail $ "Stream " <> unpack stream <>" doesn't exist"-----------------------------------------------------------------------------------createPersistentTest :: Connection -> IO ()-createPersistentTest conn = do- let def = defaultPersistentSubscriptionSettings- stream <- freshStreamId- r <- createPersistentSubscription conn "group" stream def >>= waitAsync- case r of- Nothing -> return ()- Just e -> fail $ "Exception arised: " ++ show e-----------------------------------------------------------------------------------updatePersistentTest :: Connection -> IO ()-updatePersistentTest conn = do- let def = defaultPersistentSubscriptionSettings- stream <- freshStreamId- _ <- createPersistentSubscription conn "group" stream def >>= waitAsync- r <- updatePersistentSubscription conn "group" stream def >>= waitAsync- case r of- Nothing -> return ()- Just e -> fail $ "Exception arised: " ++ show e-----------------------------------------------------------------------------------deletePersistentTest :: Connection -> IO ()-deletePersistentTest conn = do- let def = defaultPersistentSubscriptionSettings- stream <- freshStreamId- _ <- createPersistentSubscription conn "group" stream def >>= waitAsync- r <- deletePersistentSubscription conn "group" stream >>= waitAsync- case r of- Nothing -> return ()- Just e -> fail $ "Exception arised: " ++ show e-----------------------------------------------------------------------------------connectToPersistentTest :: Connection -> IO ()-connectToPersistentTest conn = do- let def = defaultPersistentSubscriptionSettings- js1 = object ["baz" .= True]- js2 = object ["foo" .= True]- jss = [ js1- , js2- ]- evts = fmap (createEvent "foo" Nothing . withJson) jss- stream <- freshStreamId- _ <- createPersistentSubscription conn "group" stream def >>= waitAsync- _ <- sendEvents conn stream anyVersion evts >>= waitAsync- sub <- connectToPersistentSubscription conn "group" stream 1- _ <- waitConfirmation sub- r <- nextEvent sub- case resolvedEventDataAsJson r of- Just js_evt -> assertEqual "event 1 should match" js1 js_evt- _ -> fail "Deserialization error"-- notifyEventsProcessed sub [resolvedEventOriginalId r]-- r2 <- nextEvent sub- case resolvedEventDataAsJson r2 of- Just js_evt -> assertEqual "event 2 should match" js2 js_evt- _ -> fail "Deserialization error"-- notifyEventsProcessed sub [resolvedEventOriginalId r2]-- unsubscribe sub- let action = do- _ <- nextEvent sub- return False- res <- catch action $ \(_ :: SubscriptionClosed) -> return True- assertBool "Should have raised an exception" res-----------------------------------------------------------------------------------maxAgeTest :: Connection -> IO ()-maxAgeTest conn = do- let timespan = fromDays 1- metadata = buildStreamMetadata $ setMaxAge timespan- evt = createEvent "foo" Nothing- $ withJson (object ["type" .= (3 :: Int)])- stream <- freshStreamId- _ <- sendEvent conn stream anyVersion evt >>= waitAsync- _ <- setStreamMetadata conn stream anyVersion metadata >>= waitAsync- r <- getStreamMetadata conn stream >>= waitAsync- case r of- StreamMetadataResult _ _ m ->- assertEqual "Should have equal timespan" (Just timespan)- (streamMetadataMaxAge m)- _ -> fail $ "Stream " <> unpack stream <> " doesn't exist"-----------------------------------------------------------------------------------shutdownTest :: Connection -> IO ()-shutdownTest conn = do- stream <- freshStreamId- let js = object ["baz" .= True]- evt = createEvent "foo" Nothing $ withJson js- action = do- _ <- sendEvent conn stream anyVersion evt- return False- shutdown conn- waitTillClosed conn- res <- catch action $ \(_ :: SomeException) -> return True-- assertBool "Should have raised an exception" res
− tests/integration.hs
@@ -1,53 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------ |--- Module : Main--- Copyright : (C) 2015 Yorick Laupa--- License : (see the file LICENSE)------ Maintainer : Yorick Laupa <yo.eight@gmail.com>--- Stability : provisional--- Portability : non-portable------ Main integration entry point.----------------------------------------------------------------------------------module Main where-----------------------------------------------------------------------------------import ClassyPrelude-import Database.EventStore-import Database.EventStore.Logging-import Test.Tasty-import Test.Tasty.Ingredients.Basic-----------------------------------------------------------------------------------import Tests-----------------------------------------------------------------------------------main :: IO ()-main = do- let setts = defaultSettings- { s_credentials = Just $ credentials "admin" "changeit"- , s_reconnect_delay_secs = 1- , s_logger = Nothing- }- conn <- connect setts (Static "127.0.0.1" 1113)- let tree = tests conn- defaultMainWithIngredients [consoleTestReporter] tree-----------------------------------------------------------------------------------_logger :: Log -> IO ()-_logger l = do- t <- getCurrentTime- putStr "["- putStr $ pack $ show t- putStr "] "- showLog l- where- showLog (Info m) = do- putStr "[INFO] "- print m-- showLog (Error m) = do- putStr "!!! ERROR !!! "- print m