launchdarkly-server-sdk 1.0.1 → 1.0.2
raw patch · 4 files changed
+35/−12 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +5/−0
- launchdarkly-server-sdk.cabal +2/−2
- src/LaunchDarkly/Server/Client/Internal.hs +1/−1
- src/LaunchDarkly/Server/Network/Streaming.hs +27/−9
CHANGELOG.md view
@@ -2,6 +2,11 @@ All notable changes to the LaunchDarkly Haskell Server-side SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org). +## [1.0.2] - 2020-03-16+### Fixed:+- Added a timeout on SSE reads to 5 minutes.+- Fixed an issue where reading SSE streams would burn CPU in certain edge cases+ ## [1.0.1] - 2020-03-02 ### Fixed: - Client initialization status is now correctly determined by checking the feature store instead of an always in memory value. This is particularly important for usage of daemon mode. In the current implementation daemon mode evaluation always returns the default fallback value because the client never becomes initialized.
launchdarkly-server-sdk.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 8be83973b5569caf1cca5ec68092a736383d9f26014e6f128d1f52e2393825a5+-- hash: 3d20deec805adbfa07184ad9027c2fbf90433f00cb1a39de56f50cf6601c419f name: launchdarkly-server-sdk-version: 1.0.1+version: 1.0.2 synopsis: Server-side SDK for integrating with LaunchDarkly description: Please see the README on GitHub at <https://github.com/launchdarkly/haskell-server-sdk#readme> category: Web
src/LaunchDarkly/Server/Client/Internal.hs view
@@ -25,7 +25,7 @@ -- | The version string for this library. clientVersion :: Text-clientVersion = "1.0.1"+clientVersion = "1.0.2" -- | The status of the client initialization. data Status
src/LaunchDarkly/Server/Network/Streaming.hs view
@@ -2,9 +2,12 @@ import Data.Text (Text) import qualified Data.Text as T-import Data.Attoparsec.Text as P hiding (Result)+import Data.Attoparsec.Text as P hiding (Result, try) import Control.Monad (void, mzero, forever)+import Control.Monad.Catch (Exception, MonadCatch, try)+import Control.Exception (throwIO) import Data.ByteString (ByteString)+import qualified Data.ByteString as B import Control.Applicative (many) import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Data.HashMap.Strict (HashMap)@@ -19,6 +22,7 @@ import Control.Monad.Catch (MonadMask) import Control.Retry (RetryPolicyM, RetryStatus, fullJitterBackoff, capDelay, retrying) import Network.HTTP.Types.Status (ok200)+import System.Timeout (timeout) import LaunchDarkly.Server.Client.Internal (ClientI) import LaunchDarkly.Server.Store.Internal (StoreHandle, StoreResult, initializeStore, insertFlag, deleteFlag, deleteSegment, insertSegment)@@ -133,15 +137,29 @@ "delete" -> processDelete store value _ -> $(logWarn) "unknown event type" -readStream :: (MonadIO m, MonadLogger m) => IO ByteString -> StoreHandle IO -> m ()+data ReadE = ReadETimeout | ReadEClosed deriving (Show, Exception)++tryReadE :: MonadCatch m => m a -> m (Either ReadE a)+tryReadE = try++-- heartbeat expected every 120 seconds+readWithException :: IO ByteString -> IO Text+readWithException body = timeout (1000000 * 300) (brRead body) >>= \case+ Nothing -> throwIO ReadETimeout+ Just bytes -> if bytes == B.empty then throwIO ReadEClosed else pure (decodeUtf8 bytes)++readStream :: (MonadIO m, MonadLogger m, MonadMask m) => IO ByteString -> StoreHandle IO -> m () readStream body store = loop "" where- loop initial = liftIO (parseWith (decodeUtf8 <$> brRead body) parseEvent initial) >>= \case- Done remaining event -> do- processEvent store (name event) (L.fromStrict $ encodeUtf8 $ buffer event)- loop remaining- Fail _ context err ->- $(logError) $ T.intercalate " " ["failed parsing SSE frame", T.pack $ show context, T.pack err]- Partial _ -> $(logError) "failed parsing SSE frame unexpected partial"+ loop initial = tryReadE (parseWith (liftIO $ readWithException body) parseEvent initial) >>= \case+ (Left ReadETimeout) -> $(logError) "streaming connection unexpectedly closed"+ (Left ReadEClosed) -> $(logError) "timeout waiting for SSE event"+ (Right parsed) -> case parsed of+ Done remaining event -> do+ processEvent store (name event) (L.fromStrict $ encodeUtf8 $ buffer event)+ loop remaining+ Fail _ context err ->+ $(logError) $ T.intercalate " " ["failed parsing SSE frame", T.pack $ show context, T.pack err]+ Partial _ -> $(logError) "failed parsing SSE frame unexpected partial" -- https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ retryPolicy :: MonadIO m => RetryPolicyM m