Refactor so that no static is necessary for the maps cache
* Replace the lazy `once_cell` by a maps handle type * Use Rocket's managed state to manage a handle * Ensure that the handlers have access to it * Pass another handle to the maps updater loop * Try to keep the lock as short as possible Still, long downloads block the lock. Add a FIXME to refactor this so the lock is only taken when updating the maps fields.
This commit is contained in:
parent
72fe9577bd
commit
9b9b1a5f77
|
@ -1942,7 +1942,6 @@ dependencies = [
|
|||
"color-eyre",
|
||||
"geocoding",
|
||||
"image",
|
||||
"once_cell",
|
||||
"reqwest 0.11.9",
|
||||
"rocket",
|
||||
]
|
||||
|
|
|
@ -8,6 +8,5 @@ chrono = "0.4.19"
|
|||
color-eyre = "0.5.6"
|
||||
geocoding = "0.3.1"
|
||||
image = "0.24.0"
|
||||
once_cell = "1.9.0"
|
||||
reqwest = "0.11.9"
|
||||
rocket = { version = "0.5.0-rc.1", features = ["json"] }
|
||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -11,21 +11,22 @@
|
|||
)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::Result;
|
||||
use geocoding::{Forward, Openstreetmap, Point};
|
||||
use once_cell::sync::Lazy;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::serde::Serialize;
|
||||
use rocket::tokio::sync::Mutex;
|
||||
use rocket::tokio::{self, select};
|
||||
use rocket::{get, routes, FromFormField};
|
||||
use rocket::{get, routes, FromFormField, State};
|
||||
|
||||
use self::maps::Maps;
|
||||
|
||||
mod maps;
|
||||
|
||||
/// Global maps cache refreshed by a separate task.
|
||||
static MAPS: Lazy<Mutex<Maps>> = Lazy::new(|| Mutex::new(Maps::new()));
|
||||
/// A handle to access the in-memory cached maps.
|
||||
type MapsHandle = Arc<Mutex<Maps>>;
|
||||
|
||||
/// The current for a specific location.
|
||||
///
|
||||
|
@ -129,7 +130,12 @@ impl Metric {
|
|||
/// Calculates and returns the forecast.
|
||||
///
|
||||
/// The provided list `metrics` determines what will be included in the forecast.
|
||||
async fn forecast(lat: f64, lon: f64, metrics: Vec<Metric>) -> Forecast {
|
||||
async fn forecast(
|
||||
lat: f64,
|
||||
lon: f64,
|
||||
metrics: Vec<Metric>,
|
||||
_maps_handle: &State<MapsHandle>,
|
||||
) -> Forecast {
|
||||
let mut forecast = Forecast::new(lat, lon);
|
||||
|
||||
// Expand the `All` metric if present, deduplicate otherwise.
|
||||
|
@ -173,17 +179,26 @@ async fn address_position(address: String) -> Option<(f64, f64)> {
|
|||
|
||||
/// Handler for retrieving the forecast for an address.
|
||||
#[get("/forecast?<address>&<metrics>")]
|
||||
async fn forecast_address(address: String, metrics: Vec<Metric>) -> Option<Json<Forecast>> {
|
||||
async fn forecast_address(
|
||||
address: String,
|
||||
metrics: Vec<Metric>,
|
||||
maps_handle: &State<MapsHandle>,
|
||||
) -> Option<Json<Forecast>> {
|
||||
let (lat, lon) = address_position(address).await?;
|
||||
let forecast = forecast(lat, lon, metrics).await;
|
||||
let forecast = forecast(lat, lon, metrics, maps_handle).await;
|
||||
|
||||
Some(Json(forecast))
|
||||
}
|
||||
|
||||
/// Handler for retrieving the forecast for a geocoded position.
|
||||
#[get("/forecast?<lat>&<lon>&<metrics>", rank = 2)]
|
||||
async fn forecast_geo(lat: f64, lon: f64, metrics: Vec<Metric>) -> Json<Forecast> {
|
||||
let forecast = forecast(lat, lon, metrics).await;
|
||||
async fn forecast_geo(
|
||||
lat: f64,
|
||||
lon: f64,
|
||||
metrics: Vec<Metric>,
|
||||
maps_handle: &State<MapsHandle>,
|
||||
) -> Json<Forecast> {
|
||||
let forecast = forecast(lat, lon, metrics, maps_handle).await;
|
||||
|
||||
Json(forecast)
|
||||
}
|
||||
|
@ -193,14 +208,17 @@ async fn forecast_geo(lat: f64, lon: f64, metrics: Vec<Metric>) -> Json<Forecast
|
|||
async fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
let maps = Maps::new();
|
||||
let maps_handle = Arc::new(Mutex::new(maps));
|
||||
let maps_updater = tokio::spawn(maps::run(Arc::clone(&maps_handle)));
|
||||
|
||||
let rocket = rocket::build()
|
||||
.manage(maps_handle)
|
||||
.mount("/", routes![forecast_address, forecast_geo])
|
||||
.ignite()
|
||||
.await?;
|
||||
let shutdown = rocket.shutdown();
|
||||
|
||||
let maps_updater = tokio::spawn(maps::run());
|
||||
|
||||
select! {
|
||||
result = rocket.launch() => {
|
||||
result?
|
||||
|
|
14
src/maps.rs
14
src/maps.rs
|
@ -7,7 +7,7 @@ use chrono::DurationRound;
|
|||
use image::DynamicImage;
|
||||
use rocket::tokio::time::{sleep, Duration, Instant};
|
||||
|
||||
use crate::MAPS;
|
||||
use crate::MapsHandle;
|
||||
|
||||
/// The interval between map refreshes (in seconds).
|
||||
const SLEEP_INTERVAL: Duration = Duration::from_secs(60);
|
||||
|
@ -37,14 +37,14 @@ const UVI_BASE_URL: &str = "https://image.buienradar.nl/2.0/image/sprite/Weather
|
|||
const UVI_INTERVAL: Duration = Duration::from_secs(3600);
|
||||
|
||||
/// Runs a loop that keeps refreshing the maps when necessary.
|
||||
pub(crate) async fn run() -> ! {
|
||||
pub(crate) async fn run(maps_handle: MapsHandle) -> ! {
|
||||
loop {
|
||||
let mut maps = MAPS.lock().await;
|
||||
|
||||
println!("🕔 Refreshing the maps (if necessary)...");
|
||||
maps.refresh_precipitation().await;
|
||||
maps.refresh_pollen().await;
|
||||
maps.refresh_uvi().await;
|
||||
|
||||
// FIXME: Refactor this so that the lock is only held when updating the maps fields.
|
||||
maps_handle.lock().await.refresh_precipitation().await;
|
||||
maps_handle.lock().await.refresh_pollen().await;
|
||||
maps_handle.lock().await.refresh_uvi().await;
|
||||
|
||||
sleep(SLEEP_INTERVAL).await;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue