{- Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. -} module Glean.Server (main) where import Control.Concurrent.Async (race) import Control.Monad import Data.IORef import Data.Maybe import Data.Time import qualified Options.Applicative as O import System.Time.Extra (Seconds) import Facebook.Fb303 import Facebook.Service import Fb303Core.Types #ifdef FBTHRIFT import qualified Thrift.Server.CppServer as ThriftServer #else import qualified Thrift.Server.HTTP as ThriftServer #endif import Util.EventBase import Util.Log import Util.STM #if GLEAN_FACEBOOK import Data.Typeable (cast) import Network.HTTP.Client import JustKnobs (evalKnob) import Logger.IO import Glean.Facebook.Logger.Server import Glean.Facebook.Logger.Database import Glean.BatchLocation.FacebookBatchLocation import qualified Glean.Database.Backup.Manifold as Manifold import qualified Glean.Database.Backup.XDBCatalog as XDB import Glean.Server.Available ( withAvailableDBFilterViaSR ) import Glean.Server.Tracing import Manifold.Client import Glean.Util.Some #endif import Glean.Database.Config (Config(..)) import Glean.Database.Env import Glean.Database.Types import qualified Glean.Handler as GleanHandler import Glean.Impl.ConfigProvider (ConfigAPI) import qualified Glean.Index as Index import Glean.Index.GleanIndexingService.Service import Glean.Server.Config as Config import Glean.Server.Sharding ( shardManagerConfig, withShardsUpdater, waitForTerminateSignalsAndGracefulShutdown) import Glean.Util.ConfigProvider main :: IO () main = withConfigOptions (O.info options O.fullDesc) $ \(cfg0, cfgOpts) -> withEventBaseDataplane $ \evb -> withConfigProvider cfgOpts $ \(configAPI :: ConfigAPI) -> #if GLEAN_FACEBOOK withLogger configAPI $ \logger -> withTracing $ \tracer -> withAvailableDBFilterViaSR evb $ \filterAvailableDBs -> #endif let dbCfg = (cfgDBConfig cfg0){ cfgShardManager = shardManagerConfig (cfgPort cfg) #if GLEAN_FACEBOOK , cfgServerLogger = Some (GleanServerFacebookLogger logger) , cfgDatabaseLogger = Some (GleanDatabaseFacebookLogger logger) , cfgBatchLocationParser = Some (FacebookBatchLocationParser) , cfgFilterAvailableDBs = filterAvailableDBs , cfgTracer = tracer #endif } #if GLEAN_FACEBOOK cfg = cfg0{cfgDBConfig = XDB.withXdbCatalog "manifold" $ Manifold.withManifoldBackups evb dbCfg} #else cfg = cfg0{cfgDBConfig = dbCfg} #endif in withDatabases evb (cfgDBConfig cfg) configAPI $ \databases -> do terminating <- newTVarIO False withShardsUpdater evb cfg databases (1 :: Seconds) (readTVar terminating) $ do fb303 <- newFb303 "gleandriver" logInfo "Starting server" portVar <- newTVarIO Nothing let setAlive server = do let port = ThriftServer.serverPort server atomically $ writeTVar portVar (Just port) forM_ (cfgWritePort cfg) $ \path -> writeFile path (show port) logInfo $ "server alive on port " ++ show port writeIORef (fb303_status fb303) Fb303_status_ALIVE -- If the janitor is enabled, wait until it has run once to -- completion before we advertise the server as alive. Otherwise -- clients may contact this server and see no available DBs. waitForAlive server = do (_, l) <- atomically $ do l <- readTVar $ envDatabaseJanitor databases maybe retry return l case l of JanitorRunFailure JanitorFetchBackupsFailure{} -> do logError "Aborting: failed to list remote DBs at startup" return False JanitorTimeout -> do logError "Aborting: Janitor timeout at startup" return False JanitorStuck -> do logError "Aborting: Janitor stuck at startup" return False JanitorRunSuccess -> setAlive server >> return True JanitorRunFailure OtherJanitorException{} -> setAlive server >> return True JanitorDisabled -> setAlive server >> return True monitorJanitor t0 = do (t1, result) <- atomically $ do l <- readTVar (envDatabaseJanitor databases) case l of Nothing -> retry Just (t1, r) | t1 == t0 -> retry -- block until the next janitor run | otherwise -> return (t1, r) case result of JanitorStuck -> do logError "Aborting: Janitor stuck" #ifdef GLEAN_FACEBOOK JanitorRunFailure (JanitorFetchBackupsFailure fetchError) -> do let dbServerBelievedDead = if | Just NoHostsError <- cast fetchError -> True | Just (HttpExceptionRequest _req ConnectionFailure{}) <- cast fetchError -> True | Just (HttpExceptionRequest _req ConnectionTimeout{}) <- cast fetchError -> True | otherwise -> False knob <- evalKnob "code_indexing/glean:server_automated_restarts" if knob == Right True && not dbServerBelievedDead then logError "Aborting: failed to list remote DBs too many times" else monitorJanitor t1 #endif _ -> monitorJanitor t1 waitForTerminate = void $ (getCurrentTime >>= monitorJanitor) `race` waitForTerminateSignalsAndGracefulShutdown databases terminating (cfgGracefulShutdownTimeout cfg) waitToStart server = do success <- waitForAlive server when success waitForTerminate opts = ThriftServer.defaultOptions { ThriftServer.desiredPort = cfgPort cfg } getPort = fromMaybe (error "server hasn't started yet") <$> readTVarIO portVar let state = GleanHandler.State fb303 databases if cfgEnableIndexing cfg then withBackgroundFacebookServiceDeferredAlive (GleanHandler.fb303State state) (handlerIndexing state getPort) opts waitToStart else withBackgroundFacebookServiceDeferredAlive (GleanHandler.fb303State state) (GleanHandler.handler state) opts waitToStart handlerIndexing :: GleanHandler.State -> IO Int -- ^ get the port the server is running on -> GleanIndexingServiceCommand a -> IO a handlerIndexing state getPort req = case req of Index r -> Index.index getPort (GleanHandler.stEnv state) r SuperGleanService r -> GleanHandler.handler state r