Skip to content

Commit 635077e

Browse files
[#51] Revise exceptions handling (#55)
Problem: Currently we use `ExceptT BotException IO` which is inconvenient because both BotException, other sync exceptions and async exceptions should be handled separately. Solution: Remove ExceptT, use UnliftIO for exceptions handling, also use UnliftIO for asyncs instead of monad-control.
1 parent 861fb8c commit 635077e

File tree

9 files changed

+34
-77
lines changed

9 files changed

+34
-77
lines changed

package.yaml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,8 @@ library:
4343
- katip
4444
- lens
4545
- lens-aeson
46-
- lifted-async
47-
- lifted-base
4846
- managed
4947
- megaparsec
50-
- monad-control
51-
- mtl
5248
- nyan-interpolation
5349
- o-clock
5450
- random
@@ -65,9 +61,9 @@ library:
6561
- time
6662
- time-compat
6763
- transformers
68-
- transformers-base
6964
- tz
7065
- tztime
66+
- unliftio
7167
- unordered-containers
7268
- utf8-string
7369
- validation

src/TzBot/Cache.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ module TzBot.Cache
2323

2424
import Universum
2525

26-
import Control.Concurrent.Async.Lifted (withAsync)
2726
import Data.Cache (Cache)
2827
import Data.Cache qualified as Cache
2928
import Data.Cache.Internal qualified as CacheI
@@ -32,6 +31,7 @@ import Formatting (Buildable)
3231
import System.Clock (TimeSpec)
3332
import Text.Interpolation.Nyan (int, rmode')
3433
import Time (Hour, KnownDivRat, Nanosecond, Time(..), hour, threadDelay, toUnit)
34+
import UnliftIO.Async (withAsync)
3535

3636
import TzBot.Logger (KatipContext, katipAddNamespace, logDebug)
3737
import TzBot.Util (multTimeSpec, randomTimeSpec, timeToTimespec, (+-))

src/TzBot/Feedback/Save.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ module TzBot.Feedback.Save
99

1010
import Universum
1111

12-
import Control.Monad.Error.Class (MonadError(catchError))
1312
import Data.Aeson (ToJSON, encode)
1413
import Data.List.NonEmpty qualified as NE
1514
import Data.String.Conversions (cs)
1615
import Data.Time (UTCTime)
1716
import Data.Time.TZInfo (TZLabel)
1817
import Data.Time.Zones.All (toTZName)
1918
import Text.Interpolation.Nyan (int, rmode')
19+
import UnliftIO.Exception qualified as UnliftIO
2020

2121
import TzBot.Logger
2222
import TzBot.Render (TranslationPairs, asForOthersS, renderSlackBlocks)
@@ -34,14 +34,14 @@ data FeedbackEntry = FeedbackEntry
3434
} deriving stock (Show, Generic)
3535
deriving ToJSON via RecordWrapper FeedbackEntry
3636

37-
logFeedbackError :: (KatipContext m) => BotException -> m ()
37+
logFeedbackError :: (KatipContext m) => SomeException -> m ()
3838
logFeedbackError (displayException -> err) = do
3939
logError [int||Error occured while saving user feedback: #{err}|]
4040

4141
-- | Save user feedback to the Slack channel if configured
4242
-- and record to the file if configured.
4343
saveFeedback :: FeedbackEntry -> BotM ()
44-
saveFeedback entry = flip catchError logFeedbackError $ do
44+
saveFeedback entry = UnliftIO.handleAny logFeedbackError $ do
4545
FeedbackConfig {..} <- asks bsFeedbackConfig
4646
whenJust fcFeedbackChannel $ saveFeedbackSlack entry
4747
whenJust fcFeedbackFile $ saveFeedbackFile entry

src/TzBot/ProcessEvents.hs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Slacker
1818
pattern Interactive)
1919
import Slacker.SocketMode (InteractiveEnvelope(..))
2020
import Text.Interpolation.Nyan (int, rmode', rmode's)
21+
import UnliftIO.Exception qualified as UnliftIO
2122

2223
import TzBot.Logger
2324
import TzBot.ProcessEvents.BlockAction qualified as B
@@ -28,7 +29,7 @@ import TzBot.ProcessEvents.Message (processMessageEvent)
2829
import TzBot.RunMonad (BotM, BotState(..), runBotM)
2930
import TzBot.Slack.API.Block (ActionId(..))
3031
import TzBot.Slack.Fixtures qualified as Fixtures
31-
import TzBot.Util (catchAllErrors, encodeText)
32+
import TzBot.Util (encodeText)
3233

3334
{- |
3435
After the message event came, the bot sends some ephemerals
@@ -85,14 +86,11 @@ handler shutdownRef bState _cfg e = run $ do
8586
where
8687
run :: BotM a -> IO ()
8788
run action = void $ runBotM bState $ do
88-
eithRes <- catchAllErrors action
89-
whenLeft eithRes $ \eithErr -> do
90-
case eithErr of
91-
Left someExc
92-
| Just UserInterrupt <- fromException someExc ->
93-
liftIO $ join $ readIORef shutdownRef
94-
| otherwise -> logException someExc
95-
Right botExc -> logException botExc
89+
eithRes <- UnliftIO.trySyncOrAsync action
90+
whenLeft eithRes $ \e -> do
91+
case fromException e of
92+
Just UserInterrupt -> liftIO $ join $ readIORef shutdownRef
93+
_ -> logError [int||Error occured: #{displayException e}|]
9694

9795
envelopeIdentifier :: Text
9896
envelopeIdentifier = case e of

src/TzBot/ProcessEvents/Message.hs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ module TzBot.ProcessEvents.Message
88

99
import Universum hiding (try)
1010

11-
import Control.Concurrent.Async.Lifted (forConcurrently)
1211
import Data.List (singleton)
1312
import Data.List.NonEmpty qualified as NE
1413
import Data.Set qualified as S
1514
import Data.Text.Lazy.Builder (Builder)
1615
import System.Random (randomRIO)
17-
import Text.Interpolation.Nyan (int, rmode', rmode's)
16+
import Text.Interpolation.Nyan (int, rmode')
17+
import UnliftIO qualified
1818

1919
import TzBot.Cache qualified as Cache
2020
import TzBot.Config (Config(..))
@@ -26,7 +26,7 @@ import TzBot.Slack.API
2626
import TzBot.Slack.Events
2727
import TzBot.Slack.Fixtures qualified as Fixtures
2828
import TzBot.TimeReference (TimeReference(..))
29-
import TzBot.Util (catchAllErrors, isDevEnvironment, whenT, withMaybe)
29+
import TzBot.Util (isDevEnvironment, whenT, withMaybe)
3030

3131
data MessageEventType = METMessage | METMessageEdited
3232
deriving stock (Eq)
@@ -223,15 +223,14 @@ ephemeralsMailing channelId sendAction = do
223223
usersInChannelIds <- getChannelMembersCached channelId
224224
let setSize = S.size usersInChannelIds
225225
logInfo [int||#{setSize} users in the channel #{channelId}, sending ephemerals|]
226-
eithRes <- forConcurrently (toList usersInChannelIds) $ catchAllErrors . sendAction
226+
eithRes <- UnliftIO.forConcurrently (toList usersInChannelIds) $ UnliftIO.trySyncOrAsync . sendAction
227227
let failedMsg = "Ephemeral sending failed" :: Builder
228-
logAll :: Either SomeException BotException -> BotM ()
229-
logAll (Left se) = logError [int||#{failedMsg}, unknown error occured: #s{se}|]
230-
logAll (Right ke) = logError [int||#{failedMsg}, #{displayException ke}|]
228+
logAll :: SomeException -> BotM ()
229+
logAll se = logError [int||#{failedMsg}, #{displayException se}|]
231230

232231
processResult
233232
:: (Int, Int)
234-
-> Either (Either SomeException BotException) Bool
233+
-> Either SomeException Bool
235234
-> BotM (Int, Int)
236235
processResult (oks_, errs_) eithRes_ = case eithRes_ of
237236
Left err_ -> logAll err_ >> pure (oks_, errs_ + 1)

src/TzBot/RunMonad.hs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ module TzBot.RunMonad where
77
import Universum
88

99
import Control.Lens (makeLensesWith)
10-
import Control.Monad.Base (MonadBase)
11-
import Control.Monad.Except (MonadError)
12-
import Control.Monad.Trans.Control (MonadBaseControl)
1310
import Data.Set qualified as S
1411
import Katip qualified as K
1512
import Network.HTTP.Client (Manager)
16-
import Servant.Client (ClientError)
1713
import Text.Interpolation.Nyan (int, rmode')
14+
import UnliftIO (MonadUnliftIO)
1815

1916
import TzBot.Cache (TzCache)
2017
import TzBot.Config.Types (BotConfig)
@@ -44,24 +41,19 @@ data BotState = BotState
4441
makeLensesWith postfixFields ''BotState
4542

4643
newtype BotM a = BotM
47-
{ unBotM :: ReaderT BotState (ExceptT BotException IO) a
44+
{ unBotM :: ReaderT BotState IO a
4845
}
4946
deriving newtype
5047
( Functor, Applicative, Monad
51-
, MonadReader BotState, MonadError BotException
52-
, MonadIO, MonadBaseControl IO, MonadBase IO
48+
, MonadReader BotState
49+
, MonadIO, MonadUnliftIO
5350
)
5451

55-
runBotM :: BotState -> BotM a -> IO (Either BotException a)
52+
runBotM :: BotState -> BotM a -> IO a
5653
runBotM state action =
5754
action
5855
& unBotM
5956
& flip runReaderT state
60-
& runExceptT
61-
62-
runOrThrowBotM :: BotState -> BotM a -> IO a
63-
runOrThrowBotM state action =
64-
runBotM state action >>= either throwM pure
6557

6658
instance K.Katip BotM where
6759
localLogEnv f = local (over bsLogEnvL f)
@@ -74,7 +66,8 @@ instance K.KatipContext BotM where
7466
getKatipNamespace = view bsLogNamespaceL
7567

7668
runKatipWithBotState :: BotState -> K.KatipContextT m a -> m a
77-
runKatipWithBotState BotState {..} action = K.runKatipContextT bsLogEnv bsLogContext bsLogNamespace action
69+
runKatipWithBotState BotState {..} action =
70+
K.runKatipContextT bsLogEnv bsLogContext bsLogNamespace action
7871
----------------------------------------------------------------------------
7972
-- Exceptions
8073
----------------------------------------------------------------------------
@@ -86,7 +79,6 @@ type ErrorDescription = Text
8679
data BotException
8780
= EndpointFailed EndpointName ErrorDescription
8881
| UnexpectedResult EndpointName FunctionName ErrorDescription
89-
| ServantError ClientError
9082
deriving stock (Show, Generic)
9183

9284
instance Exception BotException where
@@ -100,5 +92,3 @@ instance Exception BotException where
10092
[int|s|
10193
'#{funcName}', unexpected result from endpoint '#{endpoint}': #{err}
10294
|]
103-
ServantError clientError ->
104-
displayException clientError

src/TzBot/Slack.hs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
module TzBot.Slack
66
( BotM(..)
77
, runBotM
8-
, runOrThrowBotM
98
, AppLevelToken(..)
109
, BotToken(..)
1110
, BotState(..)
@@ -24,7 +23,6 @@ module TzBot.Slack
2423

2524
import Universum hiding (toString)
2625

27-
import Control.Monad.Except (throwError)
2826
import Data.Aeson (Value)
2927
import Data.ByteString.UTF8 (toString)
3028
import Data.DList qualified as DList
@@ -40,13 +38,13 @@ import Servant.Client.Core
4038
(ClientError(FailureResponse), ResponseF(responseHeaders, responseStatusCode))
4139
import Text.Interpolation.Nyan (int, rmode', rmode's)
4240
import Time.Units (sec, threadDelay)
41+
import UnliftIO.Exception qualified as UnliftIO
4342

4443
import TzBot.Cache qualified as Cache
4544
import TzBot.Config
4645
import TzBot.Logger
4746
import TzBot.RunMonad
48-
(BotException(..), BotM(..), BotState(..), ErrorDescription, runBotM, runKatipWithBotState,
49-
runOrThrowBotM)
47+
(BotException(..), BotM(..), BotState(..), ErrorDescription, runBotM, runKatipWithBotState)
5048
import TzBot.Slack.API
5149
(ChannelId, Cursor, Limit(..), Message(..), MessageId(..), OpenViewReq(..), PostEphemeralReq(..),
5250
PostMessageReq(..), SlackContents(..), SlackResponse(..), ThreadId, UpdateViewReq(..), User,
@@ -107,7 +105,7 @@ retrieveOneMessage channelId messageId = do
107105
case safeHead msgs of
108106
Just msg -> pure msg
109107
Nothing ->
110-
throwError $
108+
UnliftIO.throwIO $
111109
UnexpectedResult endpointName functionName
112110
$ mkErrorMessage messageId Nothing
113111

@@ -133,7 +131,7 @@ retrieveOneMessageFromThread channelId threadId messageId = do
133131
case find (\m -> mMessageId m == messageId) msgs of
134132
Just msg -> pure msg
135133
Nothing ->
136-
throwError $
134+
UnliftIO.throwIO $
137135
UnexpectedResult endpointName functionName
138136
$ mkErrorMessage messageId $ Just threadId
139137

@@ -175,7 +173,7 @@ handleSlackError endpoint = \case
175173
SRSuccess a -> pure a
176174
SRError err_ metadata -> do
177175
logError [int||#{endpoint} error: #{err_}; metadata: #s{metadata}|]
178-
throwError $ EndpointFailed endpoint err_
176+
UnliftIO.throwIO $ EndpointFailed endpoint err_
179177

180178
handleSlackErrorSingle :: Text -> SlackResponse $ SlackContents key a -> BotM a
181179
handleSlackErrorSingle endpoint = fmap scContents . handleSlackError endpoint
@@ -261,8 +259,8 @@ usersInfo
261259
handleTooManyRequests (runClientM act clientEnv)) (cMaxRetries config)) >>= \case
262260
Right a -> pure a
263261
Left clientError -> do
264-
logError [int||Client call failed: ${show @Text clientError}|]
265-
throwError $ ServantError clientError
262+
logError [int||Client call failed: #s{clientError}|]
263+
UnliftIO.throwIO clientError
266264

267265
-- | Handles slack API response with status code 429 @Too many requests@.
268266
-- If action result is success, then return result. If action result is error

src/TzBot/Util.hs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ module TzBot.Util where
66

77
import Universum hiding (last, try)
88

9-
import Control.Exception.Lifted
109
import Control.Lens (LensRules, lensField, lensRules, mappingNamer)
11-
import Control.Monad.Except (MonadError(catchError))
12-
import Control.Monad.Trans.Control (MonadBaseControl)
1310
import Data.Aeson
1411
import Data.Aeson qualified as AeKey
1512
import Data.Aeson qualified as Aeson
@@ -176,22 +173,5 @@ lookup key ciStorage = H.lookup (CI.mk key) $ unCIStorage ciStorage
176173
postfixFields :: LensRules
177174
postfixFields = lensRules & lensField .~ mappingNamer (\n -> [n ++ "L"])
178175

179-
----
180-
-- not present in mtl-2.2.2
181-
tryError :: MonadError e m => m a -> m (Either e a)
182-
tryError action = (Right <$> action) `catchError` (pure . Left)
183-
184-
-- | This catches all the exceptions (including asynchronous ones).
185-
catchAllErrors
186-
:: (MonadError e m, MonadBaseControl IO m)
187-
=> m a
188-
-> m (Either (Either SomeException e) a)
189-
catchAllErrors action = fmap reorder $ try $ tryError action
190-
where
191-
reorder :: Either e1 (Either e2 a) -> Either (Either e1 e2) a
192-
reorder (Left e) = Left (Left e)
193-
reorder (Right (Left e)) = Left (Right e)
194-
reorder (Right (Right r)) = Right r
195-
196176
whenT :: (Applicative m) => Bool -> m Bool -> m Bool
197177
whenT cond_ action_ = if cond_ then action_ else pure False

tzbot.cabal

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,8 @@ library
135135
, katip
136136
, lens
137137
, lens-aeson
138-
, lifted-async
139-
, lifted-base
140138
, managed
141139
, megaparsec
142-
, monad-control
143-
, mtl
144140
, nyan-interpolation
145141
, o-clock
146142
, optparse-applicative
@@ -157,10 +153,10 @@ library
157153
, time
158154
, time-compat
159155
, transformers
160-
, transformers-base
161156
, tz
162157
, tztime
163158
, universum
159+
, unliftio
164160
, unordered-containers
165161
, utf8-string
166162
, validation

0 commit comments

Comments
 (0)