add the ability to retry database connections when they fail

This commit is contained in:
lovasoa
2023-08-20 23:23:33 +02:00
parent edb987eaaf
commit cf61cce9dc
4 changed files with 41 additions and 14 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+18 -4
View File
@@ -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 })
}