diff --git a/server/src-lib/Hasura/Server/App.hs b/server/src-lib/Hasura/Server/App.hs index af41f467fe7..bcbb9c703dd 100644 --- a/server/src-lib/Hasura/Server/App.hs +++ b/server/src-lib/Hasura/Server/App.hs @@ -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 diff --git a/server/src-lib/Hasura/Server/AppStateRef.hs b/server/src-lib/Hasura/Server/AppStateRef.hs index da994ee4c39..8cb6cfc7bd6 100644 --- a/server/src-lib/Hasura/Server/AppStateRef.hs +++ b/server/src-lib/Hasura/Server/AppStateRef.hs @@ -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)