Skip to content

Commit cf61cce

Browse files
committed
add the ability to retry database connections when they fail
1 parent edb987e commit cf61cce

File tree

4 files changed

+41
-14
lines changed

4 files changed

+41
-14
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# 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
2-
COMPOSE_PROFILES=postgres
2+
COMPOSE_PROFILES=mssql

configuration.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ SQLPage can be configured through either [environment variables](https://en.wiki
44
on a [JSON](https://en.wikipedia.org/wiki/JSON) file placed in `sqlpage/sqlpage.json`.
55

66
| variable | default | description |
7-
|--------------------------------------------|------------------------------|--------------------------------------------------------------------------|
7+
| ------------------------------------------ | ---------------------------- | ------------------------------------------------------------------------ |
88
| `listen_on` | 0.0.0.0:8080 | Interface and port on which the web server should listen |
99
| `database_url` | sqlite://sqlpage.db?mode=rwc | Database connection URL |
1010
| `port` | 8080 | Like listen_on, but specifies only the port. |
1111
| `max_database_pool_connections` | depends on the database | How many simultaneous database connections to open at most |
1212
| `database_connection_idle_timeout_seconds` | depends on the database | Automatically close database connections after this period of inactivity |
1313
| `database_connection_max_lifetime_seconds` | depends on the database | Always close database connections after this amount of time |
14+
| `database_connection_retries` | 6 | Database connection attempts before giving up. Retries will happen every 5 seconds. |
1415
| `sqlite_extensions` | | An array of SQLite extensions to load, such as `mod_spatialite` |
1516

1617
You can find an example configuration file in [`sqlpage/sqlpage.json`](./sqlpage/sqlpage.json).
@@ -26,5 +27,5 @@ but in uppercase.
2627

2728
```bash
2829
DATABASE_URL="sqlite:///path/to/my_database.db?mode=rwc"
29-
SQLITE_EXTENSIONS="mod_spatialite crypto define regexp"
30-
```
30+
SQLITE_EXTENSIONS="mod_spatialite crypto define regexp"
31+
```

src/app_config.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@ pub struct AppConfig {
2121
#[serde(deserialize_with = "deserialize_socket_addr")]
2222
pub listen_on: SocketAddr,
2323
pub port: Option<u16>,
24+
25+
/// Number of times to retry connecting to the database after a failure when the server starts
26+
/// up. Retries will happen every 5 seconds. The default is 6 retries, which means the server
27+
/// will wait up to 30 seconds for the database to become available.
28+
#[serde(default = "default_database_connection_retries")]
29+
pub database_connection_retries: u32,
2430
}
2531

2632
pub fn load() -> anyhow::Result<AppConfig> {
2733
let mut conf = Config::builder()
2834
.set_default("listen_on", "0.0.0.0:8080")?
2935
.add_source(config::File::with_name("sqlpage/sqlpage").required(false))
30-
.add_source(
31-
config::Environment::default()
32-
.try_parsing(true)
33-
.list_separator(" ")
34-
.with_list_parse_key("sqlite_extensions"),
35-
)
36+
.add_source(env_config())
3637
.build()?
3738
.try_deserialize::<AppConfig>()
3839
.with_context(|| "Unable to load configuration")?;
@@ -42,6 +43,13 @@ pub fn load() -> anyhow::Result<AppConfig> {
4243
Ok(conf)
4344
}
4445

46+
fn env_config() -> config::Environment {
47+
config::Environment::default()
48+
.try_parsing(true)
49+
.list_separator(" ")
50+
.with_list_parse_key("sqlite_extensions")
51+
}
52+
4553
fn deserialize_socket_addr<'de, D: Deserializer<'de>>(
4654
deserializer: D,
4755
) -> Result<SocketAddr, D::Error> {
@@ -80,6 +88,10 @@ fn default_database_url() -> String {
8088
prefix + ":memory:"
8189
}
8290

91+
fn default_database_connection_retries() -> u32 {
92+
6
93+
}
94+
8395
#[cfg(test)]
8496
pub(crate) mod tests {
8597
use super::AppConfig;

src/webserver/database/mod.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,24 @@ impl Database {
217217
);
218218
set_custom_connect_options(&mut connect_options, config);
219219
log::info!("Connecting to database: {database_url}");
220-
let connection = Self::create_pool_options(config, connect_options.kind())
221-
.connect_with(connect_options)
222-
.await
223-
.with_context(|| format!("Unable to open connection to {database_url}"))?;
220+
let mut retries = config.database_connection_retries;
221+
let connection = loop {
222+
match Self::create_pool_options(config, connect_options.kind())
223+
.connect_with(connect_options.clone())
224+
.await
225+
{
226+
Ok(c) => break c,
227+
Err(e) => {
228+
if retries == 0 {
229+
return Err(anyhow::Error::new(e)
230+
.context(format!("Unable to open connection to {database_url}")));
231+
}
232+
log::warn!("Failed to connect to the database: {e:#}. Retrying in 5 seconds.");
233+
retries -= 1;
234+
tokio::time::sleep(Duration::from_secs(5)).await;
235+
}
236+
}
237+
};
224238
log::debug!("Initialized database pool: {connection:#?}");
225239
Ok(Database { connection })
226240
}

0 commit comments

Comments
 (0)