calamity 0.7.0.1 → 0.7.1.0
raw patch · 5 files changed
+86/−68 lines, 5 filesdep ~PyFdep ~aesondep ~http-api-dataPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: PyF, aeson, http-api-data, req, typerep-map, vector
API changes (from Hackage documentation)
- Calamity.Cache.Eff: delDM :: forall r_a4YKB. Member CacheEff r_a4YKB => Snowflake DMChannel -> Sem r_a4YKB ()
+ Calamity.Cache.Eff: delDM :: forall r_a4Xt4. Member CacheEff r_a4Xt4 => Snowflake DMChannel -> Sem r_a4Xt4 ()
- Calamity.Cache.Eff: delGuild :: forall r_a4YKu. Member CacheEff r_a4YKu => Snowflake Guild -> Sem r_a4YKu ()
+ Calamity.Cache.Eff: delGuild :: forall r_a4XsX. Member CacheEff r_a4XsX => Snowflake Guild -> Sem r_a4XsX ()
- Calamity.Cache.Eff: delMessage :: forall r_a4YKW. Member CacheEff r_a4YKW => Snowflake Message -> Sem r_a4YKW ()
+ Calamity.Cache.Eff: delMessage :: forall r_a4Xtp. Member CacheEff r_a4Xtp => Snowflake Message -> Sem r_a4Xtp ()
- Calamity.Cache.Eff: delUnavailableGuild :: forall r_a4YKP. Member CacheEff r_a4YKP => Snowflake Guild -> Sem r_a4YKP ()
+ Calamity.Cache.Eff: delUnavailableGuild :: forall r_a4Xti. Member CacheEff r_a4Xti => Snowflake Guild -> Sem r_a4Xti ()
- Calamity.Cache.Eff: delUser :: forall r_a4YKI. Member CacheEff r_a4YKI => Snowflake User -> Sem r_a4YKI ()
+ Calamity.Cache.Eff: delUser :: forall r_a4Xtb. Member CacheEff r_a4Xtb => Snowflake User -> Sem r_a4Xtb ()
- Calamity.Cache.Eff: getBotUser :: forall r_a4YKm. Member CacheEff r_a4YKm => Sem r_a4YKm (Maybe User)
+ Calamity.Cache.Eff: getBotUser :: forall r_a4XsP. Member CacheEff r_a4XsP => Sem r_a4XsP (Maybe User)
- Calamity.Cache.Eff: getDM :: forall r_a4YKy. Member CacheEff r_a4YKy => Snowflake DMChannel -> Sem r_a4YKy (Maybe DMChannel)
+ Calamity.Cache.Eff: getDM :: forall r_a4Xt1. Member CacheEff r_a4Xt1 => Snowflake DMChannel -> Sem r_a4Xt1 (Maybe DMChannel)
- Calamity.Cache.Eff: getDMs :: forall r_a4YKA. Member CacheEff r_a4YKA => Sem r_a4YKA [DMChannel]
+ Calamity.Cache.Eff: getDMs :: forall r_a4Xt3. Member CacheEff r_a4Xt3 => Sem r_a4Xt3 [DMChannel]
- Calamity.Cache.Eff: getGuild :: forall r_a4YKp. Member CacheEff r_a4YKp => Snowflake Guild -> Sem r_a4YKp (Maybe Guild)
+ Calamity.Cache.Eff: getGuild :: forall r_a4XsS. Member CacheEff r_a4XsS => Snowflake Guild -> Sem r_a4XsS (Maybe Guild)
- Calamity.Cache.Eff: getGuildChannel :: forall r_a4YKr. Member CacheEff r_a4YKr => Snowflake GuildChannel -> Sem r_a4YKr (Maybe GuildChannel)
+ Calamity.Cache.Eff: getGuildChannel :: forall r_a4XsU. Member CacheEff r_a4XsU => Snowflake GuildChannel -> Sem r_a4XsU (Maybe GuildChannel)
- Calamity.Cache.Eff: getGuilds :: forall r_a4YKt. Member CacheEff r_a4YKt => Sem r_a4YKt [Guild]
+ Calamity.Cache.Eff: getGuilds :: forall r_a4XsW. Member CacheEff r_a4XsW => Sem r_a4XsW [Guild]
- Calamity.Cache.Eff: getMessage :: forall r_a4YKT. Member CacheEff r_a4YKT => Snowflake Message -> Sem r_a4YKT (Maybe Message)
+ Calamity.Cache.Eff: getMessage :: forall r_a4Xtm. Member CacheEff r_a4Xtm => Snowflake Message -> Sem r_a4Xtm (Maybe Message)
- Calamity.Cache.Eff: getMessages :: forall r_a4YKV. Member CacheEff r_a4YKV => Sem r_a4YKV [Message]
+ Calamity.Cache.Eff: getMessages :: forall r_a4Xto. Member CacheEff r_a4Xto => Sem r_a4Xto [Message]
- Calamity.Cache.Eff: getUnavailableGuilds :: forall r_a4YKO. Member CacheEff r_a4YKO => Sem r_a4YKO [Snowflake Guild]
+ Calamity.Cache.Eff: getUnavailableGuilds :: forall r_a4Xth. Member CacheEff r_a4Xth => Sem r_a4Xth [Snowflake Guild]
- Calamity.Cache.Eff: getUser :: forall r_a4YKF. Member CacheEff r_a4YKF => Snowflake User -> Sem r_a4YKF (Maybe User)
+ Calamity.Cache.Eff: getUser :: forall r_a4Xt8. Member CacheEff r_a4Xt8 => Snowflake User -> Sem r_a4Xt8 (Maybe User)
- Calamity.Cache.Eff: getUsers :: forall r_a4YKH. Member CacheEff r_a4YKH => Sem r_a4YKH [User]
+ Calamity.Cache.Eff: getUsers :: forall r_a4Xta. Member CacheEff r_a4Xta => Sem r_a4Xta [User]
- Calamity.Cache.Eff: isUnavailableGuild :: forall r_a4YKM. Member CacheEff r_a4YKM => Snowflake Guild -> Sem r_a4YKM Bool
+ Calamity.Cache.Eff: isUnavailableGuild :: forall r_a4Xtf. Member CacheEff r_a4Xtf => Snowflake Guild -> Sem r_a4Xtf Bool
- Calamity.Cache.Eff: setBotUser :: forall r_a4YKk. Member CacheEff r_a4YKk => User -> Sem r_a4YKk ()
+ Calamity.Cache.Eff: setBotUser :: forall r_a4XsN. Member CacheEff r_a4XsN => User -> Sem r_a4XsN ()
- Calamity.Cache.Eff: setDM :: forall r_a4YKw. Member CacheEff r_a4YKw => DMChannel -> Sem r_a4YKw ()
+ Calamity.Cache.Eff: setDM :: forall r_a4XsZ. Member CacheEff r_a4XsZ => DMChannel -> Sem r_a4XsZ ()
- Calamity.Cache.Eff: setGuild :: forall r_a4YKn. Member CacheEff r_a4YKn => Guild -> Sem r_a4YKn ()
+ Calamity.Cache.Eff: setGuild :: forall r_a4XsQ. Member CacheEff r_a4XsQ => Guild -> Sem r_a4XsQ ()
- Calamity.Cache.Eff: setMessage :: forall r_a4YKR. Member CacheEff r_a4YKR => Message -> Sem r_a4YKR ()
+ Calamity.Cache.Eff: setMessage :: forall r_a4Xtk. Member CacheEff r_a4Xtk => Message -> Sem r_a4Xtk ()
- Calamity.Cache.Eff: setUnavailableGuild :: forall r_a4YKK. Member CacheEff r_a4YKK => Snowflake Guild -> Sem r_a4YKK ()
+ Calamity.Cache.Eff: setUnavailableGuild :: forall r_a4Xtd. Member CacheEff r_a4Xtd => Snowflake Guild -> Sem r_a4Xtd ()
- Calamity.Cache.Eff: setUser :: forall r_a4YKD. Member CacheEff r_a4YKD => User -> Sem r_a4YKD ()
+ Calamity.Cache.Eff: setUser :: forall r_a4Xt6. Member CacheEff r_a4Xt6 => User -> Sem r_a4Xt6 ()
- Calamity.HTTP.Internal.Ratelimit: getRatelimitState :: forall r_a3TPe. Member RatelimitEff r_a3TPe => Sem r_a3TPe RateLimitState
+ Calamity.HTTP.Internal.Ratelimit: getRatelimitState :: forall r_a3SEC. Member RatelimitEff r_a3SEC => Sem r_a3SEC RateLimitState
- Calamity.Interactions.Eff: getInteraction :: forall r_a3LVR. Member InteractionEff r_a3LVR => Sem r_a3LVR Interaction
+ Calamity.Interactions.Eff: getInteraction :: forall r_a3IND. Member InteractionEff r_a3IND => Sem r_a3IND Interaction
- Calamity.Interactions.View: endView :: forall ret_a6xeN inp_a6xeO sendResp_a6xeP r_a6xsv. Member (ViewEff ret_a6xeN inp_a6xeO sendResp_a6xeP) r_a6xsv => ret_a6xeN -> Sem r_a6xsv ()
+ Calamity.Interactions.View: endView :: forall ret_a6vo6 inp_a6vo7 sendResp_a6vo8 r_a6vBO. Member (ViewEff ret_a6vo6 inp_a6vo7 sendResp_a6vo8) r_a6vBO => ret_a6vo6 -> Sem r_a6vBO ()
- Calamity.Interactions.View: getSendResponse :: forall ret_a6xeV inp_a6xeW sendResp_a6xeX r_a6xsA. Member (ViewEff ret_a6xeV inp_a6xeW sendResp_a6xeX) r_a6xsA => Sem r_a6xsA sendResp_a6xeX
+ Calamity.Interactions.View: getSendResponse :: forall ret_a6voe inp_a6vof sendResp_a6vog r_a6vBT. Member (ViewEff ret_a6voe inp_a6vof sendResp_a6vog) r_a6vBT => Sem r_a6vBT sendResp_a6vog
- Calamity.Interactions.View: replaceView :: forall ret_a6xeT inp_a6xeR sendResp_a6xeU r_a6xsx. Member (ViewEff ret_a6xeT inp_a6xeR sendResp_a6xeU) r_a6xsx => View inp_a6xeR -> ([Component] -> Sem r_a6xsx ()) -> Sem r_a6xsx ()
+ Calamity.Interactions.View: replaceView :: forall ret_a6voc inp_a6voa sendResp_a6vod r_a6vBQ. Member (ViewEff ret_a6voc inp_a6voa sendResp_a6vod) r_a6vBQ => View inp_a6voa -> ([Component] -> Sem r_a6vBQ ()) -> Sem r_a6vBQ ()
- Calamity.Internal.LocalWriter: llisten :: forall o_atuZ r_atwp a_X0. Member (LocalWriter o_atuZ) r_atwp => Sem r_atwp a_X0 -> Sem r_atwp (o_atuZ, a_X0)
+ Calamity.Internal.LocalWriter: llisten :: forall o_attO r_atve a_X0. Member (LocalWriter o_attO) r_atve => Sem r_atve a_X0 -> Sem r_atve (o_attO, a_X0)
- Calamity.Internal.LocalWriter: ltell :: forall o_atuV r_atwn. Member (LocalWriter o_atuV) r_atwn => o_atuV -> Sem r_atwn ()
+ Calamity.Internal.LocalWriter: ltell :: forall o_attK r_atvc. Member (LocalWriter o_attK) r_atvc => o_attK -> Sem r_atvc ()
- Calamity.Internal.Utils: whileMFinalIO :: Sem r Bool -> Sem r ()
+ Calamity.Internal.Utils: whileMFinalIO :: Member (Final IO) r => Sem r Bool -> Sem r ()
- Calamity.Metrics.Eff: addCounter :: forall r_auT0. Member MetricEff r_auT0 => Int -> Counter -> Sem r_auT0 Int
+ Calamity.Metrics.Eff: addCounter :: forall r_auRR. Member MetricEff r_auRR => Int -> Counter -> Sem r_auRR Int
- Calamity.Metrics.Eff: modifyGauge :: forall r_auT3. Member MetricEff r_auT3 => (Double -> Double) -> Gauge -> Sem r_auT3 Double
+ Calamity.Metrics.Eff: modifyGauge :: forall r_auRU. Member MetricEff r_auRU => (Double -> Double) -> Gauge -> Sem r_auRU Double
- Calamity.Metrics.Eff: observeHistogram :: forall r_auT6. Member MetricEff r_auT6 => Double -> Histogram -> Sem r_auT6 HistogramSample
+ Calamity.Metrics.Eff: observeHistogram :: forall r_auRX. Member MetricEff r_auRX => Double -> Histogram -> Sem r_auRX HistogramSample
- Calamity.Metrics.Eff: registerCounter :: forall r_auSQ. Member MetricEff r_auSQ => Text -> [(Text, Text)] -> Sem r_auSQ Counter
+ Calamity.Metrics.Eff: registerCounter :: forall r_auRH. Member MetricEff r_auRH => Text -> [(Text, Text)] -> Sem r_auRH Counter
- Calamity.Metrics.Eff: registerGauge :: forall r_auST. Member MetricEff r_auST => Text -> [(Text, Text)] -> Sem r_auST Gauge
+ Calamity.Metrics.Eff: registerGauge :: forall r_auRK. Member MetricEff r_auRK => Text -> [(Text, Text)] -> Sem r_auRK Gauge
- Calamity.Metrics.Eff: registerHistogram :: forall r_auSW. Member MetricEff r_auSW => Text -> [(Text, Text)] -> [Double] -> Sem r_auSW Histogram
+ Calamity.Metrics.Eff: registerHistogram :: forall r_auRN. Member MetricEff r_auRN => Text -> [(Text, Text)] -> [Double] -> Sem r_auRN Histogram
- Calamity.Types.TokenEff: getBotToken :: forall r_a1FbI. Member TokenEff r_a1FbI => Sem r_a1FbI Token
+ Calamity.Types.TokenEff: getBotToken :: forall r_a1ELy. Member TokenEff r_a1ELy => Sem r_a1ELy Token
Files
- Calamity/HTTP/Internal/Ratelimit.hs +47/−32
- Calamity/HTTP/Internal/Route.hs +8/−8
- Calamity/Internal/Utils.hs +19/−20
- ChangeLog.md +4/−0
- calamity.cabal +8/−8
Calamity/HTTP/Internal/Ratelimit.hs view
@@ -62,6 +62,27 @@ Nothing -> pure $ UnknownRatelimit h +mergeBucketStates :: BucketState -> BucketState -> BucketState+mergeBucketStates old new =+ new+ { ongoing = old ^. #ongoing+ , -- we only ignore the previous 'remaining' if we've not reset yet and the+ -- reset time has changed+ remaining =+ if isJust (old ^. #resetTime) && old ^. #resetKey /= new ^. #resetKey+ then min (old ^. #remaining) (new ^. #remaining)+ else new ^. #remaining+ , -- only take the new resetTime if it actually changed+ resetTime =+ if old ^. #resetKey /= new ^. #resetKey+ then new ^. #resetTime+ else old ^. #resetTime+ }+++updateKnownBucket :: Bucket -> BucketState -> STM ()+updateKnownBucket bucket bucketState = modifyTVar' (bucket ^. #state) (`mergeBucketStates` bucketState)+ {- | Knowing the bucket for a route, and the ratelimit info, map the route to the bucket key and retrieve the bucket -}@@ -75,32 +96,23 @@ bucket <- SC.lookup bucketKey' $ buckets s case bucket of Just bucket' -> do- modifyTVar' (bucket' ^. #state) (`mergeStates` bucketState)+ modifyTVar' (bucket' ^. #state) (`mergeBucketStates` bucketState) pure bucket' Nothing -> Prelude.error "Not possible" Nothing -> do- -- the bucket key wasn't known, make a new bucket and insert it- bs <- Bucket <$> newTVar bucketState- SC.insert bs b $ buckets s+ -- we didn't know the key to this bucket, but we might know the bucket+ -- if we truly don't know the bucket, then make a new one+ bs <- do+ bucket <- SC.lookup b $ buckets s+ case bucket of+ Just bs -> pure bs+ Nothing -> do+ bs <- Bucket <$> newTVar bucketState+ SC.insert bs b $ buckets s+ pure bs+ SC.insert b h $ bucketKeys s pure bs- where- mergeStates :: BucketState -> BucketState -> BucketState- mergeStates old new =- new- { ongoing = old ^. #ongoing- , -- we only ignore the previous 'remaining' if we've not reset yet and the- -- reset time has changed- remaining =- if isJust (old ^. #resetTime) && old ^. #resetKey /= new ^. #resetKey- then min (old ^. #remaining) (new ^. #remaining)- else new ^. #remaining- , -- only take the new resetTime if it actually changed- resetTime =- if old ^. #resetKey /= new ^. #resetKey- then new ^. #resetTime- else old ^. #resetTime- } resetBucket :: Bucket -> STM () resetBucket bucket =@@ -178,8 +190,6 @@ -- [0] wait and then retry after we can unlock the bucket pure (intoWaitDelay $ s ^. #resetTime) - -- putStrLn (show now <> ": Using bucket, waiting until: " <> show mWaitDelay <> ", uses: " <> show s <> ", " <> inf)- case mWaitDelay of WaitUntil waitUntil -> do if waitUntil < now@@ -336,36 +346,41 @@ void . P.embed . atomically $ do case rl of KnownRatelimit bucket ->- modifyTVar' (bucket ^. #state) (#ongoing %~ succ)+ modifyTVar' (bucket ^. #state) (#ongoing %~ pred) _ -> pure ()- case rlHeaders of- Just (bs, bk) ->+ case (rl, rlHeaders) of+ (KnownRatelimit bucket, Just (bs, _bk)) ->+ updateKnownBucket bucket bs+ (_, Just (bs, bk)) -> void $ updateBucket rlstate (routeKey route) bk bs- Nothing -> pure ()+ (_, Nothing) -> pure () pure $ RGood v+ Ratelimited unlockWhen False (Just (bs, bk)) -> do debug . T.pack $ "429 ratelimited on route, retrying at " <> show unlockWhen P.embed . atomically $ do case rl of- KnownRatelimit bucket ->- modifyTVar' (bucket ^. #state) (#ongoing %~ succ)- _ -> pure ()- void $ updateBucket rlstate (routeKey route) bk bs+ KnownRatelimit bucket -> do+ modifyTVar' (bucket ^. #state) (#ongoing %~ pred)+ updateKnownBucket bucket bs+ _ -> void $ updateBucket rlstate (routeKey route) bk bs P.embed $ do threadDelayUntil unlockWhen pure $ Retry (HTTPError 429 Nothing)+ Ratelimited unlockWhen False _ -> do debug "Internal error (ratelimited but no headers), retrying" case rl of KnownRatelimit bucket ->- void . P.embed . atomically $ modifyTVar' (bucket ^. #state) (#ongoing %~ succ)+ void . P.embed . atomically $ modifyTVar' (bucket ^. #state) (#ongoing %~ pred) _ -> pure () P.embed $ threadDelayUntil unlockWhen pure $ Retry (HTTPError 429 Nothing)+ Ratelimited unlockWhen True bs -> do debug "429 ratelimited globally"
Calamity/HTTP/Internal/Route.hs view
@@ -174,17 +174,17 @@ Route buildRoute (UnsafeMkRouteBuilder route ids params) = Route- (foldl' (/:) baseURL $ map goR route)- (T.concat (map goIdent route))+ (foldl' (/:) baseURL $ map goRoute route)+ (T.concat (map goKey route)) (Snowflake <$> lookup (typeRep (Proxy @Channel)) ids) (Snowflake <$> lookup (typeRep (Proxy @Guild)) ids) where- goR (S' t) = t- goR (PS' t) = fromJust $ lookup t params- goR (ID' t) = TextShow.showt . fromJust $ lookup t ids+ goRoute (S' t) = t+ goRoute (PS' t) = fromJust $ lookup t params+ goRoute (ID' t) = TextShow.showt . fromJust $ lookup t ids - goIdent (S' t) = t- goIdent (PS' s) = T.pack s- goIdent (ID' t) = TextShow.showt t+ goKey (S' t) = t+ goKey (PS' t) = T.pack t+ goKey (ID' t) = TextShow.showt t $(makeFieldLabelsNoPrefix ''Route)
Calamity/Internal/Utils.hs view
@@ -26,37 +26,38 @@ CalamityToJSON' (..), ) where --- import Calamity.Internal.RunIntoIO+import Calamity.Internal.RunIntoIO import Calamity.Types.LogEff import Control.Applicative-import Control.Monad (when)+import qualified Data.Aeson as Aeson import Data.Aeson.Encoding (null_) import Data.Default.Class import qualified Data.Map as M+import Data.Maybe (catMaybes) import Data.Semigroup (Last (..)) import Data.Text import qualified Data.Vector.Unboxing as VU import qualified DiPolysemy as Di import qualified Polysemy as P import TextShow-import qualified Data.Aeson as Aeson-import Data.Maybe (catMaybes) {- | Like whileM, but stateful effects are not preserved to mitigate memory leaks This means Polysemy.Error won't work to break the loop, etc. Instead, Error/Alternative will just result in the loop quitting. -}-whileMFinalIO :: P.Sem r Bool -> P.Sem r ()+whileMFinalIO :: P.Member (P.Final IO) r => P.Sem r Bool -> P.Sem r () whileMFinalIO action = do- go action+ action' <- runSemToIO action+ P.embedFinal $ go action' where- go action = do- r <- action- when r $ go_b action- {-# INLINE go #-}- go_b = go- {-# NOINLINE go_b #-}+ go action' = do+ r <- action'+ case r of+ Just True ->+ go action'+ _ ->+ pure () {- | Like untilJust, but stateful effects are not preserved to mitigate memory leaks @@ -65,18 +66,16 @@ -} untilJustFinalIO :: P.Member (P.Final IO) r => P.Sem r (Maybe a) -> P.Sem r a untilJustFinalIO action = do- go action+ action' <- runSemToIO action+ P.embedFinal $ go action' where- go action = do- r <- action+ go action' = do+ r <- action' case r of- Just a ->+ Just (Just a) -> pure a _ ->- go_b action- {-# INLINE go #-}- go_b = go- {-# NOINLINE go_b #-}+ go action' whenJust :: Applicative m => Maybe a -> (a -> m ()) -> m () whenJust = flip $ maybe (pure ())
ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for Calamity +## 0.7.1.0+++ Fix ratelimits being effectively broken (succ -> pred)+ ## 0.7.0.1 + Fix accidental semver breakage
calamity.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: calamity-version: 0.7.0.1+version: 0.7.1.0 synopsis: A library for writing discord bots in haskell description: Please see the README on GitHub at <https://github.com/simmsb/calamity#readme> category: Network, Web@@ -18,7 +18,7 @@ license-file: LICENSE build-type: Simple tested-with:- GHC == 8.10.7, GHC == 9.2.4+ GHC == 8.10.7, GHC == 9.2.5 extra-source-files: README.md ChangeLog.md@@ -186,8 +186,8 @@ QuasiQuotes ghc-options: -fplugin=Polysemy.Plugin -funbox-strict-fields -Wall -fno-warn-name-shadowing build-depends:- PyF ==0.10.*- , aeson ==2.0.*+ PyF ==0.11.*+ , aeson >=2.0 && <2.2 , async >=2.2 && <3 , base >=4.13 && <5 , bytestring >=0.10 && <0.12@@ -206,7 +206,7 @@ , exceptions ==0.10.* , focus >=1.0 && <2 , hashable >=1.2 && <2- , http-api-data >=0.4.3 && <0.5+ , http-api-data >=0.4.3 && <0.6 , http-client >=0.5 && <0.8 , http-date >=0.0.8 && <0.1 , http-types ==0.12.*@@ -216,7 +216,7 @@ , polysemy >=1.5 && <2 , polysemy-plugin >=0.3 && <0.5 , reflection >=2.1 && <3- , req >=3.9.2 && <3.11+ , req >=3.9.2 && <3.14 , safe-exceptions >=0.1 && <2 , scientific ==0.3.* , stm >=2.5 && <3@@ -226,11 +226,11 @@ , text-show >=3.8 && <4 , time >=1.8 && <1.13 , tls >=1.4 && <2- , typerep-map >=0.3 && <0.6+ , typerep-map >=0.5 && <0.7 , unagi-chan ==0.4.* , unboxing-vector ==0.2.* , unordered-containers ==0.2.*- , vector ==0.12.*+ , vector >=0.12 && <0.14 , websockets >=0.12 && <0.13 , x509-system >=1.6.6 && <1.7 , random >=1.2 && <1.3