add the ability to retry database connections when they fail
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
# Set COMPOSE_PROFILES to one of the following: postgres, mysql, mssql. If set to mssql, you will need to change the depends_on propery in docker-compose.yml
|
||||
COMPOSE_PROFILES=postgres
|
||||
COMPOSE_PROFILES=mssql
|
||||
+4
-3
@@ -4,13 +4,14 @@ SQLPage can be configured through either [environment variables](https://en.wiki
|
||||
on a [JSON](https://en.wikipedia.org/wiki/JSON) file placed in `sqlpage/sqlpage.json`.
|
||||
|
||||
| variable | default | description |
|
||||
|--------------------------------------------|------------------------------|--------------------------------------------------------------------------|
|
||||
| ------------------------------------------ | ---------------------------- | ------------------------------------------------------------------------ |
|
||||
| `listen_on` | 0.0.0.0:8080 | Interface and port on which the web server should listen |
|
||||
| `database_url` | sqlite://sqlpage.db?mode=rwc | Database connection URL |
|
||||
| `port` | 8080 | Like listen_on, but specifies only the port. |
|
||||
| `max_database_pool_connections` | depends on the database | How many simultaneous database connections to open at most |
|
||||
| `database_connection_idle_timeout_seconds` | depends on the database | Automatically close database connections after this period of inactivity |
|
||||
| `database_connection_max_lifetime_seconds` | depends on the database | Always close database connections after this amount of time |
|
||||
| `database_connection_retries` | 6 | Database connection attempts before giving up. Retries will happen every 5 seconds. |
|
||||
| `sqlite_extensions` | | An array of SQLite extensions to load, such as `mod_spatialite` |
|
||||
|
||||
You can find an example configuration file in [`sqlpage/sqlpage.json`](./sqlpage/sqlpage.json).
|
||||
@@ -26,5 +27,5 @@ but in uppercase.
|
||||
|
||||
```bash
|
||||
DATABASE_URL="sqlite:///path/to/my_database.db?mode=rwc"
|
||||
SQLITE_EXTENSIONS="mod_spatialite crypto define regexp"
|
||||
```
|
||||
SQLITE_EXTENSIONS="mod_spatialite crypto define regexp"
|
||||
```
|
||||
|
||||
+18
-6
@@ -21,18 +21,19 @@ pub struct AppConfig {
|
||||
#[serde(deserialize_with = "deserialize_socket_addr")]
|
||||
pub listen_on: SocketAddr,
|
||||
pub port: Option<u16>,
|
||||
|
||||
/// Number of times to retry connecting to the database after a failure when the server starts
|
||||
/// up. Retries will happen every 5 seconds. The default is 6 retries, which means the server
|
||||
/// will wait up to 30 seconds for the database to become available.
|
||||
#[serde(default = "default_database_connection_retries")]
|
||||
pub database_connection_retries: u32,
|
||||
}
|
||||
|
||||
pub fn load() -> anyhow::Result<AppConfig> {
|
||||
let mut conf = Config::builder()
|
||||
.set_default("listen_on", "0.0.0.0:8080")?
|
||||
.add_source(config::File::with_name("sqlpage/sqlpage").required(false))
|
||||
.add_source(
|
||||
config::Environment::default()
|
||||
.try_parsing(true)
|
||||
.list_separator(" ")
|
||||
.with_list_parse_key("sqlite_extensions"),
|
||||
)
|
||||
.add_source(env_config())
|
||||
.build()?
|
||||
.try_deserialize::<AppConfig>()
|
||||
.with_context(|| "Unable to load configuration")?;
|
||||
@@ -42,6 +43,13 @@ pub fn load() -> anyhow::Result<AppConfig> {
|
||||
Ok(conf)
|
||||
}
|
||||
|
||||
fn env_config() -> config::Environment {
|
||||
config::Environment::default()
|
||||
.try_parsing(true)
|
||||
.list_separator(" ")
|
||||
.with_list_parse_key("sqlite_extensions")
|
||||
}
|
||||
|
||||
fn deserialize_socket_addr<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<SocketAddr, D::Error> {
|
||||
@@ -80,6 +88,10 @@ fn default_database_url() -> String {
|
||||
prefix + ":memory:"
|
||||
}
|
||||
|
||||
fn default_database_connection_retries() -> u32 {
|
||||
6
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::AppConfig;
|
||||
|
||||
@@ -217,10 +217,24 @@ impl Database {
|
||||
);
|
||||
set_custom_connect_options(&mut connect_options, config);
|
||||
log::info!("Connecting to database: {database_url}");
|
||||
let connection = Self::create_pool_options(config, connect_options.kind())
|
||||
.connect_with(connect_options)
|
||||
.await
|
||||
.with_context(|| format!("Unable to open connection to {database_url}"))?;
|
||||
let mut retries = config.database_connection_retries;
|
||||
let connection = loop {
|
||||
match Self::create_pool_options(config, connect_options.kind())
|
||||
.connect_with(connect_options.clone())
|
||||
.await
|
||||
{
|
||||
Ok(c) => break c,
|
||||
Err(e) => {
|
||||
if retries == 0 {
|
||||
return Err(anyhow::Error::new(e)
|
||||
.context(format!("Unable to open connection to {database_url}")));
|
||||
}
|
||||
log::warn!("Failed to connect to the database: {e:#}. Retrying in 5 seconds.");
|
||||
retries -= 1;
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
};
|
||||
log::debug!("Initialized database pool: {connection:#?}");
|
||||
Ok(Database { connection })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user