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

View File

@ -18,6 +18,7 @@ module Hasura.Server.AppStateRef
readAppContextRef,
getAppContext,
logInconsistentMetadata,
withSchemaCacheReadUpdate,
)
where
@ -136,6 +137,42 @@ withSchemaCacheUpdate (AppStateRef lock cacheRef metadataVersionGauge) logger mL
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'
readAppContextRef :: AppStateRef impl -> IO (RebuildableAppContext impl)
readAppContextRef scRef = asAppCtx <$> readIORef (_scrCache scRef)