content: {-
    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.
  -}
  
  {-# LANGUAGE CPP #-}
  module Glean.Glass.Main
    ( mainWith
  
    -- * Useful helpers
    , withEnv
    ) where
  
  import Control.Concurrent (myThreadId)
  import Control.Trace (traceMsg, (>$<))
  import Data.Default (def)
  import Data.Hashable (hash)
  import Data.List (stripPrefix)
  import Facebook.Service ( runFacebookService' )
  import Facebook.Fb303 ( fb303Handler, withFb303 )
  import System.Timeout
  import TextShow
  import Thrift.Channel (Header)
  import Thrift.Protocol.ApplicationException.Types as Thrift
  #ifdef FBTHRIFT
  import qualified Thrift.Server.CppServer as Thrift
  #else
  import qualified Thrift.Server.HTTP as Thrift
  #endif
  import Util.EventBase ( withEventBaseDataplane )
  import Util.Log.Text ( logInfo )
  import Logger.IO (withLogger)
  
  import Control.Exception (SomeException, fromException, throwIO)
  import Data.Text (Text)
  import Data.Text.Encoding (encodeUtf8)
  import Options.Applicative (Parser)
  
  import qualified Glean
  import Glean.Init ( withOptions )
  import Glean.LocalOrRemote as Glean
      ( Service(Remote), withBackendWithDefaultOptions )
  import Glean.Schema.Builtin.Types (schema_id)
  import Glean.Util.ConfigProvider
      ( ConfigProvider(defaultConfigOptions, withConfigProvider) )
  import Glean.Util.Some ( Some(Some) )
  
  import Glean.Glass.RepoMapping -- site-specific
  import qualified Glean.Glass.Env as Glass
  import Glean.Glass.Repos (withLatestRepos)
  import qualified Glean.Glass.Options as Glass
  import qualified Glean.Glass.Handler.Documents as Handler
  import qualified Glean.Glass.Handler.Symbols as Handler
  import Glean.Glass.GlassService.Service ( GlassServiceCommand(..) )
  
  import Glean.Glass.Types
    ( GlassException (GlassException, glassException_reasons),
      GlassExceptionReason (..), RequestOptions)
  import Glean.Glass.Env (Env'(tracer), Env)
  import Glean.Glass.Tracer ( isTracingEnabled )
  import Glean.Glass.Handler.Cxx as Cxx
  import Glean.Glass.Tracing
    (GlassTrace(TraceCommand), GlassTraceWithId (GlassTraceWithId))
  
  kThriftCacheNoCache :: Text
  kThriftCacheNoCache = "nocache"
  
  -- | Ok, go.
  mainWith
    :: Parser (Glass.Config GlassTraceWithId -> Glass.Config GlassTraceWithId)
    -> IO ()
  mainWith = withGlass runGlass
  
  type Server trace = Glass.Env' trace -> Glass.Config trace -> IO ()
  
  -- | Set up the resources we'll need
  withGlass
    :: TextShow trace
    => Server trace -> Parser (Glass.Config trace -> Glass.Config trace) -> IO ()
  withGlass f backendsP =
    withOptions (Glass.options backendsP) $ \config ->
    withEnv config Nothing $ \env -> f env config
  
  -- | Construct a server environment
  withEnv
    :: TextShow trace
    => Glass.Config trace
    -> Maybe Glean.Repo
    -> (Glass.Env' trace -> IO a)
    -> IO a
  withEnv Glass.Config{..} gleanDB f =
    withEventBaseDataplane $ \evp ->
    withConfigProvider defaultConfigOptions $ \cfgapi ->
    withLogger cfgapi $ \logger ->
    withFb303 serviceName $ \fb303 ->
    withBackendWithDefaultOptions evp cfgapi gleanService (Just schema_id)
      $ \backend -> do
    scm <- sourceControl evp
    snapshotBackend <- snapshotBackend evp
    hst <- haxlState evp
    withLatestRepos backend scm (Just logger)
      (if isRemote gleanService then listDatabasesRetry else Nothing) refreshFreq
      $ \latestGleanRepos -> do
        repoMapping <- getRepoMapping def
        f Glass.Env
          { gleanBackend = Some backend
          , gleanDB = gleanDB
          , snapshotBackend = snapshotBackend
          , sourceControl = scm
          , haxlState = hst
          , ..
          }
  
  -- | Some of the retry/logging logic only applies to remote services
  isRemote :: Service -> Bool
  isRemote service = case service of
    Remote{} -> True
    _ -> False
  
  -- | Kick off the server
  runGlass :: Server GlassTraceWithId
  runGlass res@Glass.Env{fb303, evp} conf@Glass.Config{..} = do
  
    logInfo =<< welcomeMessage evp conf
  
    let options = Thrift.defaultOptions
          { Thrift.desiredPort = Just listenPort
          , Thrift.numWorkerThreads = numWorkerThreads
          }
    runFacebookService' fb303 (glassHandler res) assignHeaders options
  
  assignHeaders :: GlassServiceCommand r -> Either SomeException r -> Header
  assignHeaders _ (Left e) | isRevisionNotAvailableException e =
    [ (encodeUtf8 kThriftCacheNoCache, "1")]
    where
      isRevisionNotAvailableException e = case fromException e of
        Just GlassException{glassException_reasons} ->
          all isRevisionNotAvailable glassException_reasons
        _ -> False
      isRevisionNotAvailable e = case e of
        GlassExceptionReason_exactRevisionNotAvailable{} -> True
        GlassExceptionReason_matchingRevisionNotAvailable{} -> True
        _ -> False
  assignHeaders _ _ = []
  
  -- | Perform an operation with the latest RepoMapping
  withCurrentRepoMapping :: RequestOptions -> Glass.Env -> (Glass.Env -> IO a) -> IO a
  withCurrentRepoMapping opts env0 fn = do
    current <- getRepoMapping opts
    fn (env0 { Glass.repoMapping = current })
  
  withRequestTracing :: Env' GlassTraceWithId -> (Env -> IO b) -> IO b
  withRequestTracing env k = do
    enabled <- isTracingEnabled
    if enabled
      then do
        -- Fix the trace id for this request traces.
        -- Since every request is handled by a fresh new thread,
        -- we reuse thread ids as trace ids
        tid <- tidToIndex <$> myThreadId
        k env{tracer = GlassTraceWithId tid >$< tracer env}
      else k env { tracer = mempty }
    where
      tidToIndex tid = case stripPrefix "ThreadId " (show tid) of
        Just n | [(i, "")] <- reads n -> i
        _ -> hash tid -- should be unreachable
  
  getOptsFromCommand :: GlassServiceCommand r -> RequestOptions
  getOptsFromCommand cmd = case cmd of
    SuperFacebookService _ -> def
    DocumentSymbolListX _ opts -> opts
    DocumentSymbolIndex _ opts -> opts
    FindReferenceRanges _ opts -> opts
    DescribeSymbol _ opts -> opts
    SymbolLocation _ opts -> opts
    ResolveSymbols _ opts -> opts
    SearchSymbol _ opts -> opts
    SearchRelated _ opts _ -> opts
    SearchRelatedNeighborhood _ opts _ -> opts
    FileIncludeLocations _ opts -> opts
    ClangUSRToDefinition _ opts -> opts
    UsrToDefinition _ opts -> opts
  
  -- Actual glass service handler, types from glass.thrift
  -- TODO: snapshot the env, rather than passing in the mutable fields
  --
  glassHandler :: Glass.Env' GlassTraceWithId -> GlassServiceCommand r -> IO r
  glassHandler env0 cmd =
    Glass.withAllocationLimit env0 $
    withRequestTracing env0 $ \env1 ->
    withCurrentRepoMapping (getOptsFromCommand cmd) env1 $ \env ->
    tracing env $
    withTimeout $ case cmd of
    SuperFacebookService r -> fb303Handler (Glass.fb303 env) r
  
    -- Listing symbols in files
    DocumentSymbolListX r opts -> Handler.documentSymbolListX env r opts
    DocumentSymbolIndex r opts -> Handler.documentSymbolIndex env r opts
  
    -- Navigating
    FindReferenceRanges r opts -> Handler.findReferenceRanges env r opts
  
    -- Symbol info
    DescribeSymbol r opts -> Handler.describeSymbol env r opts
    SymbolLocation r opts -> Handler.symbolLocation env r opts
    ResolveSymbols r opts -> Handler.resolveSymbols env r opts
  
    -- Search for symbols
    -- by string
    SearchSymbol r opts -> Handler.searchSymbol env r opts
  
    -- by relationship
    SearchRelated r opts req -> Handler.searchRelated env r opts req
    SearchRelatedNeighborhood r opts req ->
      Handler.searchRelatedNeighborhood env r opts req
  
    -- C++/LSP specific
    FileIncludeLocations r opts -> Cxx.fileIncludeLocations env r opts
    ClangUSRToDefinition r opts -> fst <$> Cxx.clangUSRToDefinition env r opts
    UsrToDefinition r opts -> fst <$> Cxx.usrToDefinition env r opts
  
    where
      tracing env
        | SuperFacebookService{} <- cmd = id
        | otherwise = traceMsg (tracer env) (TraceCommand cmd)
  
      withTimeout act = do
        -- workaround for lack of request cancellation on client-side timeout
        result <- timeout clientTimeoutMs act
        case result of
          Nothing ->
            throwIO $
              Thrift.ApplicationException
                "glass server timeout"
                  Thrift.ApplicationExceptionType_Timeout
          Just r -> return r
  
      clientTimeoutMs =
        -- TODO get the client timeout from the Thrift request
        30 * 1000000
  