ez-couch 0.4.1 → 0.4.2
raw patch · 13 files changed
+76/−68 lines, 13 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ EZCouch: ResponseException :: Text -> EZCouchException
Files
- ez-couch.cabal +4/−3
- src/Control/Retry.hs +0/−36
- src/EZCouch/Action.hs +5/−6
- src/EZCouch/Entity.hs +3/−3
- src/EZCouch/EntityIsolation.hs +3/−5
- src/EZCouch/Isolation.hs +6/−9
- src/EZCouch/Logging.hs +8/−0
- src/EZCouch/ReadAction.hs +4/−0
- src/EZCouch/Retry.hs +36/−0
- src/EZCouch/Sweeper.hs +2/−2
- src/EZCouch/Time.hs +1/−1
- src/EZCouch/Types.hs +2/−1
- src/Util/Logging.hs +2/−2
ez-couch.cabal view
@@ -1,5 +1,5 @@ name: ez-couch-version: 0.4.1+version: 0.4.2 cabal-version: >=1.8 build-type: Simple license: MIT@@ -18,8 +18,7 @@ hs-source-dirs: src extensions: PatternGuards exposed-modules: EZCouch- other-modules: Control.Retry- Database.CouchDB.Conduit.View.Query+ other-modules: Database.CouchDB.Conduit.View.Query EZCouch.Base62 EZCouch.Entity EZCouch.Action@@ -34,6 +33,7 @@ EZCouch.Model.View EZCouch.Parsing EZCouch.ReadAction+ EZCouch.Retry EZCouch.Time EZCouch.Try EZCouch.Types@@ -43,6 +43,7 @@ EZCouch.Model.EntityIsolation EZCouch.EntityIsolation EZCouch.Sweeper+ EZCouch.Logging build-depends: base >= 4.5 && < 5, ghc-prim >= 0.2, time >= 1.4,
− src/Control/Retry.hs
@@ -1,36 +0,0 @@-{-# LANGUAGE NoMonomorphismRestriction, OverloadedStrings #-}-module Control.Retry where--import Prelude ()-import ClassyPrelude-import Control.Concurrent-import qualified Util.Logging as Logging---retryingEither [] action = action -retryingEither (i:is) action = action >>= processResult- where - processResult (Left _) - | i == 0 = retryingEither is action- | otherwise = liftIO (threadDelay i) >> retryingEither is action- processResult r = return r--retryingEither' = retryingEither defaultIntervals--retrying exceptionIntervals action = retrying_ 0- where- retrying_ attempt = catch action processException- where- exceptionInterval = listToMaybe . drop attempt . exceptionIntervals- processException e - | Just i <- exceptionInterval e = do- Logging.logM 0 "Control.Retry"- $ "Error occurred: " ++ show e ++ ". " - ++ "Retrying with a " ++ show (i `div` sec) ++ "s delay."- unless (i == 0) (liftIO (threadDelay i)) - retrying_ (attempt + 1)- | otherwise = throwIO e--defaultIntervals = [sec, sec * 5, sec * 15]-sec = 10 ^ 6-
src/EZCouch/Action.hs view
@@ -5,21 +5,19 @@ import ClassyPrelude.Conduit import Control.Exception (SomeException(..)) import Control.Monad.Reader-import Control.Retry import System.IO.Error (ioeGetErrorString) import EZCouch.Types+import EZCouch.Logging+import EZCouch.Retry import Network.HTTP.Types as HTTP import Network.HTTP.Conduit as HTTP import Network.HTTP.Conduit.Request as HTTP import qualified Database.CouchDB.Conduit.View.Query as CC import qualified Blaze.ByteString.Builder as Blaze-import qualified Util.Logging as Logging import qualified Data.Aeson as Aeson import qualified Data.Conduit.Attoparsec as Atto -logM lvl = Logging.logM lvl "EZCouch.Action" - data ConnectionSettings = ConnectionSettings { connectionSettingsHost :: Text,@@ -58,7 +56,7 @@ queryString = query, requestBody = RequestBodyLBS body, checkStatus = checkStatus,- responseTimeout = Just $ 10 ^ 6 * 30+ responseTimeout = Just $ 10 ^ 6 * 5 } where authenticated@@ -72,7 +70,7 @@ => Request m -> m (Response (ResumableSource m ByteString)) performRequest request = do- logM 0 $ "Performing a " + logLn 0 $ "Performing a " ++ show (HTTP.method request) ++ " at " ++ show (HTTP.url request) (_, manager) <- ask@@ -85,6 +83,7 @@ | elem code [200, 201, 202, 304] = Nothing | otherwise = Just $ SomeException $ StatusCodeException status headers exceptionIntervals (ConnectionException {}) = [10^3, 10^6, 10^6*10]+ exceptionIntervals (ServerException {}) = [10^6, 10^6*10, 10^6*60] exceptionIntervals _ = [] handleHttpException e = case e of FailedConnectionException host port -> throwIO $ ConnectionException $
src/EZCouch/Entity.hs view
@@ -19,9 +19,9 @@ instance (GEntity a) => GEntity (M1 i c a) where gEntityType = gEntityType . unM1 -instance (Constructor c) => GEntity (C1 c a) where- gEntityType = const . pack $ conName (undefined :: t c a p)-+instance (Datatype d) => GEntity (D1 d a) where+ gEntityType = const . pack $ datatypeName (undefined :: t d a p)+ instance (GEntity a, GEntity b) => GEntity (a :+: b) where gEntityType (L1 x) = gEntityType x gEntityType (R1 x) = gEntityType x
src/EZCouch/EntityIsolation.hs view
@@ -14,11 +14,9 @@ import EZCouch.ReadAction import EZCouch.WriteAction import EZCouch.Try+import EZCouch.Logging import qualified EZCouch.Model.EntityIsolation as Model-import qualified Util.Logging as Logging -logM lvl = Logging.logM lvl "EZCouch.EntityIsolation"- data Isolation e = Isolation { isolationIdRev :: IdRev Model.EntityIsolation, isolationIdentified :: Identified e@@ -46,7 +44,7 @@ results <- isolateEntities timeout . singleton $ persisted case results of [result] -> return result- _ -> throwIO $ ServerException $ "EZCouch.EntityIsolation.isolateEntity"+ _ -> throwIO $ ResponseException $ "EZCouch.EntityIsolation.isolateEntity" -- | Does the same as `isolateEntity` but for multiple entities and in a single -- request.@@ -87,7 +85,7 @@ releaseIsolation = releaseIsolations . singleton >=> maybe fail return . listToMaybe where- fail = throwIO $ ServerException "EZCouch.EntityIsolation.releaseIsolation"+ fail = throwIO $ ResponseException "EZCouch.EntityIsolation.releaseIsolation" releaseIsolations :: (MonadAction m, Entity e) => [Isolation e]
src/EZCouch/Isolation.hs view
@@ -7,15 +7,12 @@ import EZCouch.Time import EZCouch.Types-import EZCouch.Action hiding (logM)+import EZCouch.Action import EZCouch.ReadAction import EZCouch.WriteAction import EZCouch.View import EZCouch.Model.Isolation as Isolation--import qualified Util.Logging as Logging--logM lvl = Logging.logM lvl "EZCouch.Isolation"+import EZCouch.Logging -- | Protect an action from being executed on multiple clients. Can be used to create transactions in a preemptive manner, i.e. instead of performing some actions and rolling back on transaction validation failure it does validation based on the provided identifier prior to actually executing the transaction. This function however does not provide you with atomicity guarantees (<http://en.wikipedia.org/wiki/Atomicity_(database_systems)>), as it does not rollback in case of client-interrupt - it's up to your algorithms to handle those cases. inIsolation :: MonadAction m @@ -33,18 +30,18 @@ Just isolation -> do if (Isolation.since . persistedValue) isolation < Time.addUTCTime (negate $ fromIntegral timeout) time then do - logM 0 $ "Deleting outdated isolation: " ++ id'+ logLn 0 $ "Deleting outdated isolation: " ++ id' tryToDelete isolation inIsolation timeout id action else do- logM 0 $ "Skipping a busy isolation: " ++ id'+ logLn 0 $ "Skipping a busy isolation: " ++ id' return Nothing Nothing -> do- logM 0 $ "Skipping a finished isolation: " ++ id'+ logLn 0 $ "Skipping a finished isolation: " ++ id' return Nothing Left e -> throwIO e Right isolation -> do- logM 0 $ "Performing an isolation: " ++ id'+ logLn 0 $ "Performing an isolation: " ++ id' finally (Just <$> action) (deleteEntity isolation) where id' = "EZCouchIsolation-" ++ id
+ src/EZCouch/Logging.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction, FlexibleContexts, MultiParamTypeClasses, ScopedTypeVariables, DeriveDataTypeable, DeriveGeneric #-}+module EZCouch.Logging where++import Prelude ()+import ClassyPrelude+import qualified Util.Logging as Logging++logLn lvl = Logging.logLn lvl "EZCouch"
src/EZCouch/ReadAction.hs view
@@ -8,6 +8,7 @@ import EZCouch.Types import EZCouch.Parsing import EZCouch.View+import EZCouch.Logging import qualified EZCouch.Encoding as Encoding import qualified Database.CouchDB.Conduit.View.Query as CC import qualified System.Random as Random@@ -39,6 +40,9 @@ HTTP.StatusCodeException (HTTP.Status code _) _ | code `elem` [404, 500] -> do+ logLn 2 $ "View " + ++ fromMaybe undefined (viewGeneratedName view) + ++ " does not exist. Generating." createOrUpdateView view action path qps body _ -> throwIO e
+ src/EZCouch/Retry.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE NoMonomorphismRestriction, OverloadedStrings #-}+module EZCouch.Retry where++import Prelude ()+import ClassyPrelude+import Control.Concurrent+import EZCouch.Logging+++retryingEither [] action = action +retryingEither (i:is) action = action >>= processResult+ where + processResult (Left _) + | i == 0 = retryingEither is action+ | otherwise = liftIO (threadDelay i) >> retryingEither is action+ processResult r = return r++retryingEither' = retryingEither defaultIntervals++retrying exceptionIntervals action = retrying_ 0+ where+ retrying_ attempt = catch action processException+ where+ exceptionInterval = listToMaybe . drop attempt . exceptionIntervals+ processException e + | Just i <- exceptionInterval e = do+ logLn 2 + $ "Error occurred: " ++ show e ++ ". " + ++ "Retrying with a " ++ show (i `div` sec) ++ "s delay."+ unless (i == 0) (liftIO (threadDelay i)) + retrying_ (attempt + 1)+ | otherwise = throwIO e++defaultIntervals = [sec, sec * 5, sec * 15]+sec = 10 ^ 6+
src/EZCouch/Sweeper.hs view
@@ -17,11 +17,11 @@ import EZCouch.Model.EntityIsolation (EntityIsolation) import qualified EZCouch.Model.EntityIsolation as EntityIsolation import EZCouch.Isolation-import qualified Util.Logging as Logging+import EZCouch.Logging runSweeper = forever $ do- Logging.logM 0 "EZCouch.Sweeper" $ "Sweeping zombie entity isolations"+ logLn 2 $ "Sweeping zombie entity isolations" readZombieEntityIsolations >>= releaseIsolations liftIO $ threadDelay $ 10 ^ 6 * 60 * 60 * 24 * 2
src/EZCouch/Time.hs view
@@ -20,7 +20,7 @@ Just time -> return time Nothing -> throwIO $ ParsingException $ "Couldn't parse date: `" ++ decodeUtf8 value ++ "`" | otherwise = getHeadersTime tail-getHeadersTime _ = throwIO $ ServerException "No date header in response"+getHeadersTime _ = throwIO $ ResponseException "No date header in response" toTime = parseTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %Z" . unpack . asText . decodeUtf8
src/EZCouch/Types.hs view
@@ -38,9 +38,10 @@ -- ^ A response from CouchDB could not be parsed. | OperationException Text -- ^ An operation failed, e.g. a document couldn't be created or deleted.- | ServerException Text+ | ResponseException Text -- ^ E.g., server provided an unexpected response | ConnectionException Text+ | ServerException Text deriving (Show, Typeable) instance Exception EZCouchException
src/Util/Logging.hs view
@@ -15,8 +15,8 @@ import Data.Time (getZonedTime, getCurrentTime, formatTime) import Control.Concurrent (myThreadId) -logM :: (MonadIO m) => Int -> Text -> Text -> m ()-logM level logger message = liftIO $ +logLn :: (MonadIO m) => Int -> Text -> Text -> m ()+logLn level logger message = liftIO $ Logger.logM (unpack logger) (levelPriority level) (unpack message) initialize = initializeWithFormat "$level $time, $logger: $message"