mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-18 13:02:11 +03:00
2dfbf99b41
* Remove unused ExitCode constructors * Simplify shutdown logic * Update server/src-lib/Hasura/App.hs Co-authored-by: Brandon Simmons <brandon@hasura.io> * WIP: fix zombie thread issue * Use forkCodensity for the schema sync thread * Use forkCodensity for the oauthTokenUpdateWorker * Use forkCodensity for the schema update processor thread * Add deprecation notice * Logger threads use Codensity * Add the MonadFix instance for Codensity to get log-sender thread logs * Move outIdleGC out to the top level, WIP * Update forkImmortal fuction for more logging info * add back the idle GC to Pro * setupAuth * use ImmortalThreadLog * Fix tests * Add another finally block * loud warnings * Change log level * hlint * Finalize the logger in the correct place * Add ManagedT * Update server/src-lib/Hasura/Server/Auth.hs Co-authored-by: Brandon Simmons <brandon@hasura.io> * Comments etc. Co-authored-by: Brandon Simmons <brandon@hasura.io> Co-authored-by: Naveen Naidu <naveennaidu479@gmail.com> GitOrigin-RevId: 156065c5c3ace0e13d1997daef6921cc2e9f641c
133 lines
5.3 KiB
Haskell
133 lines
5.3 KiB
Haskell
module Control.Concurrent.Extended
|
|
( module Control.Concurrent
|
|
, sleep
|
|
, ForkableMonadIO
|
|
-- * Robust forking
|
|
, forkImmortal
|
|
, forkManagedT
|
|
-- * Deprecated
|
|
, threadDelay
|
|
, forkIO
|
|
) where
|
|
|
|
import Prelude
|
|
import Control.Exception
|
|
import Control.Monad.Trans.Managed (ManagedT(..), allocate)
|
|
import Control.Monad.IO.Class
|
|
import Control.Monad
|
|
import Data.Aeson
|
|
import Data.Void
|
|
|
|
import qualified Control.Concurrent as Base
|
|
import qualified Control.Concurrent.Async.Lifted.Safe as LA
|
|
import qualified Control.Immortal as Immortal
|
|
import qualified Control.Monad.Trans.Control as MC
|
|
|
|
import Control.Concurrent hiding (threadDelay, forkIO)
|
|
import Data.Time.Clock.Units (seconds, Microseconds (..), DiffTime)
|
|
|
|
|
|
-- For forkImmortal. We could also have it take a cumbersome continuation if we
|
|
-- want to break this dependency. Probably best to move Hasura.Logging into a
|
|
-- separate lib with this if we do the override thing.
|
|
import Hasura.Logging
|
|
|
|
-- | Like 'Base.threadDelay', but takes a 'DiffTime' instead of an 'Int' microseconds.
|
|
--
|
|
-- NOTE: you cannot simply replace e.g. @threadDelay 1000@ with @sleep 1000@ since those literals
|
|
-- have different meanings!
|
|
sleep :: DiffTime -> IO ()
|
|
sleep = Base.threadDelay . round . Microseconds
|
|
|
|
{-# DEPRECATED threadDelay "Please use `sleep` instead (and read the docs!)" #-}
|
|
threadDelay :: Int -> IO ()
|
|
threadDelay = Base.threadDelay
|
|
|
|
{-# DEPRECATED forkIO
|
|
"Please use 'Control.Control.Concurrent.Async.Lifted.Safe.withAsync'\
|
|
\ or our 'forkImmortal' instead formore robust threading." #-}
|
|
forkIO :: IO () -> IO ThreadId
|
|
forkIO = Base.forkIO
|
|
|
|
-- | Note: Please consider using 'forkManagedT' instead to ensure reliable
|
|
-- resource cleanup.
|
|
forkImmortal
|
|
:: ForkableMonadIO m
|
|
=> String
|
|
-- ^ A label describing this thread's function (see 'labelThread').
|
|
-> Logger Hasura
|
|
-> m Void
|
|
-- ^ An IO action we expect never to return normally. This will have the type
|
|
-- signature ':: m a' (see e.g. the type of 'forever').
|
|
-> m Immortal.Thread
|
|
-- ^ A handle for the forked thread. See "Control.Immortal".
|
|
forkImmortal label logger m =
|
|
Immortal.createWithLabel label $ \this -> do
|
|
-- Log that the thread has started
|
|
liftIO $ unLogger logger (ImmortalThreadRestarted label )
|
|
-- In this case, we are handling unexpected exceptions.
|
|
-- i.e This does not catch the asynchronous exception which stops the thread.
|
|
Immortal.onUnexpectedFinish this logAndPause (void m)
|
|
where logAndPause = \case
|
|
Right _void -> pure () -- absurd _void (i.e. unreachable)
|
|
Left e -> liftIO $ do
|
|
liftIO $ unLogger logger (ImmortalThreadUnexpectedException label e)
|
|
-- pause before restarting some arbitrary amount of time. The idea is not to flood
|
|
-- logs or cause other cascading failures.
|
|
sleep (seconds 1)
|
|
|
|
-- | This function pairs a call to 'forkImmortal' with a finalizer which stops
|
|
-- the immortal thread.
|
|
--
|
|
-- Note, the thread object can leave its scope if this function is incorrectly
|
|
-- used. Generally, the result should only be used later in the same ManagedT
|
|
-- scope.
|
|
forkManagedT
|
|
:: ForkableMonadIO m
|
|
=> String
|
|
-> Logger Hasura
|
|
-> m Void
|
|
-> ManagedT m Immortal.Thread
|
|
forkManagedT label logger m = allocate
|
|
(forkImmortal label logger m)
|
|
(\thread -> do
|
|
unLogger logger (ImmortalThreadStopping label)
|
|
liftIO $ Immortal.stop thread)
|
|
|
|
data ImmortalThreadLog
|
|
= ImmortalThreadUnexpectedException String SomeException
|
|
-- ^ Synchronous Exception
|
|
| ImmortalThreadStopping String
|
|
-- ^ Asynchronous Exception about to be sent
|
|
| ImmortalThreadRestarted String
|
|
|
|
instance ToEngineLog ImmortalThreadLog Hasura where
|
|
toEngineLog (ImmortalThreadStopping label) =
|
|
(LevelInfo, ELTInternal ILTUnstructured, toJSON msg)
|
|
where msg = "Stopping immortal " <> label <> " thread"
|
|
toEngineLog (ImmortalThreadUnexpectedException label e) =
|
|
(LevelError, ELTInternal ILTUnstructured, toJSON msg)
|
|
where msg = "Unexpected exception in immortal thread " <> label <> " (it will be restarted):\n"
|
|
<> show e
|
|
toEngineLog (ImmortalThreadRestarted label) =
|
|
(LevelInfo, ELTInternal ILTUnstructured, toJSON msg)
|
|
where msg = "Thread " <> label <> " (re)started"
|
|
|
|
-- TODO
|
|
-- - maybe use this everywhere, but also:
|
|
-- - consider unifying with: src-lib/Control/Monad/Stateless.hs ?
|
|
-- - nice TypeError: https://kodimensional.dev/type-errors
|
|
--
|
|
-- | Like 'MonadIO' but constrained to stacks in which forking a new thread is reasonable/safe.
|
|
-- In particular 'StateT' causes problems.
|
|
--
|
|
-- This is the constraint you can use for functions that call 'LA.async', or 'immortal'.
|
|
type ForkableMonadIO m = (MonadIO m, MC.MonadBaseControl IO m, LA.Forall (LA.Pure m))
|
|
|
|
|
|
-- TODO consider deprecating async.
|
|
-- export something with polymorphic return type, which makes "fork and forget" difficult
|
|
-- this could automatically link in one variant
|
|
-- another variant might return ThreadId that self destructs w/ finalizer (mkWeakThreadId)
|
|
-- and note: "Holding a normal ThreadId reference will prevent the delivery of BlockedIndefinitely exceptions because the reference could be used as the target of throwTo at any time, "
|