glean-0.2.0.0: glean/db/Glean/Database/Delete.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.
-}
module Glean.Database.Delete (
deleteDatabase,
asyncDeleteDatabase,
expireDatabase
) where
import Control.Concurrent.Async (Async)
import qualified Control.Concurrent.Async as Async
import Control.Exception hiding(handle)
import Control.Monad.Extra
import qualified Data.HashMap.Strict as HashMap
import Data.Time
import GHC.Stack (HasCallStack)
import ServiceData.GlobalStats as Stats
import ServiceData.Types as Stats
import Util.Control.Exception
import qualified Util.Control.Exception.CallStack as CallStack
import Util.Defer
import Util.Log
import Util.STM
import Util.TimeSec
import qualified Glean.Database.Catalog as Catalog
import Glean.Database.Catalog.Filter
import Glean.Database.Close
import Glean.Database.Meta
import Glean.Database.Repo
import qualified Glean.Database.Storage as Storage
import Glean.Database.Types
import Glean.Types hiding (Database)
import qualified Glean.Util.Warden as Warden
-- | Schedule DBs for deletion or expiration.
-- Throws 'UnknownDatabase' exceptions
expireDatabase :: HasCallStack => Maybe NominalDiffTime -> Env -> Repo -> IO ()
expireDatabase delay env@Env{..} repo = do
now <- getCurrentTime
expired <- immediately $ do
exp <- lift $ Catalog.readExpiring envCatalog repo
case exp of
Just t -> return (now > t)
Nothing -> case delay of
Just delay | delay > 0 -> do
meta <- lift $ Catalog.readMeta envCatalog repo
later $ do
logInfo $ inRepo repo $ "database is doomed " ++
" (" ++ showNominalDiffTime (dbAge now meta) ++ " old)"
logInfo $ inRepo repo $ "expiring in " <> show delay
lift $ Catalog.setExpiring envCatalog repo $ delay `addUTCTime` now
return False
_ -> return True
when expired $ void $ asyncDeleteDatabase env repo
-- | Database deletion thread
removeDatabase
:: Env
-> Repo
-> TMVar (Maybe DB)
-> IO ()
removeDatabase env@Env{..} repo todo = uninterruptibleMask_ $
-- This runs under uninterruptibleMask_ because there is really nothing
-- sensible we can do if we get interrupted.
--
-- * We need to close the DB even if the program is shutting down.
-- * Closing the DB itself should be uninterruptible.
-- * Once we start deleting things we shouldn't stop until we've deleted
-- them all.
--
-- So really, the only sensible interruption points is after we've closed the
-- DB but before we start deleting which just doesn't seem worth it.
do
r <- atomically $ readTMVar todo
let cleanUp = atomically (modifyTVar' envDeleting $ HashMap.delete repo)
forM_ r $ \DB{..} -> flip finally cleanUp $ do
logInfo $ inRepo repo "deleting"
addStatValueType "glean.db.deleted" 1 Stats.Sum
atomically $ do
users <- readTVar dbUsers
when (users /= 0) retry
logExceptions (\s -> inRepo repo $ "while deleting: " ++s) $ do
state <- readTVarIO dbState
case state of
Open odb -> closeOpenDB env odb
`finally` atomically (writeTVar dbState Closed)
_ -> return ()
meta <- atomically $ Catalog.readMeta envCatalog repo
withStorageFor env repo meta $ \storage -> do
Storage.delete storage repo
Catalog.delete envCatalog repo
Storage.safeRemoveForcibly storage repo
atomically $ modifyTVar envDerivations $
HashMap.filterWithKey (\(repo',_) _ -> repo' /= repo)
logInfo $ inRepo repo "deleted"
-- | Schedule a DB for deletion and return the 'Async' which can be used to
-- obtain the result.
asyncDeleteDatabase :: HasCallStack => Env -> Repo -> IO (Async ())
asyncDeleteDatabase env@Env{..} repo = bracket
newEmptyTMVarIO
(\todo -> atomically $ tryPutTMVar todo Nothing) $ \todo -> do
remover <- Warden.spawnMask envWarden $ \_ -> removeDatabase env repo todo
join $ atomically $ do
active <- HashMap.lookup repo <$> readTVar envActive
let deleteDB db = do
modifyTVar' envDeleting $ HashMap.insert repo remover
putTMVar todo $ Just db
return $ return remover
case active of
Just db -> do
modifyTVar' envActive $ HashMap.delete repo
deleteDB db
Nothing -> do
deleting <- HashMap.lookup repo <$> readTVar envDeleting
exists <- Catalog.exists envCatalog [Local] repo
case deleting of
Nothing
| exists -> do
db <- DB repo
<$> newTVar Closed
<*> newTVar 0
deleteDB db
other -> do
putTMVar todo Nothing
case other of
Nothing -> return $ CallStack.throwIO $ UnknownDatabase repo
Just remover -> return $ return remover
deleteDatabase :: HasCallStack => Env -> Repo -> IO ()
deleteDatabase env repo = asyncDeleteDatabase env repo >>= Async.wait