packages feed

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 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 @@ =============================  [![Join the chat at https://gitter.im/YoEight/eventstore](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/YoEight/eventstore?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)-[![Build Status](https://travis-ci.org/YoEight/eventstore.svg?branch=master)](https://travis-ci.org/YoEight/eventstore)+[![Build Status](https://travis-ci.org/YoEight/eventstore.svg?branch=dev%2F0.15)](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