feat: introduce pool acquisition timeout (fixes #2348)
The configuration option db-pool-acquisition-timeout specifies the time in seconds to wait for the pool to free up a connection slot. Otherwise, a 504 error is returned. By default, there is no timeout.
This commit is contained in:
@@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- #2401, #2444, Fix SIGUSR1 to fully flush connections pool, remove `db-pool-timeout`. - @robx
|
- #2401, #2444, Fix SIGUSR1 to fully flush connections pool, remove `db-pool-timeout`. - @robx
|
||||||
|
- #2348, Add `db-pool-acquisition-timeout` configuration option, time in seconds to wait to acquire a connection. - @robx
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ destroy = destroyPool
|
|||||||
|
|
||||||
initPool :: AppConfig -> IO SQL.Pool
|
initPool :: AppConfig -> IO SQL.Pool
|
||||||
initPool AppConfig{..} =
|
initPool AppConfig{..} =
|
||||||
SQL.acquire configDbPoolSize Nothing $ toUtf8 configDbUri
|
SQL.acquire configDbPoolSize timeoutMilliseconds $ toUtf8 configDbUri
|
||||||
|
where
|
||||||
|
timeoutMilliseconds = (* oneSecond) <$> configDbPoolAcquisitionTimeout
|
||||||
|
oneSecond = 1000000
|
||||||
|
|
||||||
-- | Run an action with a database connection.
|
-- | Run an action with a database connection.
|
||||||
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
|
usePool :: AppState -> SQL.Session a -> IO (Either SQL.UsageError a)
|
||||||
|
|||||||
@@ -148,6 +148,9 @@ exampleConfigFile =
|
|||||||
|## Number of open connections in the pool
|
|## Number of open connections in the pool
|
||||||
|db-pool = 10
|
|db-pool = 10
|
||||||
|
|
|
|
||||||
|
|## Time in seconds to wait to acquire a slot from the connection pool
|
||||||
|
|# db-pool-acquisition-timeout = 10
|
||||||
|
|
|
||||||
|## Stored proc to exec immediately after auth
|
|## Stored proc to exec immediately after auth
|
||||||
|# db-pre-request = "stored_proc_name"
|
|# db-pre-request = "stored_proc_name"
|
||||||
|
|
|
|
||||||
|
|||||||
+37
-34
@@ -62,39 +62,40 @@ import Protolude hiding (Proxy, toList)
|
|||||||
|
|
||||||
|
|
||||||
data AppConfig = AppConfig
|
data AppConfig = AppConfig
|
||||||
{ configAppSettings :: [(Text, Text)]
|
{ configAppSettings :: [(Text, Text)]
|
||||||
, configDbAnonRole :: Maybe Text
|
, configDbAnonRole :: Maybe Text
|
||||||
, configDbChannel :: Text
|
, configDbChannel :: Text
|
||||||
, configDbChannelEnabled :: Bool
|
, configDbChannelEnabled :: Bool
|
||||||
, configDbExtraSearchPath :: [Text]
|
, configDbExtraSearchPath :: [Text]
|
||||||
, configDbMaxRows :: Maybe Integer
|
, configDbMaxRows :: Maybe Integer
|
||||||
, configDbPlanEnabled :: Bool
|
, configDbPlanEnabled :: Bool
|
||||||
, configDbPoolSize :: Int
|
, configDbPoolSize :: Int
|
||||||
, configDbPreRequest :: Maybe QualifiedIdentifier
|
, configDbPoolAcquisitionTimeout :: Maybe Int
|
||||||
, configDbPreparedStatements :: Bool
|
, configDbPreRequest :: Maybe QualifiedIdentifier
|
||||||
, configDbRootSpec :: Maybe QualifiedIdentifier
|
, configDbPreparedStatements :: Bool
|
||||||
, configDbSchemas :: NonEmpty Text
|
, configDbRootSpec :: Maybe QualifiedIdentifier
|
||||||
, configDbConfig :: Bool
|
, configDbSchemas :: NonEmpty Text
|
||||||
, configDbTxAllowOverride :: Bool
|
, configDbConfig :: Bool
|
||||||
, configDbTxRollbackAll :: Bool
|
, configDbTxAllowOverride :: Bool
|
||||||
, configDbUri :: Text
|
, configDbTxRollbackAll :: Bool
|
||||||
, configDbUseLegacyGucs :: Bool
|
, configDbUri :: Text
|
||||||
, configFilePath :: Maybe FilePath
|
, configDbUseLegacyGucs :: Bool
|
||||||
, configJWKS :: Maybe JWKSet
|
, configFilePath :: Maybe FilePath
|
||||||
, configJwtAudience :: Maybe StringOrURI
|
, configJWKS :: Maybe JWKSet
|
||||||
, configJwtRoleClaimKey :: JSPath
|
, configJwtAudience :: Maybe StringOrURI
|
||||||
, configJwtSecret :: Maybe BS.ByteString
|
, configJwtRoleClaimKey :: JSPath
|
||||||
, configJwtSecretIsBase64 :: Bool
|
, configJwtSecret :: Maybe BS.ByteString
|
||||||
, configLogLevel :: LogLevel
|
, configJwtSecretIsBase64 :: Bool
|
||||||
, configOpenApiMode :: OpenAPIMode
|
, configLogLevel :: LogLevel
|
||||||
, configOpenApiSecurityActive :: Bool
|
, configOpenApiMode :: OpenAPIMode
|
||||||
, configOpenApiServerProxyUri :: Maybe Text
|
, configOpenApiSecurityActive :: Bool
|
||||||
, configRawMediaTypes :: [MediaType]
|
, configOpenApiServerProxyUri :: Maybe Text
|
||||||
, configServerHost :: Text
|
, configRawMediaTypes :: [MediaType]
|
||||||
, configServerPort :: Int
|
, configServerHost :: Text
|
||||||
, configServerUnixSocket :: Maybe FilePath
|
, configServerPort :: Int
|
||||||
, configServerUnixSocketMode :: FileMode
|
, configServerUnixSocket :: Maybe FilePath
|
||||||
, configAdminServerPort :: Maybe Int
|
, configServerUnixSocketMode :: FileMode
|
||||||
|
, configAdminServerPort :: Maybe Int
|
||||||
}
|
}
|
||||||
|
|
||||||
data LogLevel = LogCrit | LogError | LogWarn | LogInfo
|
data LogLevel = LogCrit | LogError | LogWarn | LogInfo
|
||||||
@@ -129,6 +130,7 @@ toText conf =
|
|||||||
,("db-max-rows", maybe "\"\"" show . configDbMaxRows)
|
,("db-max-rows", maybe "\"\"" show . configDbMaxRows)
|
||||||
,("db-plan-enabled", T.toLower . show . configDbPlanEnabled)
|
,("db-plan-enabled", T.toLower . show . configDbPlanEnabled)
|
||||||
,("db-pool", show . configDbPoolSize)
|
,("db-pool", show . configDbPoolSize)
|
||||||
|
,("db-pool-acquisition-timeout", maybe "\"\"" show . configDbPoolAcquisitionTimeout)
|
||||||
,("db-pre-request", q . maybe mempty dumpQi . configDbPreRequest)
|
,("db-pre-request", q . maybe mempty dumpQi . configDbPreRequest)
|
||||||
,("db-prepared-statements", T.toLower . show . configDbPreparedStatements)
|
,("db-prepared-statements", T.toLower . show . configDbPreparedStatements)
|
||||||
,("db-root-spec", q . maybe mempty dumpQi . configDbRootSpec)
|
,("db-root-spec", q . maybe mempty dumpQi . configDbRootSpec)
|
||||||
@@ -217,6 +219,7 @@ parser optPath env dbSettings =
|
|||||||
(optInt "max-rows")
|
(optInt "max-rows")
|
||||||
<*> (fromMaybe False <$> optBool "db-plan-enabled")
|
<*> (fromMaybe False <$> optBool "db-plan-enabled")
|
||||||
<*> (fromMaybe 10 <$> optInt "db-pool")
|
<*> (fromMaybe 10 <$> optInt "db-pool")
|
||||||
|
<*> optInt "db-pool-acquisition-timeout"
|
||||||
<*> (fmap toQi <$> optWithAlias (optString "db-pre-request")
|
<*> (fmap toQi <$> optWithAlias (optString "db-pre-request")
|
||||||
(optString "pre-request"))
|
(optString "pre-request"))
|
||||||
<*> (fromMaybe True <$> optBool "db-prepared-statements")
|
<*> (fromMaybe True <$> optBool "db-prepared-statements")
|
||||||
@@ -352,7 +355,7 @@ parser optPath env dbSettings =
|
|||||||
let dbSettingName = T.pack $ dashToUnderscore <$> toS key in
|
let dbSettingName = T.pack $ dashToUnderscore <$> toS key in
|
||||||
if dbSettingName `notElem` [
|
if dbSettingName `notElem` [
|
||||||
"server_host", "server_port", "server_unix_socket", "server_unix_socket_mode", "admin_server_port", "log_level",
|
"server_host", "server_port", "server_unix_socket", "server_unix_socket_mode", "admin_server_port", "log_level",
|
||||||
"db_uri", "db_channel_enabled", "db_channel", "db_pool", "db_config"]
|
"db_uri", "db_channel_enabled", "db_channel", "db_pool", "db_pool_acquisition_timeout", "db_config"]
|
||||||
then lookup dbSettingName dbSettings
|
then lookup dbSettingName dbSettings
|
||||||
else Nothing
|
else Nothing
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public"
|
|||||||
db-max-rows = 1000
|
db-max-rows = 1000
|
||||||
db-plan-enabled = false
|
db-plan-enabled = false
|
||||||
db-pool = 10
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = ""
|
||||||
db-pre-request = "check_alias"
|
db-pre-request = "check_alias"
|
||||||
db-prepared-statements = true
|
db-prepared-statements = true
|
||||||
db-root-spec = "open_alias"
|
db-root-spec = "open_alias"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public"
|
|||||||
db-max-rows = ""
|
db-max-rows = ""
|
||||||
db-plan-enabled = false
|
db-plan-enabled = false
|
||||||
db-pool = 10
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = ""
|
||||||
db-pre-request = ""
|
db-pre-request = ""
|
||||||
db-prepared-statements = false
|
db-prepared-statements = false
|
||||||
db-root-spec = ""
|
db-root-spec = ""
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public"
|
|||||||
db-max-rows = ""
|
db-max-rows = ""
|
||||||
db-plan-enabled = false
|
db-plan-enabled = false
|
||||||
db-pool = 10
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = ""
|
||||||
db-pre-request = ""
|
db-pre-request = ""
|
||||||
db-prepared-statements = false
|
db-prepared-statements = false
|
||||||
db-root-spec = ""
|
db-root-spec = ""
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public"
|
|||||||
db-max-rows = ""
|
db-max-rows = ""
|
||||||
db-plan-enabled = false
|
db-plan-enabled = false
|
||||||
db-pool = 10
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = ""
|
||||||
db-pre-request = ""
|
db-pre-request = ""
|
||||||
db-prepared-statements = true
|
db-prepared-statements = true
|
||||||
db-root-spec = ""
|
db-root-spec = ""
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public,extensions,other"
|
|||||||
db-max-rows = 100
|
db-max-rows = 100
|
||||||
db-plan-enabled = true
|
db-plan-enabled = true
|
||||||
db-pool = 1
|
db-pool = 1
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
db-pre-request = "test.other_custom_headers"
|
db-pre-request = "test.other_custom_headers"
|
||||||
db-prepared-statements = false
|
db-prepared-statements = false
|
||||||
db-root-spec = "other_root"
|
db-root-spec = "other_root"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public,extensions,private"
|
|||||||
db-max-rows = 1000
|
db-max-rows = 1000
|
||||||
db-plan-enabled = true
|
db-plan-enabled = true
|
||||||
db-pool = 1
|
db-pool = 1
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
db-pre-request = "test.custom_headers"
|
db-pre-request = "test.custom_headers"
|
||||||
db-prepared-statements = false
|
db-prepared-statements = false
|
||||||
db-root-spec = "root"
|
db-root-spec = "root"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public,test"
|
|||||||
db-max-rows = 1000
|
db-max-rows = 1000
|
||||||
db-plan-enabled = true
|
db-plan-enabled = true
|
||||||
db-pool = 1
|
db-pool = 1
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
db-pre-request = "please_run_fast"
|
db-pre-request = "please_run_fast"
|
||||||
db-prepared-statements = false
|
db-prepared-statements = false
|
||||||
db-root-spec = "openapi_v3"
|
db-root-spec = "openapi_v3"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public"
|
|||||||
db-max-rows = ""
|
db-max-rows = ""
|
||||||
db-plan-enabled = false
|
db-plan-enabled = false
|
||||||
db-pool = 10
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = ""
|
||||||
db-pre-request = ""
|
db-pre-request = ""
|
||||||
db-prepared-statements = true
|
db-prepared-statements = true
|
||||||
db-root-spec = ""
|
db-root-spec = ""
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ PGRST_DB_EXTRA_SEARCH_PATH: public, test
|
|||||||
PGRST_DB_MAX_ROWS: 1000
|
PGRST_DB_MAX_ROWS: 1000
|
||||||
PGRST_DB_PLAN_ENABLED: true
|
PGRST_DB_PLAN_ENABLED: true
|
||||||
PGRST_DB_POOL: 1
|
PGRST_DB_POOL: 1
|
||||||
|
PGRST_DB_POOL_ACQUISITION_TIMEOUT: 10
|
||||||
PGRST_DB_PREPARED_STATEMENTS: false
|
PGRST_DB_PREPARED_STATEMENTS: false
|
||||||
PGRST_DB_PRE_REQUEST: please_run_fast
|
PGRST_DB_PRE_REQUEST: please_run_fast
|
||||||
PGRST_DB_ROOT_SPEC: openapi_v3
|
PGRST_DB_ROOT_SPEC: openapi_v3
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ db-extra-search-path = "public, test"
|
|||||||
db-max-rows = 1000
|
db-max-rows = 1000
|
||||||
db-plan-enabled = true
|
db-plan-enabled = true
|
||||||
db-pool = 1
|
db-pool = 1
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
db-pre-request = "please_run_fast"
|
db-pre-request = "please_run_fast"
|
||||||
db-prepared-statements = false
|
db-prepared-statements = false
|
||||||
db-root-spec = "openapi_v3"
|
db-root-spec = "openapi_v3"
|
||||||
|
|||||||
@@ -548,6 +548,22 @@ def test_pool_size(defaultenv, metapostgrest):
|
|||||||
assert delta > 1 and delta < 1.5
|
assert delta > 1 and delta < 1.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_pool_acquisition_timeout(defaultenv, metapostgrest):
|
||||||
|
"Verify that PGRST_DB_POOL_ACQUISITON_TIMEOUT times out when the pool is empty"
|
||||||
|
|
||||||
|
env = {
|
||||||
|
**defaultenv,
|
||||||
|
"PGRST_DB_POOL": "1",
|
||||||
|
"PGRST_DB_POOL_ACQUISITION_TIMEOUT": "1", # 1 second
|
||||||
|
}
|
||||||
|
|
||||||
|
with run(env=env, no_pool_connection_available=True) as postgrest:
|
||||||
|
response = postgrest.session.get("/projects")
|
||||||
|
assert response.status_code == 504
|
||||||
|
data = response.json()
|
||||||
|
assert data["message"] == "Timed out acquiring connection from connection pool."
|
||||||
|
|
||||||
|
|
||||||
def test_change_statement_timeout_held_connection(defaultenv, metapostgrest):
|
def test_change_statement_timeout_held_connection(defaultenv, metapostgrest):
|
||||||
"Statement timeout changes take effect immediately, even with a request outliving the reconfiguration"
|
"Statement timeout changes take effect immediately, even with a request outliving the reconfiguration"
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
|
|||||||
, configDbMaxRows = Nothing
|
, configDbMaxRows = Nothing
|
||||||
, configDbPlanEnabled = False
|
, configDbPlanEnabled = False
|
||||||
, configDbPoolSize = 10
|
, configDbPoolSize = 10
|
||||||
|
, configDbPoolAcquisitionTimeout = Nothing
|
||||||
, configDbPreRequest = Just $ QualifiedIdentifier "test" "switch_role"
|
, configDbPreRequest = Just $ QualifiedIdentifier "test" "switch_role"
|
||||||
, configDbPreparedStatements = True
|
, configDbPreparedStatements = True
|
||||||
, configDbRootSpec = Nothing
|
, configDbRootSpec = Nothing
|
||||||
|
|||||||
Reference in New Issue
Block a user