diff --git a/.env b/.env index 96c70e8b..440b4920 100644 --- a/.env +++ b/.env @@ -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 \ No newline at end of file +COMPOSE_PROFILES=mssql \ No newline at end of file diff --git a/configuration.md b/configuration.md index ec93961a..d5864494 100644 --- a/configuration.md +++ b/configuration.md @@ -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" -``` \ No newline at end of file +SQLITE_EXTENSIONS="mod_spatialite crypto define regexp" +``` diff --git a/src/app_config.rs b/src/app_config.rs index 8f88355c..7c2f4363 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -21,18 +21,19 @@ pub struct AppConfig { #[serde(deserialize_with = "deserialize_socket_addr")] pub listen_on: SocketAddr, pub port: Option, + + /// 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 { 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::() .with_context(|| "Unable to load configuration")?; @@ -42,6 +43,13 @@ pub fn load() -> anyhow::Result { 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 { @@ -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; diff --git a/src/webserver/database/mod.rs b/src/webserver/database/mod.rs index 97642f49..cbd76840 100644 --- a/src/webserver/database/mod.rs +++ b/src/webserver/database/mod.rs @@ -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 }) }