{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module PostgREST.Workers
( connectionWorker
, reReadConfig
, listener
) where
import qualified Data.Aeson as JSON
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Text.Encoding as T
import qualified Hasql.Notifications as SQL
import qualified Hasql.Pool as SQL
import qualified Hasql.Transaction.Sessions as SQL
import Control.Retry (RetryStatus, capDelay, exponentialBackoff,
retrying, rsPreviousDelay)
import Hasql.Connection (acquire)
import PostgREST.AppState (AppState)
import PostgREST.Config (AppConfig (..), readAppConfig)
import PostgREST.Config.Database (queryDbSettings, queryPgVersion)
import PostgREST.Config.PgVersion (PgVersion (..), minimumPgVersion)
import PostgREST.DbStructure (queryDbStructure)
import PostgREST.Error (PgError (PgError), checkIsFatal,
errorPayload)
import qualified PostgREST.AppState as AppState
import Protolude
data ConnectionStatus
= NotConnected
| Connected PgVersion
| FatalConnectionError Text
deriving (ConnectionStatus -> ConnectionStatus -> Bool
(ConnectionStatus -> ConnectionStatus -> Bool)
-> (ConnectionStatus -> ConnectionStatus -> Bool)
-> Eq ConnectionStatus
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ConnectionStatus -> ConnectionStatus -> Bool
$c/= :: ConnectionStatus -> ConnectionStatus -> Bool
== :: ConnectionStatus -> ConnectionStatus -> Bool
$c== :: ConnectionStatus -> ConnectionStatus -> Bool
Eq)
data SCacheStatus
= SCLoaded
| SCOnRetry
| SCFatalFail
connectionWorker :: AppState -> IO ()
connectionWorker :: AppState -> IO ()
connectionWorker AppState
appState = do
Bool
isWorkerOn <- AppState -> IO Bool
AppState.getIsWorkerOn AppState
appState
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isWorkerOn (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
AppState -> Bool -> IO ()
AppState.putIsWorkerOn AppState
appState Bool
True
IO ThreadId -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO ThreadId -> IO ()) -> IO ThreadId -> IO ()
forall a b. (a -> b) -> a -> b
$ IO () -> IO ThreadId
forkIO IO ()
work
where
work :: IO ()
work = do
AppConfig{Bool
Int
[(Text, Text)]
[ByteString]
[Text]
JSPath
Maybe Integer
Maybe FilePath
Maybe ByteString
Maybe Text
Maybe StringOrURI
Maybe JWKSet
Maybe QualifiedIdentifier
Text
FileMode
NonEmpty Text
NominalDiffTime
OpenAPIMode
LogLevel
configServerUnixSocketMode :: AppConfig -> FileMode
configServerUnixSocket :: AppConfig -> Maybe FilePath
configServerPort :: AppConfig -> Int
configServerHost :: AppConfig -> Text
configRawMediaTypes :: AppConfig -> [ByteString]
configOpenApiServerProxyUri :: AppConfig -> Maybe Text
configOpenApiMode :: AppConfig -> OpenAPIMode
configLogLevel :: AppConfig -> LogLevel
configJwtSecretIsBase64 :: AppConfig -> Bool
configJwtSecret :: AppConfig -> Maybe ByteString
configJwtRoleClaimKey :: AppConfig -> JSPath
configJwtAudience :: AppConfig -> Maybe StringOrURI
configJWKS :: AppConfig -> Maybe JWKSet
configFilePath :: AppConfig -> Maybe FilePath
configDbUseLegacyGucs :: AppConfig -> Bool
configDbUri :: AppConfig -> Text
configDbTxRollbackAll :: AppConfig -> Bool
configDbTxAllowOverride :: AppConfig -> Bool
configDbConfig :: AppConfig -> Bool
configDbSchemas :: AppConfig -> NonEmpty Text
configDbRootSpec :: AppConfig -> Maybe QualifiedIdentifier
configDbPreparedStatements :: AppConfig -> Bool
configDbPreRequest :: AppConfig -> Maybe QualifiedIdentifier
configDbPoolTimeout :: AppConfig -> NominalDiffTime
configDbPoolSize :: AppConfig -> Int
configDbMaxRows :: AppConfig -> Maybe Integer
configDbExtraSearchPath :: AppConfig -> [Text]
configDbChannelEnabled :: AppConfig -> Bool
configDbChannel :: AppConfig -> Text
configDbAnonRole :: AppConfig -> Text
configAppSettings :: AppConfig -> [(Text, Text)]
configServerUnixSocketMode :: FileMode
configServerUnixSocket :: Maybe FilePath
configServerPort :: Int
configServerHost :: Text
configRawMediaTypes :: [ByteString]
configOpenApiServerProxyUri :: Maybe Text
configOpenApiMode :: OpenAPIMode
configLogLevel :: LogLevel
configJwtSecretIsBase64 :: Bool
configJwtSecret :: Maybe ByteString
configJwtRoleClaimKey :: JSPath
configJwtAudience :: Maybe StringOrURI
configJWKS :: Maybe JWKSet
configFilePath :: Maybe FilePath
configDbUseLegacyGucs :: Bool
configDbUri :: Text
configDbTxRollbackAll :: Bool
configDbTxAllowOverride :: Bool
configDbConfig :: Bool
configDbSchemas :: NonEmpty Text
configDbRootSpec :: Maybe QualifiedIdentifier
configDbPreparedStatements :: Bool
configDbPreRequest :: Maybe QualifiedIdentifier
configDbPoolTimeout :: NominalDiffTime
configDbPoolSize :: Int
configDbMaxRows :: Maybe Integer
configDbExtraSearchPath :: [Text]
configDbChannelEnabled :: Bool
configDbChannel :: Text
configDbAnonRole :: Text
configAppSettings :: [(Text, Text)]
..} <- AppState -> IO AppConfig
AppState.getConfig AppState
appState
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
"Attempting to connect to the database..."
ConnectionStatus
connected <- AppState -> IO ConnectionStatus
connectionStatus AppState
appState
case ConnectionStatus
connected of
FatalConnectionError Text
reason ->
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
reason IO () -> IO () -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ThreadId -> IO ()
killThread (AppState -> ThreadId
AppState.getMainThreadId AppState
appState)
ConnectionStatus
NotConnected ->
() -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
Connected PgVersion
actualPgVersion -> do
AppState -> PgVersion -> IO ()
AppState.putPgVersion AppState
appState PgVersion
actualPgVersion
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
configDbChannelEnabled (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
AppState -> IO ()
AppState.signalListener AppState
appState
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
"Connection successful"
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
configDbConfig (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ Bool -> AppState -> IO ()
reReadConfig Bool
False AppState
appState
SCacheStatus
scStatus <- AppState -> IO SCacheStatus
loadSchemaCache AppState
appState
case SCacheStatus
scStatus of
SCacheStatus
SCLoaded ->
() -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
SCacheStatus
SCOnRetry ->
IO ()
work
SCacheStatus
SCFatalFail ->
ThreadId -> IO ()
killThread (ThreadId -> IO ()) -> ThreadId -> IO ()
forall a b. (a -> b) -> a -> b
$ AppState -> ThreadId
AppState.getMainThreadId AppState
appState
AppState -> Bool -> IO ()
AppState.putIsWorkerOn AppState
appState Bool
False
connectionStatus :: AppState -> IO ConnectionStatus
connectionStatus :: AppState -> IO ConnectionStatus
connectionStatus AppState
appState =
RetryPolicyM IO
-> (RetryStatus -> ConnectionStatus -> IO Bool)
-> (RetryStatus -> IO ConnectionStatus)
-> IO ConnectionStatus
forall (m :: * -> *) b.
MonadIO m =>
RetryPolicyM m
-> (RetryStatus -> b -> m Bool) -> (RetryStatus -> m b) -> m b
retrying RetryPolicyM IO
retrySettings RetryStatus -> ConnectionStatus -> IO Bool
shouldRetry ((RetryStatus -> IO ConnectionStatus) -> IO ConnectionStatus)
-> (RetryStatus -> IO ConnectionStatus) -> IO ConnectionStatus
forall a b. (a -> b) -> a -> b
$
IO ConnectionStatus -> RetryStatus -> IO ConnectionStatus
forall a b. a -> b -> a
const (IO ConnectionStatus -> RetryStatus -> IO ConnectionStatus)
-> IO ConnectionStatus -> RetryStatus -> IO ConnectionStatus
forall a b. (a -> b) -> a -> b
$ Pool -> IO ()
SQL.release Pool
pool IO () -> IO ConnectionStatus -> IO ConnectionStatus
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ConnectionStatus
getConnectionStatus
where
pool :: Pool
pool = AppState -> Pool
AppState.getPool AppState
appState
retrySettings :: RetryPolicyM IO
retrySettings = Int -> RetryPolicyM IO -> RetryPolicyM IO
forall (m :: * -> *).
Monad m =>
Int -> RetryPolicyM m -> RetryPolicyM m
capDelay Int
delayMicroseconds (RetryPolicyM IO -> RetryPolicyM IO)
-> RetryPolicyM IO -> RetryPolicyM IO
forall a b. (a -> b) -> a -> b
$ Int -> RetryPolicy
exponentialBackoff Int
backoffMicroseconds
delayMicroseconds :: Int
delayMicroseconds = Int
32000000
backoffMicroseconds :: Int
backoffMicroseconds = Int
1000000
getConnectionStatus :: IO ConnectionStatus
getConnectionStatus :: IO ConnectionStatus
getConnectionStatus = do
Either UsageError PgVersion
pgVersion <- Pool -> Session PgVersion -> IO (Either UsageError PgVersion)
forall a. Pool -> Session a -> IO (Either UsageError a)
SQL.use Pool
pool Session PgVersion
queryPgVersion
case Either UsageError PgVersion
pgVersion of
Left UsageError
e -> do
let err :: PgError
err = Bool -> UsageError -> PgError
PgError Bool
False UsageError
e
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> (ByteString -> Text) -> ByteString -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Text
T.decodeUtf8 (ByteString -> Text)
-> (ByteString -> ByteString) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
LBS.toStrict (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ PgError -> ByteString
forall a. PgrstError a => a -> ByteString
errorPayload PgError
err
case PgError -> Maybe Text
checkIsFatal PgError
err of
Just Text
reason ->
ConnectionStatus -> IO ConnectionStatus
forall (m :: * -> *) a. Monad m => a -> m a
return (ConnectionStatus -> IO ConnectionStatus)
-> ConnectionStatus -> IO ConnectionStatus
forall a b. (a -> b) -> a -> b
$ Text -> ConnectionStatus
FatalConnectionError Text
reason
Maybe Text
Nothing ->
ConnectionStatus -> IO ConnectionStatus
forall (m :: * -> *) a. Monad m => a -> m a
return ConnectionStatus
NotConnected
Right PgVersion
version ->
if PgVersion
version PgVersion -> PgVersion -> Bool
forall a. Ord a => a -> a -> Bool
< PgVersion
minimumPgVersion then
ConnectionStatus -> IO ConnectionStatus
forall (m :: * -> *) a. Monad m => a -> m a
return (ConnectionStatus -> IO ConnectionStatus)
-> (Text -> ConnectionStatus) -> Text -> IO ConnectionStatus
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> ConnectionStatus
FatalConnectionError (Text -> IO ConnectionStatus) -> Text -> IO ConnectionStatus
forall a b. (a -> b) -> a -> b
$
Text
"Cannot run in this PostgreSQL version, PostgREST needs at least "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PgVersion -> Text
pgvName PgVersion
minimumPgVersion
else
ConnectionStatus -> IO ConnectionStatus
forall (m :: * -> *) a. Monad m => a -> m a
return (ConnectionStatus -> IO ConnectionStatus)
-> (PgVersion -> ConnectionStatus)
-> PgVersion
-> IO ConnectionStatus
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PgVersion -> ConnectionStatus
Connected (PgVersion -> IO ConnectionStatus)
-> PgVersion -> IO ConnectionStatus
forall a b. (a -> b) -> a -> b
$ PgVersion
version
shouldRetry :: RetryStatus -> ConnectionStatus -> IO Bool
shouldRetry :: RetryStatus -> ConnectionStatus -> IO Bool
shouldRetry RetryStatus
rs ConnectionStatus
isConnSucc = do
let
delay :: Int
delay = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe Int
0 (RetryStatus -> Maybe Int
rsPreviousDelay RetryStatus
rs) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
backoffMicroseconds
itShould :: Bool
itShould = ConnectionStatus
NotConnected ConnectionStatus -> ConnectionStatus -> Bool
forall a. Eq a => a -> a -> Bool
== ConnectionStatus
isConnSucc
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
itShould (IO () -> IO ()) -> (Text -> IO ()) -> Text -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$
Text
"Attempting to reconnect to the database in "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (Int -> Text
forall a b. (Show a, ConvertText FilePath b) => a -> b
show Int
delay::Text)
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" seconds..."
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
itShould (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ AppState -> Int -> IO ()
AppState.putRetryNextIn AppState
appState Int
delay
Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
itShould
loadSchemaCache :: AppState -> IO SCacheStatus
loadSchemaCache :: AppState -> IO SCacheStatus
loadSchemaCache AppState
appState = do
AppConfig{Bool
Int
[(Text, Text)]
[ByteString]
[Text]
JSPath
Maybe Integer
Maybe FilePath
Maybe ByteString
Maybe Text
Maybe StringOrURI
Maybe JWKSet
Maybe QualifiedIdentifier
Text
FileMode
NonEmpty Text
NominalDiffTime
OpenAPIMode
LogLevel
configServerUnixSocketMode :: FileMode
configServerUnixSocket :: Maybe FilePath
configServerPort :: Int
configServerHost :: Text
configRawMediaTypes :: [ByteString]
configOpenApiServerProxyUri :: Maybe Text
configOpenApiMode :: OpenAPIMode
configLogLevel :: LogLevel
configJwtSecretIsBase64 :: Bool
configJwtSecret :: Maybe ByteString
configJwtRoleClaimKey :: JSPath
configJwtAudience :: Maybe StringOrURI
configJWKS :: Maybe JWKSet
configFilePath :: Maybe FilePath
configDbUseLegacyGucs :: Bool
configDbUri :: Text
configDbTxRollbackAll :: Bool
configDbTxAllowOverride :: Bool
configDbConfig :: Bool
configDbSchemas :: NonEmpty Text
configDbRootSpec :: Maybe QualifiedIdentifier
configDbPreparedStatements :: Bool
configDbPreRequest :: Maybe QualifiedIdentifier
configDbPoolTimeout :: NominalDiffTime
configDbPoolSize :: Int
configDbMaxRows :: Maybe Integer
configDbExtraSearchPath :: [Text]
configDbChannelEnabled :: Bool
configDbChannel :: Text
configDbAnonRole :: Text
configAppSettings :: [(Text, Text)]
configServerUnixSocketMode :: AppConfig -> FileMode
configServerUnixSocket :: AppConfig -> Maybe FilePath
configServerPort :: AppConfig -> Int
configServerHost :: AppConfig -> Text
configRawMediaTypes :: AppConfig -> [ByteString]
configOpenApiServerProxyUri :: AppConfig -> Maybe Text
configOpenApiMode :: AppConfig -> OpenAPIMode
configLogLevel :: AppConfig -> LogLevel
configJwtSecretIsBase64 :: AppConfig -> Bool
configJwtSecret :: AppConfig -> Maybe ByteString
configJwtRoleClaimKey :: AppConfig -> JSPath
configJwtAudience :: AppConfig -> Maybe StringOrURI
configJWKS :: AppConfig -> Maybe JWKSet
configFilePath :: AppConfig -> Maybe FilePath
configDbUseLegacyGucs :: AppConfig -> Bool
configDbUri :: AppConfig -> Text
configDbTxRollbackAll :: AppConfig -> Bool
configDbTxAllowOverride :: AppConfig -> Bool
configDbConfig :: AppConfig -> Bool
configDbSchemas :: AppConfig -> NonEmpty Text
configDbRootSpec :: AppConfig -> Maybe QualifiedIdentifier
configDbPreparedStatements :: AppConfig -> Bool
configDbPreRequest :: AppConfig -> Maybe QualifiedIdentifier
configDbPoolTimeout :: AppConfig -> NominalDiffTime
configDbPoolSize :: AppConfig -> Int
configDbMaxRows :: AppConfig -> Maybe Integer
configDbExtraSearchPath :: AppConfig -> [Text]
configDbChannelEnabled :: AppConfig -> Bool
configDbChannel :: AppConfig -> Text
configDbAnonRole :: AppConfig -> Text
configAppSettings :: AppConfig -> [(Text, Text)]
..} <- AppState -> IO AppConfig
AppState.getConfig AppState
appState
PgVersion
actualPgVersion <- AppState -> IO PgVersion
AppState.getPgVersion AppState
appState
Either UsageError DbStructure
result <-
let transaction :: IsolationLevel -> Mode -> Transaction a -> Session a
transaction = if Bool
configDbPreparedStatements then IsolationLevel -> Mode -> Transaction a -> Session a
forall a. IsolationLevel -> Mode -> Transaction a -> Session a
SQL.transaction else IsolationLevel -> Mode -> Transaction a -> Session a
forall a. IsolationLevel -> Mode -> Transaction a -> Session a
SQL.unpreparedTransaction in
Pool -> Session DbStructure -> IO (Either UsageError DbStructure)
forall a. Pool -> Session a -> IO (Either UsageError a)
SQL.use (AppState -> Pool
AppState.getPool AppState
appState) (Session DbStructure -> IO (Either UsageError DbStructure))
-> (Transaction DbStructure -> Session DbStructure)
-> Transaction DbStructure
-> IO (Either UsageError DbStructure)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IsolationLevel
-> Mode -> Transaction DbStructure -> Session DbStructure
forall a. IsolationLevel -> Mode -> Transaction a -> Session a
transaction IsolationLevel
SQL.ReadCommitted Mode
SQL.Read (Transaction DbStructure -> IO (Either UsageError DbStructure))
-> Transaction DbStructure -> IO (Either UsageError DbStructure)
forall a b. (a -> b) -> a -> b
$
[Text] -> [Text] -> PgVersion -> Bool -> Transaction DbStructure
queryDbStructure (NonEmpty Text -> [Text]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList NonEmpty Text
configDbSchemas) [Text]
configDbExtraSearchPath PgVersion
actualPgVersion Bool
configDbPreparedStatements
case Either UsageError DbStructure
result of
Left UsageError
e -> do
let
err :: PgError
err = Bool -> UsageError -> PgError
PgError Bool
False UsageError
e
putErr :: IO ()
putErr = AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> (ByteString -> Text) -> ByteString -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Text
T.decodeUtf8 (ByteString -> Text)
-> (ByteString -> ByteString) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
LBS.toStrict (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ PgError -> ByteString
forall a. PgrstError a => a -> ByteString
errorPayload PgError
err
case PgError -> Maybe Text
checkIsFatal PgError
err of
Just Text
hint -> do
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
"A fatal error ocurred when loading the schema cache"
IO ()
putErr
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
hint
SCacheStatus -> IO SCacheStatus
forall (m :: * -> *) a. Monad m => a -> m a
return SCacheStatus
SCFatalFail
Maybe Text
Nothing -> do
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
"An error ocurred when loading the schema cache"
IO ()
putErr
SCacheStatus -> IO SCacheStatus
forall (m :: * -> *) a. Monad m => a -> m a
return SCacheStatus
SCOnRetry
Right DbStructure
dbStructure -> do
AppState -> DbStructure -> IO ()
AppState.putDbStructure AppState
appState DbStructure
dbStructure
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Maybe QualifiedIdentifier -> Bool
forall a. Maybe a -> Bool
isJust Maybe QualifiedIdentifier
configDbRootSpec) (IO () -> IO ()) -> (ByteString -> IO ()) -> ByteString -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
AppState -> ByteString -> IO ()
AppState.putJsonDbS AppState
appState (ByteString -> IO ())
-> (ByteString -> ByteString) -> ByteString -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
LBS.toStrict (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ DbStructure -> ByteString
forall a. ToJSON a => a -> ByteString
JSON.encode DbStructure
dbStructure
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
"Schema cache loaded"
SCacheStatus -> IO SCacheStatus
forall (m :: * -> *) a. Monad m => a -> m a
return SCacheStatus
SCLoaded
listener :: AppState -> IO ()
listener :: AppState -> IO ()
listener AppState
appState = do
AppConfig{Bool
Int
[(Text, Text)]
[ByteString]
[Text]
JSPath
Maybe Integer
Maybe FilePath
Maybe ByteString
Maybe Text
Maybe StringOrURI
Maybe JWKSet
Maybe QualifiedIdentifier
Text
FileMode
NonEmpty Text
NominalDiffTime
OpenAPIMode
LogLevel
configServerUnixSocketMode :: FileMode
configServerUnixSocket :: Maybe FilePath
configServerPort :: Int
configServerHost :: Text
configRawMediaTypes :: [ByteString]
configOpenApiServerProxyUri :: Maybe Text
configOpenApiMode :: OpenAPIMode
configLogLevel :: LogLevel
configJwtSecretIsBase64 :: Bool
configJwtSecret :: Maybe ByteString
configJwtRoleClaimKey :: JSPath
configJwtAudience :: Maybe StringOrURI
configJWKS :: Maybe JWKSet
configFilePath :: Maybe FilePath
configDbUseLegacyGucs :: Bool
configDbUri :: Text
configDbTxRollbackAll :: Bool
configDbTxAllowOverride :: Bool
configDbConfig :: Bool
configDbSchemas :: NonEmpty Text
configDbRootSpec :: Maybe QualifiedIdentifier
configDbPreparedStatements :: Bool
configDbPreRequest :: Maybe QualifiedIdentifier
configDbPoolTimeout :: NominalDiffTime
configDbPoolSize :: Int
configDbMaxRows :: Maybe Integer
configDbExtraSearchPath :: [Text]
configDbChannelEnabled :: Bool
configDbChannel :: Text
configDbAnonRole :: Text
configAppSettings :: [(Text, Text)]
configServerUnixSocketMode :: AppConfig -> FileMode
configServerUnixSocket :: AppConfig -> Maybe FilePath
configServerPort :: AppConfig -> Int
configServerHost :: AppConfig -> Text
configRawMediaTypes :: AppConfig -> [ByteString]
configOpenApiServerProxyUri :: AppConfig -> Maybe Text
configOpenApiMode :: AppConfig -> OpenAPIMode
configLogLevel :: AppConfig -> LogLevel
configJwtSecretIsBase64 :: AppConfig -> Bool
configJwtSecret :: AppConfig -> Maybe ByteString
configJwtRoleClaimKey :: AppConfig -> JSPath
configJwtAudience :: AppConfig -> Maybe StringOrURI
configJWKS :: AppConfig -> Maybe JWKSet
configFilePath :: AppConfig -> Maybe FilePath
configDbUseLegacyGucs :: AppConfig -> Bool
configDbUri :: AppConfig -> Text
configDbTxRollbackAll :: AppConfig -> Bool
configDbTxAllowOverride :: AppConfig -> Bool
configDbConfig :: AppConfig -> Bool
configDbSchemas :: AppConfig -> NonEmpty Text
configDbRootSpec :: AppConfig -> Maybe QualifiedIdentifier
configDbPreparedStatements :: AppConfig -> Bool
configDbPreRequest :: AppConfig -> Maybe QualifiedIdentifier
configDbPoolTimeout :: AppConfig -> NominalDiffTime
configDbPoolSize :: AppConfig -> Int
configDbMaxRows :: AppConfig -> Maybe Integer
configDbExtraSearchPath :: AppConfig -> [Text]
configDbChannelEnabled :: AppConfig -> Bool
configDbChannel :: AppConfig -> Text
configDbAnonRole :: AppConfig -> Text
configAppSettings :: AppConfig -> [(Text, Text)]
..} <- AppState -> IO AppConfig
AppState.getConfig AppState
appState
let dbChannel :: Text
dbChannel = Text -> Text
forall a b. ConvertText a b => a -> b
toS Text
configDbChannel
AppState -> IO ()
AppState.waitListener AppState
appState
IO ThreadId -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO ThreadId -> IO ()) -> (IO () -> IO ThreadId) -> IO () -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (IO () -> (Either SomeException () -> IO ()) -> IO ThreadId)
-> (Either SomeException () -> IO ()) -> IO () -> IO ThreadId
forall a b c. (a -> b -> c) -> b -> a -> c
flip IO () -> (Either SomeException () -> IO ()) -> IO ThreadId
forall a. IO a -> (Either SomeException a -> IO ()) -> IO ThreadId
forkFinally (Text -> Either SomeException () -> IO ()
forall p. Text -> p -> IO ()
handleFinally Text
dbChannel) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
Either (Maybe ByteString) Connection
dbOrError <- ByteString -> IO (Either (Maybe ByteString) Connection)
acquire (ByteString -> IO (Either (Maybe ByteString) Connection))
-> ByteString -> IO (Either (Maybe ByteString) Connection)
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
forall a. ConvertText a Text => a -> ByteString
toUtf8 Text
configDbUri
case Either (Maybe ByteString) Connection
dbOrError of
Right Connection
db -> do
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Text
"Listening for notifications on the " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
dbChannel Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" channel"
Connection -> PgIdentifier -> IO ()
SQL.listen Connection
db (PgIdentifier -> IO ()) -> PgIdentifier -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> PgIdentifier
SQL.toPgIdentifier Text
dbChannel
(ByteString -> ByteString -> IO ()) -> Connection -> IO ()
SQL.waitForNotifications ByteString -> ByteString -> IO ()
forall p. p -> ByteString -> IO ()
handleNotification Connection
db
Either (Maybe ByteString) Connection
_ ->
Text -> IO ()
forall a. Text -> IO a
die (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Text
"Could not listen for notifications on the " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
dbChannel Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" channel"
where
handleFinally :: Text -> p -> IO ()
handleFinally Text
dbChannel p
_ = do
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Text
"Retrying listening for notifications on the " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
dbChannel Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" channel.."
AppState -> IO ()
connectionWorker AppState
appState
AppState -> IO ()
listener AppState
appState
handleNotification :: p -> ByteString -> IO ()
handleNotification p
_ ByteString
msg
| ByteString -> Bool
BS.null ByteString
msg = IO ()
scLoader
| ByteString
msg ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
"reload schema" = IO ()
scLoader
| ByteString
msg ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
"reload config" = Bool -> AppState -> IO ()
reReadConfig Bool
False AppState
appState
| Bool
otherwise = () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
scLoader :: IO ()
scLoader =
IO SCacheStatus -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO SCacheStatus -> IO ()) -> IO SCacheStatus -> IO ()
forall a b. (a -> b) -> a -> b
$ AppState -> IO SCacheStatus
loadSchemaCache AppState
appState
reReadConfig :: Bool -> AppState -> IO ()
reReadConfig :: Bool -> AppState -> IO ()
reReadConfig Bool
startingUp AppState
appState = do
AppConfig{Bool
Int
[(Text, Text)]
[ByteString]
[Text]
JSPath
Maybe Integer
Maybe FilePath
Maybe ByteString
Maybe Text
Maybe StringOrURI
Maybe JWKSet
Maybe QualifiedIdentifier
Text
FileMode
NonEmpty Text
NominalDiffTime
OpenAPIMode
LogLevel
configServerUnixSocketMode :: FileMode
configServerUnixSocket :: Maybe FilePath
configServerPort :: Int
configServerHost :: Text
configRawMediaTypes :: [ByteString]
configOpenApiServerProxyUri :: Maybe Text
configOpenApiMode :: OpenAPIMode
configLogLevel :: LogLevel
configJwtSecretIsBase64 :: Bool
configJwtSecret :: Maybe ByteString
configJwtRoleClaimKey :: JSPath
configJwtAudience :: Maybe StringOrURI
configJWKS :: Maybe JWKSet
configFilePath :: Maybe FilePath
configDbUseLegacyGucs :: Bool
configDbUri :: Text
configDbTxRollbackAll :: Bool
configDbTxAllowOverride :: Bool
configDbConfig :: Bool
configDbSchemas :: NonEmpty Text
configDbRootSpec :: Maybe QualifiedIdentifier
configDbPreparedStatements :: Bool
configDbPreRequest :: Maybe QualifiedIdentifier
configDbPoolTimeout :: NominalDiffTime
configDbPoolSize :: Int
configDbMaxRows :: Maybe Integer
configDbExtraSearchPath :: [Text]
configDbChannelEnabled :: Bool
configDbChannel :: Text
configDbAnonRole :: Text
configAppSettings :: [(Text, Text)]
configServerUnixSocketMode :: AppConfig -> FileMode
configServerUnixSocket :: AppConfig -> Maybe FilePath
configServerPort :: AppConfig -> Int
configServerHost :: AppConfig -> Text
configRawMediaTypes :: AppConfig -> [ByteString]
configOpenApiServerProxyUri :: AppConfig -> Maybe Text
configOpenApiMode :: AppConfig -> OpenAPIMode
configLogLevel :: AppConfig -> LogLevel
configJwtSecretIsBase64 :: AppConfig -> Bool
configJwtSecret :: AppConfig -> Maybe ByteString
configJwtRoleClaimKey :: AppConfig -> JSPath
configJwtAudience :: AppConfig -> Maybe StringOrURI
configJWKS :: AppConfig -> Maybe JWKSet
configFilePath :: AppConfig -> Maybe FilePath
configDbUseLegacyGucs :: AppConfig -> Bool
configDbUri :: AppConfig -> Text
configDbTxRollbackAll :: AppConfig -> Bool
configDbTxAllowOverride :: AppConfig -> Bool
configDbConfig :: AppConfig -> Bool
configDbSchemas :: AppConfig -> NonEmpty Text
configDbRootSpec :: AppConfig -> Maybe QualifiedIdentifier
configDbPreparedStatements :: AppConfig -> Bool
configDbPreRequest :: AppConfig -> Maybe QualifiedIdentifier
configDbPoolTimeout :: AppConfig -> NominalDiffTime
configDbPoolSize :: AppConfig -> Int
configDbMaxRows :: AppConfig -> Maybe Integer
configDbExtraSearchPath :: AppConfig -> [Text]
configDbChannelEnabled :: AppConfig -> Bool
configDbChannel :: AppConfig -> Text
configDbAnonRole :: AppConfig -> Text
configAppSettings :: AppConfig -> [(Text, Text)]
..} <- AppState -> IO AppConfig
AppState.getConfig AppState
appState
[(Text, Text)]
dbSettings <-
if Bool
configDbConfig then do
Either UsageError [(Text, Text)]
qDbSettings <- Pool -> Bool -> IO (Either UsageError [(Text, Text)])
queryDbSettings (AppState -> Pool
AppState.getPool AppState
appState) Bool
configDbPreparedStatements
case Either UsageError [(Text, Text)]
qDbSettings of
Left UsageError
e -> do
let
err :: PgError
err = Bool -> UsageError -> PgError
PgError Bool
False UsageError
e
putErr :: IO ()
putErr = AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> (ByteString -> Text) -> ByteString -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Text
T.decodeUtf8 (ByteString -> Text)
-> (ByteString -> ByteString) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
LBS.toStrict (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ PgError -> ByteString
forall a. PgrstError a => a -> ByteString
errorPayload PgError
err
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState
Text
"An error ocurred when trying to query database settings for the config parameters"
case PgError -> Maybe Text
checkIsFatal PgError
err of
Just Text
hint -> do
IO ()
putErr
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
hint
ThreadId -> IO ()
killThread (AppState -> ThreadId
AppState.getMainThreadId AppState
appState)
Maybe Text
Nothing -> do
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ UsageError -> Text
forall a b. (Show a, ConvertText FilePath b) => a -> b
show UsageError
e
[(Text, Text)] -> IO [(Text, Text)]
forall (f :: * -> *) a. Applicative f => a -> f a
pure []
Right [(Text, Text)]
x -> [(Text, Text)] -> IO [(Text, Text)]
forall (f :: * -> *) a. Applicative f => a -> f a
pure [(Text, Text)]
x
else
[(Text, Text)] -> IO [(Text, Text)]
forall (f :: * -> *) a. Applicative f => a -> f a
pure [(Text, Text)]
forall a. Monoid a => a
mempty
[(Text, Text)]
-> Maybe FilePath -> Maybe Text -> IO (Either Text AppConfig)
readAppConfig [(Text, Text)]
dbSettings Maybe FilePath
configFilePath (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
configDbUri) IO (Either Text AppConfig)
-> (Either Text AppConfig -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left Text
err ->
if Bool
startingUp then
Text -> IO ()
forall a. HasCallStack => Text -> a
panic Text
err
else
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Text
"Failed re-loading config: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
err
Right AppConfig
newConf -> do
AppState -> AppConfig -> IO ()
AppState.putConfig AppState
appState AppConfig
newConf
if Bool
startingUp then
IO ()
forall (f :: * -> *). Applicative f => f ()
pass
else
AppState -> Text -> IO ()
AppState.logWithZTime AppState
appState Text
"Config re-loaded"