Commit 67b1307a authored by Robert Czechowski's avatar Robert Czechowski Committed by Robert Czechowski

Restart database connection if connection is lost

parent d6bf1411
Pipeline #850 passed with stages
in 18 minutes and 50 seconds
......@@ -46,13 +46,14 @@ pub struct ContestInfo {
pub public: bool,
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum MedalError {
NotLoggedIn,
AccessDenied,
CsrfCheckFailed,
SessionTimeout,
DatabaseError,
DatabaseConnectionError,
PasswordHashingError,
UnmatchedPasswords,
NotFound,
......@@ -189,7 +190,7 @@ pub fn debug<T: MedalConnection>(conn: &T, session_token: Option<String>)
pub fn debug_create_session<T: MedalConnection>(conn: &T, session_token: Option<String>) {
if let Some(token) = session_token {
conn.get_session_or_new(&token);
conn.get_session_or_new(&token).unwrap();
}
}
......@@ -203,11 +204,11 @@ pub enum ContestVisibility {
pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str, login_info: LoginInfo,
visibility: ContestVisibility)
-> MedalValue
-> MedalValueResult
{
let mut data = json_val::Map::new();
let session = conn.get_session_or_new(&session_token);
let session = conn.get_session_or_new(&session_token).map_err(|_| MedalError::DatabaseConnectionError)?;
fill_user_data(&session, &mut data);
if session.is_logged_in() {
......@@ -242,7 +243,7 @@ pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str, login_in
ContestVisibility::All => "Alle Wettbewerbe",
}));
("contests".to_owned(), data)
Ok(("contests".to_owned(), data))
}
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
......@@ -303,7 +304,7 @@ pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token
query_string: Option<String>, login_info: LoginInfo)
-> MedalValueResult
{
let session = conn.get_session_or_new(&session_token);
let session = conn.get_session_or_new(&session_token).unwrap();
let contest = conn.get_contest_by_id_complete(contest_id);
let grades = conn.get_contest_user_grades(&session_token, contest_id);
......@@ -485,7 +486,7 @@ pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, sessi
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str)
-> MedalResult<()> {
// TODO: Is _or_new the right semantic? We need a CSRF token anyway …
let session = conn.get_session_or_new(&session_token);
let session = conn.get_session_or_new(&session_token).unwrap();
let contest = conn.get_contest_by_id(contest_id);
// Check logged in or open contest
......@@ -680,7 +681,7 @@ pub fn save_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token
}
pub fn show_task<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str) -> MedalValueResult {
let session = conn.get_session_or_new(&session_token);
let session = conn.get_session_or_new(&session_token).unwrap();
let (t, tg, c) = conn.get_task_by_id_complete(task_id);
let grade = conn.get_taskgroup_user_grade(&session_token, tg.id.unwrap()); // TODO: Unwrap?
......
......@@ -175,6 +175,8 @@ impl MedalObject<Connection> for Contest {
}
impl MedalConnection for Connection {
fn reconnect(config: &config::Config) -> Self { Self::reconnect_concrete(config) }
fn dbtype(&self) -> &'static str { "postgres" }
fn migration_already_applied(&self, name: &str) -> bool {
......@@ -308,16 +310,21 @@ impl MedalConnection for Connection {
SessionUser::minimal(id, session_token.to_owned(), csrf_token)
}
fn get_session_or_new(&self, key: &str) -> SessionUser {
let query = "UPDATE session
SET session_token = $1
WHERE session_token = $2";
self.get_session(&key).ensure_alive().unwrap_or_else(|| {
// TODO: Factor this out in own function
// TODO: Should a new session key be generated every time?
self.execute(query, &[&Option::<String>::None, &key]).unwrap();
self.new_session(&key)
})
fn get_session_or_new(&self, key: &str) -> Result<SessionUser, ()> {
fn disable_old_session_and_create_new(conn: &Connection, key: &str) -> Result<SessionUser, ()> {
let query = "UPDATE session
SET session_token = $1
WHERE session_token = $2";
// TODO: Should a new session key be generated every time?
conn.execute(query, &[&Option::<String>::None, &key]).map_err(|_| ())?;
Ok(conn.new_session(&key))
}
if let Some(session) = self.get_session(&key).ensure_alive() {
Ok(session)
} else {
disable_old_session_and_create_new(self, key)
}
}
fn get_user_by_id(&self, user_id: i32) -> Option<SessionUser> {
......@@ -571,7 +578,7 @@ impl MedalConnection for Connection {
fn signup(&self, session_token: &str, username: &str, email: &str, password_hash: String, salt: &str)
-> SignupResult {
let mut session_user = self.get_session_or_new(&session_token);
let mut session_user = self.get_session_or_new(&session_token).unwrap();
if session_user.is_logged_in() {
return SignupResult::UserLoggedIn;
......
......@@ -12,6 +12,7 @@
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see *
\* <http://www.gnu.org/licenses/>. */
use config;
use db_objects::*;
#[derive(Debug)]
......@@ -26,6 +27,8 @@ pub enum SignupResult {
/// This trait abstracts the database connection and provides function for all actions to be performed on the database
/// in the medal platform.
pub trait MedalConnection {
fn reconnect(&config::Config) -> Self;
fn dbtype(&self) -> &'static str;
fn migration_already_applied(&self, name: &str) -> bool;
......@@ -44,7 +47,9 @@ pub trait MedalConnection {
/// Saves the session data of `session` in the database.
fn save_session(&self, session: SessionUser);
/// Combination of [`get_session`](#tymethod.get_session) and [`new_session`](#tymethod.new_session).
fn get_session_or_new(&self, key: &str) -> SessionUser;
///
/// This method can still fail in case of database error in order to bubble them up to the webframework
fn get_session_or_new(&self, key: &str) -> Result<SessionUser, ()>;
/// Try to get session associated to the id `user_id`.
///
......
......@@ -16,6 +16,7 @@
extern crate postgres;
use config;
use postgres::Connection;
use time;
use time::Duration;
......@@ -34,6 +35,8 @@ trait Queryable {
where F: FnMut(postgres::rows::Row<'_>) -> T;
fn exists(&self, sql: &str, params: &[&dyn postgres::types::ToSql]) -> bool;
fn get_last_id(&self) -> Option<i32>;
fn reconnect_concrete(&config::Config) -> Self;
}
impl Queryable for Connection {
......@@ -62,6 +65,10 @@ impl Queryable for Connection {
})
}
// Empty line intended
fn reconnect_concrete(config: &config::Config) -> Self {
postgres::Connection::connect(config.database_url.clone().unwrap(), postgres::TlsMode::None).unwrap()
}
}
impl MedalObject<Connection> for Submission {
......
......@@ -29,6 +29,7 @@
extern crate postgres;
use config;
use postgres::Connection;
use time;
use time::Duration;
......@@ -47,6 +48,8 @@ trait Queryable {
where F: FnMut(postgres::rows::Row<'_>) -> T;
fn exists(&self, sql: &str, params: &[&dyn postgres::types::ToSql]) -> bool;
fn get_last_id(&self) -> Option<i32>;
fn reconnect_concrete(&config::Config) -> Self;
}
impl Queryable for Connection {
......@@ -75,6 +78,10 @@ impl Queryable for Connection {
})
}
// Empty line intended
fn reconnect_concrete(config: &config::Config) -> Self {
postgres::Connection::connect(config.database_url.clone().unwrap(), postgres::TlsMode::None).unwrap()
}
}
impl MedalObject<Connection> for Submission {
......@@ -287,6 +294,8 @@ impl MedalObject<Connection> for Contest {
}
impl MedalConnection for Connection {
fn reconnect(config: &config::Config) -> Self { Self::reconnect_concrete(config) }
fn dbtype(&self) -> &'static str { "postgres" }
fn migration_already_applied(&self, name: &str) -> bool {
......@@ -420,16 +429,21 @@ impl MedalConnection for Connection {
SessionUser::minimal(id, session_token.to_owned(), csrf_token)
}
fn get_session_or_new(&self, key: &str) -> SessionUser {
let query = "UPDATE session
SET session_token = $1
WHERE session_token = $2";
self.get_session(&key).ensure_alive().unwrap_or_else(|| {
// TODO: Factor this out in own function
// TODO: Should a new session key be generated every time?
self.execute(query, &[&Option::<String>::None, &key]).unwrap();
self.new_session(&key)
})
fn get_session_or_new(&self, key: &str) -> Result<SessionUser, ()> {
fn disable_old_session_and_create_new(conn: &Connection, key: &str) -> Result<SessionUser, ()> {
let query = "UPDATE session
SET session_token = $1
WHERE session_token = $2";
// TODO: Should a new session key be generated every time?
conn.execute(query, &[&Option::<String>::None, &key]).map_err(|_| ())?;
Ok(conn.new_session(&key))
}
if let Some(session) = self.get_session(&key).ensure_alive() {
Ok(session)
} else {
disable_old_session_and_create_new(self, key)
}
}
fn get_user_by_id(&self, user_id: i32) -> Option<SessionUser> {
......@@ -683,7 +697,7 @@ impl MedalConnection for Connection {
fn signup(&self, session_token: &str, username: &str, email: &str, password_hash: String, salt: &str)
-> SignupResult {
let mut session_user = self.get_session_or_new(&session_token);
let mut session_user = self.get_session_or_new(&session_token).unwrap();
if session_user.is_logged_in() {
return SignupResult::UserLoggedIn;
......
......@@ -16,6 +16,7 @@
extern crate rusqlite;
use config;
use rusqlite::Connection;
use time;
use time::Duration;
......@@ -34,6 +35,8 @@ trait Queryable {
where F: FnMut(&rusqlite::Row) -> T;
fn exists(&self, sql: &str, params: &[&dyn rusqlite::types::ToSql]) -> bool;
fn get_last_id(&self) -> Option<i32>;
fn reconnect_concrete(&config::Config) -> Self;
}
impl Queryable for Connection {
......@@ -62,6 +65,10 @@ impl Queryable for Connection {
}
fn get_last_id(&self) -> Option<i32> { self.query_row("SELECT last_insert_rowid()", &[], |row| row.get(0)).ok() }
fn reconnect_concrete(config: &config::Config) -> Self {
rusqlite::Connection::open(config.database_file.clone().unwrap()).unwrap()
}
}
impl MedalObject<Connection> for Submission {
......
......@@ -29,6 +29,7 @@
extern crate rusqlite;
use config;
use rusqlite::Connection;
use time;
use time::Duration;
......@@ -47,6 +48,8 @@ trait Queryable {
where F: FnMut(&rusqlite::Row) -> T;
fn exists(&self, sql: &str, params: &[&dyn rusqlite::types::ToSql]) -> bool;
fn get_last_id(&self) -> Option<i32>;
fn reconnect_concrete(&config::Config) -> Self;
}
impl Queryable for Connection {
......@@ -75,6 +78,10 @@ impl Queryable for Connection {
}
fn get_last_id(&self) -> Option<i32> { self.query_row("SELECT last_insert_rowid()", &[], |row| row.get(0)).ok() }
fn reconnect_concrete(config: &config::Config) -> Self {
rusqlite::Connection::open(config.database_file.clone().unwrap()).unwrap()
}
}
impl MedalObject<Connection> for Submission {
......@@ -287,6 +294,8 @@ impl MedalObject<Connection> for Contest {
}
impl MedalConnection for Connection {
fn reconnect(config: &config::Config) -> Self { Self::reconnect_concrete(config) }
fn dbtype(&self) -> &'static str { "sqlite_v2" }
fn migration_already_applied(&self, name: &str) -> bool {
......@@ -420,16 +429,21 @@ impl MedalConnection for Connection {
SessionUser::minimal(id, session_token.to_owned(), csrf_token)
}
fn get_session_or_new(&self, key: &str) -> SessionUser {
let query = "UPDATE session
SET session_token = ?1
WHERE session_token = ?2";
self.get_session(&key).ensure_alive().unwrap_or_else(|| {
// TODO: Factor this out in own function
// TODO: Should a new session key be generated every time?
self.execute(query, &[&Option::<String>::None, &key]).unwrap();
self.new_session(&key)
})
fn get_session_or_new(&self, key: &str) -> Result<SessionUser, ()> {
fn disable_old_session_and_create_new(conn: &Connection, key: &str) -> Result<SessionUser, ()> {
let query = "UPDATE session
SET session_token = ?1
WHERE session_token = ?2";
// TODO: Should a new session key be generated every time?
conn.execute(query, &[&Option::<String>::None, &key]).map_err(|_| ())?;
Ok(conn.new_session(&key))
}
if let Some(session) = self.get_session(&key).ensure_alive() {
Ok(session)
} else {
disable_old_session_and_create_new(self, key)
}
}
fn get_user_by_id(&self, user_id: i32) -> Option<SessionUser> {
......@@ -683,7 +697,7 @@ impl MedalConnection for Connection {
fn signup(&self, session_token: &str, username: &str, email: &str, password_hash: String, salt: &str)
-> SignupResult {
let mut session_user = self.get_session_or_new(&session_token);
let mut session_user = self.get_session_or_new(&session_token).unwrap();
if session_user.is_logged_in() {
return SignupResult::UserLoggedIn;
......
......@@ -272,6 +272,10 @@ impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
IronError { error: Box::new(SessionError { message: "Database Error".to_string() }),
response: Response::with(status::InternalServerError) }
}
core::MedalError::DatabaseConnectionError => {
IronError { error: Box::new(SessionError { message: "Database Connection Error".to_string() }),
response: Response::with(status::InternalServerError) }
}
core::MedalError::PasswordHashingError => {
IronError { error: Box::new(SessionError { message: "Error hashing the passwords".to_string() }),
response: Response::with(status::InternalServerError) }
......@@ -393,7 +397,21 @@ fn contests<C>(req: &mut Request) -> IronResult<Response>
};
let config = req.get::<Read<SharedConfiguration>>().unwrap();
let (template, mut data) = with_conn![core::show_contests, C, req, &session_token, login_info(&config), visibility];
let res = with_conn![core::show_contests, C, req, &session_token, login_info(&config), visibility];
if res.is_err() {
// Database connection failed … Create a new database connection!
// TODO: This code should be unified with the database creation code in main.rs
println!("DATABASE CONNECTION LOST! Restarting database connection.");
let conn = C::reconnect(&config);
let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
let mut sharedconn = mutex.lock().unwrap_or_else(|e| e.into_inner());
*sharedconn = conn;
// return ServerError();
}
let (template, mut data) = res.unwrap();
data.insert("config".to_string(), to_json(&config.template_params));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment