* refactor: move ApiRequest a top-level module * refactor: rename DbStructure to SchemaCache * refactor: GucHeader inside Response * refactor: admin app to Workers
179 lines
5.6 KiB
Haskell
179 lines
5.6 KiB
Haskell
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
module PostgREST.AppState
|
|
( AppState
|
|
, destroy
|
|
, flushPool
|
|
, getConfig
|
|
, getSchemaCache
|
|
, getIsListenerOn
|
|
, getJsonDbS
|
|
, getMainThreadId
|
|
, getPgVersion
|
|
, getRetryNextIn
|
|
, getTime
|
|
, getWorkerSem
|
|
, init
|
|
, initWithPool
|
|
, logWithZTime
|
|
, putConfig
|
|
, putSchemaCache
|
|
, putIsListenerOn
|
|
, putJsonDbS
|
|
, putPgVersion
|
|
, putRetryNextIn
|
|
, signalListener
|
|
, usePool
|
|
, waitListener
|
|
) where
|
|
|
|
import qualified Hasql.Pool as SQL
|
|
import qualified Hasql.Session as SQL
|
|
|
|
import Control.AutoUpdate (defaultUpdateSettings, mkAutoUpdate,
|
|
updateAction)
|
|
import Data.IORef (IORef, atomicWriteIORef, newIORef,
|
|
readIORef)
|
|
import Data.Time (ZonedTime, defaultTimeLocale, formatTime,
|
|
getZonedTime)
|
|
import Data.Time.Clock (UTCTime, getCurrentTime)
|
|
|
|
import PostgREST.Config (AppConfig (..))
|
|
import PostgREST.Config.PgVersion (PgVersion (..), minimumPgVersion)
|
|
import PostgREST.SchemaCache (SchemaCache)
|
|
|
|
import Protolude
|
|
|
|
|
|
data AppState = AppState
|
|
-- | Database connection pool
|
|
{ statePool :: SQL.Pool
|
|
-- | Database server version, will be updated by the connectionWorker
|
|
, statePgVersion :: IORef PgVersion
|
|
-- | No schema cache at the start. Will be filled in by the connectionWorker
|
|
, stateSchemaCache :: IORef (Maybe SchemaCache)
|
|
-- | Cached SchemaCache in json
|
|
, stateJsonDbS :: IORef ByteString
|
|
-- | Binary semaphore to make sure just one connectionWorker can run at a time
|
|
, stateWorkerSem :: MVar ()
|
|
-- | Binary semaphore used to sync the listener(NOTIFY reload) with the connectionWorker.
|
|
, stateListener :: MVar ()
|
|
-- | State of the LISTEN channel, used for the admin server checks
|
|
, stateIsListenerOn :: IORef Bool
|
|
-- | Config that can change at runtime
|
|
, stateConf :: IORef AppConfig
|
|
-- | Time used for verifying JWT expiration
|
|
, stateGetTime :: IO UTCTime
|
|
-- | Time with time zone used for worker logs
|
|
, stateGetZTime :: IO ZonedTime
|
|
-- | Used for killing the main thread in case a subthread fails
|
|
, stateMainThreadId :: ThreadId
|
|
-- | Keeps track of when the next retry for connecting to database is scheduled
|
|
, stateRetryNextIn :: IORef Int
|
|
}
|
|
|
|
init :: AppConfig -> IO AppState
|
|
init conf = do
|
|
pool <- initPool conf
|
|
initWithPool pool conf
|
|
|
|
initWithPool :: SQL.Pool -> AppConfig -> IO AppState
|
|
initWithPool pool conf =
|
|
AppState pool
|
|
<$> newIORef minimumPgVersion -- assume we're in a supported version when starting, this will be corrected on a later step
|
|
<*> newIORef Nothing
|
|
<*> newIORef mempty
|
|
<*> newEmptyMVar
|
|
<*> newEmptyMVar
|
|
<*> newIORef False
|
|
<*> newIORef conf
|
|
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
|
<*> mkAutoUpdate defaultUpdateSettings { updateAction = getZonedTime }
|
|
<*> myThreadId
|
|
<*> newIORef 0
|
|
|
|
destroy :: AppState -> IO ()
|
|
destroy = destroyPool
|
|
|
|
initPool :: AppConfig -> IO SQL.Pool
|
|
initPool AppConfig{..} =
|
|
SQL.acquire configDbPoolSize timeoutMilliseconds $ toUtf8 configDbUri
|
|
where
|
|
timeoutMilliseconds = (* oneSecond) <$> configDbPoolAcquisitionTimeout
|
|
oneSecond = 1000000
|
|
|
|
-- | Run an action with a database connection.
|
|
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
|
|
usePool AppState{..} = SQL.use statePool
|
|
|
|
-- | Flush the connection pool so that any future use of the pool will
|
|
-- use connections freshly established after this call.
|
|
flushPool :: AppState -> IO ()
|
|
flushPool AppState{..} = SQL.release statePool
|
|
|
|
-- | Destroy the pool on shutdown.
|
|
destroyPool :: AppState -> IO ()
|
|
destroyPool AppState{..} = SQL.release statePool
|
|
|
|
getPgVersion :: AppState -> IO PgVersion
|
|
getPgVersion = readIORef . statePgVersion
|
|
|
|
putPgVersion :: AppState -> PgVersion -> IO ()
|
|
putPgVersion = atomicWriteIORef . statePgVersion
|
|
|
|
getSchemaCache :: AppState -> IO (Maybe SchemaCache)
|
|
getSchemaCache = readIORef . stateSchemaCache
|
|
|
|
putSchemaCache :: AppState -> Maybe SchemaCache -> IO ()
|
|
putSchemaCache appState = atomicWriteIORef (stateSchemaCache appState)
|
|
|
|
getJsonDbS :: AppState -> IO ByteString
|
|
getJsonDbS = readIORef . stateJsonDbS
|
|
|
|
putJsonDbS :: AppState -> ByteString -> IO ()
|
|
putJsonDbS appState = atomicWriteIORef (stateJsonDbS appState)
|
|
|
|
getWorkerSem :: AppState -> MVar ()
|
|
getWorkerSem = stateWorkerSem
|
|
|
|
getRetryNextIn :: AppState -> IO Int
|
|
getRetryNextIn = readIORef . stateRetryNextIn
|
|
|
|
putRetryNextIn :: AppState -> Int -> IO ()
|
|
putRetryNextIn = atomicWriteIORef . stateRetryNextIn
|
|
|
|
getConfig :: AppState -> IO AppConfig
|
|
getConfig = readIORef . stateConf
|
|
|
|
putConfig :: AppState -> AppConfig -> IO ()
|
|
putConfig = atomicWriteIORef . stateConf
|
|
|
|
getTime :: AppState -> IO UTCTime
|
|
getTime = stateGetTime
|
|
|
|
-- | Log to stderr with local time
|
|
logWithZTime :: AppState -> Text -> IO ()
|
|
logWithZTime appState txt = do
|
|
zTime <- stateGetZTime appState
|
|
hPutStrLn stderr $ toS (formatTime defaultTimeLocale "%d/%b/%Y:%T %z: " zTime) <> txt
|
|
|
|
getMainThreadId :: AppState -> ThreadId
|
|
getMainThreadId = stateMainThreadId
|
|
|
|
-- | As this IO action uses `takeMVar` internally, it will only return once
|
|
-- `stateListener` has been set using `signalListener`. This is currently used
|
|
-- to syncronize workers.
|
|
waitListener :: AppState -> IO ()
|
|
waitListener = takeMVar . stateListener
|
|
|
|
-- tryPutMVar doesn't lock the thread. It should always succeed since
|
|
-- the connectionWorker is the only mvar producer.
|
|
signalListener :: AppState -> IO ()
|
|
signalListener appState = void $ tryPutMVar (stateListener appState) ()
|
|
|
|
getIsListenerOn :: AppState -> IO Bool
|
|
getIsListenerOn = readIORef . stateIsListenerOn
|
|
|
|
putIsListenerOn :: AppState -> Bool -> IO ()
|
|
putIsListenerOn = atomicWriteIORef . stateIsListenerOn
|