discord-haskell / discord-haskell

Haskell library for writing Discord bots
https://hackage.haskell.org/package/discord-haskell
Other
264 stars 57 forks source link

Thread blocks indefinitely when trying to read the username from the cache #185

Closed dwayne closed 12 months ago

dwayne commented 12 months ago

Here's the code:

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Monad.IO.Class (liftIO)
import qualified Data.Text.IO as TIO
import qualified Discord
import qualified Discord.Types

main :: IO ()
main =
  let
    opts :: Discord.RunDiscordOpts
    opts =
      Discord.def
        { Discord.discordToken = "<token>"
        , Discord.discordOnStart = onStart
        }
  in do
  userFacingError <- Discord.runDiscord opts
  TIO.putStrLn userFacingError

onStart :: Discord.DiscordHandler ()
onStart = do
  -- Reading from the cache apparently doesn't work.
  cache <- Discord.readCache
  let user = Discord.cacheCurrentUser cache
  let userName = Discord.Types.userName user

  liftIO $ TIO.putStrLn $ userName <> " has connected to Discord!"

  Discord.stopDiscord

It's supposed to be a port of the first example provided by the How to Make a Discord Bot in Python tutorial.

Here's the error:

discordOnStart handler stopped on an exception:

thread blocked indefinitely in an MVar operation
L0neGamer commented 12 months ago

The cache isn't filled if you don't have the correct runopts enabled: https://hackage.haskell.org/package/discord-haskell-1.15.6/docs/Discord.html#t:RunDiscordOpts, namely discordEnableCache.

In retrospect I think the cache should default to being filled with errors if you try to access it without this boolean being set.

dwayne commented 12 months ago

@L0neGamer Thanks, I completely missed that option. However, setting that option to True doesn't fix the problem. I get the same error as previously stated above.

L0neGamer commented 12 months ago

I think the issue here is also that at this point the user has not been set, due to timing?

The discord on start action triggers before discord sends the Ready event, which is when we first get the details to fill the cache with.

Could you try performing this action on receive of a Ready event?

L0neGamer commented 12 months ago

To fix this kind of thing in future, we could fill the cache with an error to let the user know something odd has happened, but we could also fork the on start action instead of requiring it as a precondition to the rest of the program execution, to try and make it so that the on start action can use the cache.

dwayne commented 12 months ago

@L0neGamer Thanks for all the help.

Using the Ready event allowed me to get the outcome I desired.

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Monad.IO.Class (liftIO)
import qualified Data.Text.IO as TIO
import qualified Discord
import qualified Discord.Types

main :: IO ()
main =
  let
    opts :: Discord.RunDiscordOpts
    opts =
      Discord.def
        { Discord.discordToken = "<token>"
        , Discord.discordOnEvent = onEvent
        }
  in
  Discord.runDiscord opts >>= TIO.putStrLn

onEvent :: Discord.Types.Event -> Discord.DiscordHandler ()
onEvent event =
  case event of
    Discord.Types.Ready _ user _ _ _ _ _ ->
      -- You need to enable "MESSAGE CONTENT INTENT" to work with this event.
      let
        userName = Discord.Types.userName user
      in
      liftIO $ TIO.putStrLn $ userName <> " has connected to Discord!"

    _ ->
      return ()
dwayne commented 12 months ago

Resolved!

L0neGamer commented 12 months ago

Thanks for working with me on this! I'm going to create a ticket to improve the error messages gotten from the cache, as well as investigating whether the onStart action should occur after the ready event.