This commit is contained in:
Mateusz Gruszczyński
2026-09-01 10:20:19 +02:00
parent 1a5c1305dc
commit 3479ed750d
34 changed files with 575 additions and 113 deletions
+33
View File
@@ -1,8 +1,27 @@
async fn lock_poll_zone_operations(state: &AppState, device_id: &str) -> Result<Vec<tokio::sync::OwnedMutexGuard<()>>, AppError> {
let mut zone_ids: Vec<String> = state.db.list_zones()?.into_iter()
.filter(|zone| zone.device_id == device_id)
.map(|zone| zone.id)
.collect();
zone_ids.sort();
zone_ids.dedup();
let mut guards = Vec::with_capacity(zone_ids.len());
for zone_id in zone_ids {
guards.push(state.lock_zone_operation(&zone_id).await);
}
Ok(guards)
}
pub async fn poll_one(state: &AppState, device_id: &str) -> Result<Device, AppError> {
// Polling can update zone ownership when it detects physical/pilot control. Acquire
// the same zone -> device lock order used by thermostat/manual actions so those writes
// cannot race and overwrite a fresh override or hand-back state.
let _zone_guards = lock_poll_zone_operations(state, device_id).await?;
let _device_guard = state.lock_device_operation(device_id).await;
poll_one_locked(state, device_id).await
}
// Caller must hold the device lock and every current zone lock associated with this device.
async fn poll_one_locked(state: &AppState, device_id: &str) -> Result<Device, AppError> {
let mut device = state.db.get_device(device_id)?
.ok_or_else(|| AppError::NotFound(format!("device {device_id}")))?;
@@ -26,12 +45,26 @@ pub(crate) async fn poll_all(state: &AppState) -> Result<()> {
.map(|device| device.id)
.collect();
for device_id in device_ids {
let _zone_guards = lock_poll_zone_operations(state, &device_id).await?;
let _device_guard = state.lock_device_operation(&device_id).await;
let _ = poll_one_locked(state, &device_id).await?;
}
Ok(())
}
/// Poll all enabled devices when the caller already holds the corresponding zone and device locks.
/// Used by configuration import so no command/poll can interleave with the replacement.
pub(crate) async fn poll_all_locked(state: &AppState) -> Result<()> {
let device_ids: Vec<String> = state.db.list_devices()?.into_iter()
.filter(|device| device.enabled)
.map(|device| device.id)
.collect();
for device_id in device_ids {
let _ = poll_one_locked(state, &device_id).await?;
}
Ok(())
}
async fn poll_device(state: &AppState, device: &mut Device) {
if device.simulated {
simulate_tick(device);