Skip to main content

zagens_tools/
resource_locks.rs

1//! Fine-grained resource lock targets for DAG scheduling (kernel-v2 M4).
2
3use std::collections::HashSet;
4
5use crate::dag_scheduler::ScheduleResource;
6
7/// Shared vs exclusive lock for one scheduling resource slot.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub enum ResourceLockMode {
10    Shared,
11    Exclusive,
12}
13
14/// Stable sort key for deadlock-free lock acquisition order.
15#[must_use]
16pub fn resource_lock_order(resource: &ScheduleResource) -> (u8, String) {
17    match resource {
18        ScheduleResource::WorkspaceScan => (0, String::new()),
19        ScheduleResource::Path(path) => (1, path.clone()),
20        ScheduleResource::WorkspaceWrite => (2, String::new()),
21    }
22}
23
24/// Resources to lock before executing one tool plan (writes → exclusive, reads → shared).
25#[must_use]
26pub fn resource_lock_targets(
27    reads: &HashSet<ScheduleResource>,
28    writes: &HashSet<ScheduleResource>,
29) -> Vec<(ScheduleResource, ResourceLockMode)> {
30    let mut out: Vec<(ScheduleResource, ResourceLockMode)> = writes
31        .iter()
32        .map(|resource| (resource.clone(), ResourceLockMode::Exclusive))
33        .collect();
34    for resource in reads {
35        if !writes.contains(resource) {
36            out.push((resource.clone(), ResourceLockMode::Shared));
37        }
38    }
39    out.sort_by(|a, b| {
40        resource_lock_order(&a.0)
41            .cmp(&resource_lock_order(&b.0))
42            .then_with(|| a.1.cmp(&b.1))
43    });
44    out
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn write_paths_are_exclusive_reads_shared() {
53        let reads = HashSet::from([
54            ScheduleResource::Path("a".to_string()),
55            ScheduleResource::WorkspaceScan,
56        ]);
57        let writes = HashSet::from([ScheduleResource::Path("a".to_string())]);
58        let targets = resource_lock_targets(&reads, &writes);
59        assert_eq!(targets.len(), 2);
60        assert!(
61            targets
62                .iter()
63                .any(|(r, m)| r == &ScheduleResource::Path("a".to_string())
64                    && *m == ResourceLockMode::Exclusive)
65        );
66        assert!(
67            targets
68                .iter()
69                .any(|(r, m)| r == &ScheduleResource::WorkspaceScan
70                    && *m == ResourceLockMode::Shared)
71        );
72    }
73
74    #[test]
75    fn distinct_read_paths_yield_two_shared_targets() {
76        let reads = HashSet::from([
77            ScheduleResource::Path("a".to_string()),
78            ScheduleResource::Path("b".to_string()),
79        ]);
80        let targets = resource_lock_targets(&reads, &HashSet::new());
81        assert_eq!(targets.len(), 2);
82        assert!(targets.iter().all(|(_, m)| *m == ResourceLockMode::Shared));
83    }
84}