headless_lms_server/programs/
regrader.rs

1use std::{env, error::Error, sync::Arc, time::Duration};
2
3use crate::domain::models_requests::{self, JwtKey};
4use headless_lms_models as models;
5use models::library::regrading;
6use sqlx::PgPool;
7
8/**
9Starts a thread that will periodically send regrading submissions to the corresponding exercise services for regrading.
10*/
11pub async fn main() -> anyhow::Result<()> {
12    // TODO: Audit that the environment access only happens in single-threaded code.
13    unsafe { env::set_var("RUST_LOG", "info,actix_web=info,sqlx=warn") };
14    dotenv::dotenv().ok();
15    crate::setup_tracing()?;
16    let db_url = env::var("DATABASE_URL")
17        .unwrap_or_else(|_| "postgres://localhost/headless_lms_dev".to_string());
18    let jwt_key = Arc::new(JwtKey::try_from_env().expect("Could not initialise JwtKey"));
19
20    let mut interval = tokio::time::interval(Duration::from_secs(10));
21    let mut ticks = 60;
22
23    // Since this is repeating every 10 seconds we can keep the connection open.
24    let db_pool = PgPool::connect(&db_url).await?;
25    let mut conn = db_pool.acquire().await?;
26    loop {
27        interval.tick().await;
28
29        ticks += 1;
30        // 60 10 second intervals = 10 minutes
31        if ticks > 60 {
32            // occasionally prints a reminder that the service is still running
33            ticks = 0;
34            tracing::info!("running the regrader");
35        }
36
37        let exercise_services_by_type =
38            models::exercise_service_info::get_upsert_all_exercise_services_by_type(
39                &mut conn,
40                models_requests::fetch_service_info,
41            )
42            .await?;
43        // do not stop the thread on error, report it and try again next tick
44        if let Err(err) = regrading::regrade(
45            &mut conn,
46            &exercise_services_by_type,
47            models_requests::make_grading_request_sender(Arc::clone(&jwt_key)),
48        )
49        .await
50        {
51            tracing::error!("Error in regrader: {}", err);
52
53            if let Some(sqlx::Error::Io(..)) =
54                err.source().and_then(|s| s.downcast_ref::<sqlx::Error>())
55            {
56                // this usually happens if the database is reset while running bin/dev etc.
57                tracing::info!(
58                    "regrader may have lost its connection to the db, trying to reconnect"
59                );
60                conn = db_pool.acquire().await?;
61            }
62        }
63    }
64}