packages feed

glean-0.1.0.0: glean/glass/Glean/Glass/Main.hs

{-
  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.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 (..))
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
      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 :: Glass.Env -> (Glass.Env -> IO a) -> IO a
withCurrentRepoMapping env0 fn = do
  current <- getRepoMapping
  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

-- 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 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