server: fix dirty read of schema cache

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8677
GitOrigin-RevId: 615bfb4dc8d22a46a87ceb76c5f89d608e88f97d
This commit is contained in:
Naveen Naidu 2023-04-07 14:51:26 +05:30 committed by hasura-bot
parent 2e443401f3
commit 2814735a20
2 changed files with 49 additions and 13 deletions

View File

@ -88,7 +88,7 @@ import Hasura.Server.AppStateRef
getAppContext, getAppContext,
getRebuildableSchemaCacheWithVersion, getRebuildableSchemaCacheWithVersion,
getSchemaCache, getSchemaCache,
withSchemaCacheUpdate, withSchemaCacheReadUpdate,
) )
import Hasura.Server.Auth (AuthMode (..), UserAuthentication (..)) import Hasura.Server.Auth (AuthMode (..), UserAuthentication (..))
import Hasura.Server.Compression import Hasura.Server.Compression
@ -439,17 +439,17 @@ v1QueryHandler ::
MonadGetApiTimeLimit m, MonadGetApiTimeLimit m,
UserInfoM m UserInfoM m
) => ) =>
(m (EncJSON, RebuildableSchemaCache) -> m EncJSON) -> ((RebuildableSchemaCache -> m (EncJSON, RebuildableSchemaCache)) -> m EncJSON) ->
RQLQuery -> RQLQuery ->
m (HttpResponse EncJSON) m (HttpResponse EncJSON)
v1QueryHandler schemaCacheRefUpdater query = do v1QueryHandler schemaCacheRefUpdater query = do
(liftEitherM . authorizeV1QueryApi query) =<< ask (liftEitherM . authorizeV1QueryApi query) =<< ask
res <- bool (fst <$> action) (schemaCacheRefUpdater action) $ queryModifiesSchemaCache query schemaCache <- asks hcSchemaCache
res <- bool (fst <$> action schemaCache) (schemaCacheRefUpdater action) $ queryModifiesSchemaCache query
return $ HttpResponse res [] return $ HttpResponse res []
where where
action = do action schemaCache = do
appContext <- asks hcAppContext appContext <- asks hcAppContext
schemaCache <- asks hcSchemaCache
runQuery runQuery
appContext appContext
schemaCache schemaCache
@ -473,15 +473,14 @@ v1MetadataHandler ::
MonadGetApiTimeLimit m, MonadGetApiTimeLimit m,
UserInfoM m UserInfoM m
) => ) =>
(m (EncJSON, RebuildableSchemaCache) -> m EncJSON) -> ((RebuildableSchemaCache -> m (EncJSON, RebuildableSchemaCache)) -> m EncJSON) ->
RQLMetadata -> RQLMetadata ->
m (HttpResponse EncJSON) m (HttpResponse EncJSON)
v1MetadataHandler schemaCacheRefUpdater query = Tracing.newSpan "Metadata" $ do v1MetadataHandler schemaCacheRefUpdater query = Tracing.newSpan "Metadata" $ do
(liftEitherM . authorizeV1MetadataApi query) =<< ask (liftEitherM . authorizeV1MetadataApi query) =<< ask
appContext <- asks hcAppContext appContext <- asks hcAppContext
schemaCache <- asks hcSchemaCache
r <- r <-
schemaCacheRefUpdater $ schemaCacheRefUpdater $ \schemaCache ->
runMetadataQuery runMetadataQuery
appContext appContext
schemaCache schemaCache
@ -503,19 +502,19 @@ v2QueryHandler ::
ProvidesNetwork m, ProvidesNetwork m,
UserInfoM m UserInfoM m
) => ) =>
(m (EncJSON, RebuildableSchemaCache) -> m EncJSON) -> ((RebuildableSchemaCache -> m (EncJSON, RebuildableSchemaCache)) -> m EncJSON) ->
V2Q.RQLQuery -> V2Q.RQLQuery ->
m (HttpResponse EncJSON) m (HttpResponse EncJSON)
v2QueryHandler schemaCacheRefUpdater query = Tracing.newSpan "v2 Query" $ do v2QueryHandler schemaCacheRefUpdater query = Tracing.newSpan "v2 Query" $ do
schemaCache <- asks hcSchemaCache
(liftEitherM . authorizeV2QueryApi query) =<< ask (liftEitherM . authorizeV2QueryApi query) =<< ask
res <- res <-
bool (fst <$> dbAction) (schemaCacheRefUpdater dbAction) $ bool (fst <$> dbAction schemaCache) (schemaCacheRefUpdater dbAction) $
V2Q.queryModifiesSchema query V2Q.queryModifiesSchema query
return $ HttpResponse res [] return $ HttpResponse res []
where where
-- Hit postgres -- Hit postgres
dbAction = do dbAction schemaCache = do
schemaCache <- asks hcSchemaCache
appContext <- asks hcAppContext appContext <- asks hcAppContext
V2Q.runQuery V2Q.runQuery
appContext appContext
@ -923,7 +922,7 @@ httpApp setupHook appStateRef AppEnv {..} consoleType ekgStore = do
-- Note: we create a schema cache updater function, to restrict the access -- Note: we create a schema cache updater function, to restrict the access
-- to 'AppStateRef' inside the request handlers -- to 'AppStateRef' inside the request handlers
let schemaCacheUpdater = withSchemaCacheUpdate appStateRef logger Nothing let schemaCacheUpdater = withSchemaCacheReadUpdate appStateRef logger Nothing
Spock.post "v1/graphql/explain" $ do Spock.post "v1/graphql/explain" $ do
onlyWhenApiEnabled isMetadataEnabled appStateRef gqlExplainAction onlyWhenApiEnabled isMetadataEnabled appStateRef gqlExplainAction

View File

@ -18,6 +18,7 @@ module Hasura.Server.AppStateRef
readAppContextRef, readAppContextRef,
getAppContext, getAppContext,
logInconsistentMetadata, logInconsistentMetadata,
withSchemaCacheReadUpdate,
) )
where where
@ -136,6 +137,42 @@ withSchemaCacheUpdate (AppStateRef lock cacheRef metadataVersionGauge) logger mL
pure res pure res
withSchemaCacheReadUpdate ::
(MonadIO m, MonadBaseControl IO m) =>
(AppStateRef impl) ->
L.Logger L.Hasura ->
Maybe (STM.TVar Bool) ->
(RebuildableSchemaCache -> m (a, RebuildableSchemaCache)) ->
m a
withSchemaCacheReadUpdate (AppStateRef lock cacheRef metadataVersionGauge) logger mLogCheckerTVar action =
withMVarMasked lock $ const do
(rebuildableSchemaCache, _) <- asSchemaCache <$> liftIO (readIORef cacheRef)
(!res, !newSC) <- action rebuildableSchemaCache
liftIO do
-- update schemacache in IO reference
modifyIORef' cacheRef $ \appState ->
let !newVer = incSchemaCacheVer (snd $ asSchemaCache appState)
in appState {asSchemaCache = (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 -> logInconsistentMetadata'
Just logCheckerTVar -> do
logCheck <- STM.readTVarIO logCheckerTVar
if null inconsistentObjectsList && logCheck
then do
STM.atomically $ STM.writeTVar logCheckerTVar False
else do
unless (logCheck || null inconsistentObjectsList) $ do
STM.atomically $ STM.writeTVar logCheckerTVar True
logInconsistentMetadata'
pure res
-- | Read the contents of the 'AppStateRef' to get the latest 'RebuildableAppContext' -- | Read the contents of the 'AppStateRef' to get the latest 'RebuildableAppContext'
readAppContextRef :: AppStateRef impl -> IO (RebuildableAppContext impl) readAppContextRef :: AppStateRef impl -> IO (RebuildableAppContext impl)
readAppContextRef scRef = asAppCtx <$> readIORef (_scrCache scRef) readAppContextRef scRef = asAppCtx <$> readIORef (_scrCache scRef)