2022-03-09 01:59:28 +03:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
|
|
|
module Hasura.Server.SchemaCacheRef
|
|
|
|
( SchemaCacheRef,
|
|
|
|
initialiseSchemaCacheRef,
|
|
|
|
withSchemaCacheUpdate,
|
|
|
|
readSchemaCacheRef,
|
|
|
|
getSchemaCache,
|
|
|
|
|
|
|
|
-- * Utility
|
|
|
|
logInconsistentMetadata,
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Control.Concurrent.MVar.Lifted
|
|
|
|
import Control.Concurrent.STM qualified as STM
|
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl)
|
|
|
|
import Data.IORef
|
|
|
|
import Hasura.Logging qualified as L
|
|
|
|
import Hasura.Prelude hiding (get, put)
|
|
|
|
import Hasura.RQL.DDL.Schema
|
2022-04-27 16:57:28 +03:00
|
|
|
import Hasura.RQL.Types.Metadata.Object
|
|
|
|
import Hasura.RQL.Types.SchemaCache
|
2022-03-09 01:59:28 +03:00
|
|
|
import Hasura.Server.Logging
|
|
|
|
import Hasura.Server.Metrics
|
|
|
|
( ServerMetrics (smSchemaCacheMetadataResourceVersion),
|
|
|
|
)
|
|
|
|
import System.Metrics.Gauge (Gauge)
|
|
|
|
import System.Metrics.Gauge qualified as Gauge
|
|
|
|
|
|
|
|
-- | A mutable reference to a 'RebuildableSchemaCache', plus
|
|
|
|
--
|
|
|
|
-- * a write lock,
|
|
|
|
-- * update version tracking, and
|
|
|
|
-- * a gauge metric that tracks the metadata version of the 'SchemaCache'.
|
|
|
|
data SchemaCacheRef = SchemaCacheRef
|
|
|
|
{ -- | The idea behind explicit locking here is to
|
|
|
|
--
|
|
|
|
-- 1. Allow maximum throughput for serving requests (/v1/graphql) (as each
|
|
|
|
-- request reads the current schemacache)
|
|
|
|
-- 2. We don't want to process more than one request at any point of time
|
|
|
|
-- which would modify the schema cache as such queries are expensive.
|
|
|
|
--
|
|
|
|
-- Another option is to consider removing this lock in place of `_scrCache ::
|
|
|
|
-- MVar ...` if it's okay or in fact correct to block during schema update in
|
|
|
|
-- e.g. _wseGCtxMap. Vamshi says: It is theoretically possible to have a
|
|
|
|
-- situation (in between building new schemacache and before writing it to
|
|
|
|
-- the IORef) where we serve a request with a stale schemacache but I guess
|
|
|
|
-- it is an okay trade-off to pay for a higher throughput (I remember doing a
|
|
|
|
-- bunch of benchmarks to test this hypothesis).
|
|
|
|
_scrLock :: MVar (),
|
|
|
|
_scrCache :: IORef (RebuildableSchemaCache, SchemaCacheVer),
|
|
|
|
-- | The gauge metric that tracks the current metadata version.
|
|
|
|
--
|
|
|
|
-- Invariant: This gauge must be updated via 'updateMetadataVersionGauge'
|
|
|
|
-- whenever the _scrCache IORef is updated.
|
|
|
|
_scrMetadataVersionGauge :: Gauge
|
|
|
|
}
|
|
|
|
|
|
|
|
-- | Build a new 'SchemaCacheRef'
|
|
|
|
initialiseSchemaCacheRef ::
|
|
|
|
MonadIO m => ServerMetrics -> RebuildableSchemaCache -> m SchemaCacheRef
|
|
|
|
initialiseSchemaCacheRef serverMetrics schemaCache = liftIO $ do
|
|
|
|
cacheLock <- newMVar ()
|
|
|
|
cacheCell <- newIORef (schemaCache, initSchemaCacheVer)
|
|
|
|
let metadataVersionGauge = smSchemaCacheMetadataResourceVersion serverMetrics
|
|
|
|
updateMetadataVersionGauge metadataVersionGauge schemaCache
|
|
|
|
pure $ SchemaCacheRef cacheLock cacheCell metadataVersionGauge
|
|
|
|
|
|
|
|
-- | Set the 'SchemaCacheRef' to the 'RebuildableSchemaCache' produced by the
|
|
|
|
-- given action.
|
|
|
|
--
|
|
|
|
-- An internal lock ensures that at most one update to the 'SchemaCacheRef' may
|
|
|
|
-- proceed at a time.
|
|
|
|
withSchemaCacheUpdate ::
|
|
|
|
(MonadIO m, MonadBaseControl IO m) =>
|
|
|
|
SchemaCacheRef ->
|
|
|
|
L.Logger L.Hasura ->
|
|
|
|
Maybe (STM.TVar Bool) ->
|
|
|
|
m (a, RebuildableSchemaCache) ->
|
|
|
|
m a
|
|
|
|
withSchemaCacheUpdate (SchemaCacheRef lock cacheRef metadataVersionGauge) logger mLogCheckerTVar action =
|
|
|
|
withMVarMasked lock $ \() -> do
|
|
|
|
(!res, !newSC) <- action
|
|
|
|
liftIO $ do
|
|
|
|
-- update schemacache in IO reference
|
|
|
|
modifyIORef' cacheRef $ \(_, prevVer) ->
|
|
|
|
let !newVer = incSchemaCacheVer prevVer
|
|
|
|
in (newSC, newVer)
|
|
|
|
|
|
|
|
-- update metric with new metadata version
|
|
|
|
updateMetadataVersionGauge metadataVersionGauge newSC
|
|
|
|
|
|
|
|
let inconsistentObjectsList = scInconsistentObjs $ lastBuiltSchemaCache newSC
|
|
|
|
logInconsistentMetadata' = logInconsistentMetadata logger inconsistentObjectsList
|
|
|
|
-- log any inconsistent objects only once and not everytime this method is called
|
|
|
|
case mLogCheckerTVar of
|
|
|
|
Nothing -> do logInconsistentMetadata'
|
|
|
|
Just logCheckerTVar -> do
|
|
|
|
logCheck <- liftIO $ STM.readTVarIO logCheckerTVar
|
|
|
|
if null inconsistentObjectsList && logCheck
|
|
|
|
then do
|
|
|
|
STM.atomically $ STM.writeTVar logCheckerTVar False
|
|
|
|
else do
|
2022-05-27 16:33:38 +03:00
|
|
|
unless (logCheck || null inconsistentObjectsList) $ do
|
2022-03-09 01:59:28 +03:00
|
|
|
STM.atomically $ STM.writeTVar logCheckerTVar True
|
|
|
|
logInconsistentMetadata'
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
-- | Read the contents of the 'SchemaCacheRef'
|
|
|
|
readSchemaCacheRef :: SchemaCacheRef -> IO (RebuildableSchemaCache, SchemaCacheVer)
|
|
|
|
readSchemaCacheRef scRef = readIORef $ _scrCache scRef
|
|
|
|
|
|
|
|
-- | Utility function. Read the latest 'SchemaCache' from the 'SchemaCacheRef'.
|
|
|
|
--
|
|
|
|
-- > getSchemaCache == fmap (lastBuiltSchemaCache . fst) . readSchemaCacheRef
|
|
|
|
getSchemaCache :: SchemaCacheRef -> IO SchemaCache
|
|
|
|
getSchemaCache scRef = lastBuiltSchemaCache . fst <$> readSchemaCacheRef scRef
|
|
|
|
|
|
|
|
-- | Utility function
|
|
|
|
logInconsistentMetadata :: L.Logger L.Hasura -> [InconsistentMetadata] -> IO ()
|
|
|
|
logInconsistentMetadata logger objs =
|
|
|
|
unless (null objs) $
|
|
|
|
L.unLogger logger $
|
|
|
|
mkInconsMetadataLog objs
|
|
|
|
|
2022-09-15 22:10:53 +03:00
|
|
|
-- Internal helper. Set the gauge metric to the metadata version of the schema
|
2022-03-09 01:59:28 +03:00
|
|
|
-- cache, if it exists.
|
|
|
|
updateMetadataVersionGauge :: MonadIO m => Gauge -> RebuildableSchemaCache -> m ()
|
|
|
|
updateMetadataVersionGauge metadataVersionGauge schemaCache = do
|
|
|
|
let metadataVersion = scMetadataResourceVersion . lastBuiltSchemaCache $ schemaCache
|
|
|
|
liftIO $ traverse_ (Gauge.set metadataVersionGauge . getMetadataResourceVersion) metadataVersion
|