atagen

roundabout

public
Star0
Watch0
9117440444eb609ecea8476b5a62b03917077591
1
ssh://brewery@pls.lobotomise.me:33/atagen/roundabout.git

Blame: src/daemon.rs

6f5b84fad3efatagen2026-09-27 03:59 UTC1
use std::{
6f5b84fad3efatagen2026-09-27 03:59 UTC2
    collections::BTreeMap,
6f5b84fad3efatagen2026-09-27 03:59 UTC3
    fs, io,
61c6e94355d9atagen2026-09-27 03:59 UTC4
    os::{
61c6e94355d9atagen2026-09-27 03:59 UTC5
        fd::FromRawFd,
61c6e94355d9atagen2026-09-27 03:59 UTC6
        unix::net::{UnixListener, UnixStream},
61c6e94355d9atagen2026-09-27 03:59 UTC7
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC8
    path::PathBuf,
6f5b84fad3efatagen2026-09-27 03:59 UTC9
    process,
61c6e94355d9atagen2026-09-27 03:59 UTC10
    sync::{
61c6e94355d9atagen2026-09-27 03:59 UTC11
        Arc, Mutex, MutexGuard,
61c6e94355d9atagen2026-09-27 03:59 UTC12
        atomic::{AtomicBool, Ordering},
61c6e94355d9atagen2026-09-27 03:59 UTC13
    },
6f5b84fad3efatagen2026-09-27 03:59 UTC14
    thread,
6f5b84fad3efatagen2026-09-27 03:59 UTC15
    time::{Duration, Instant},
6f5b84fad3efatagen2026-09-27 03:59 UTC16
};
6f5b84fad3efatagen2026-09-27 03:59 UTC17
6f5b84fad3efatagen2026-09-27 03:59 UTC18
use crate::{
6f5b84fad3efatagen2026-09-27 03:59 UTC19
    client,
6f5b84fad3efatagen2026-09-27 03:59 UTC20
    config::Config,
6f5b84fad3efatagen2026-09-27 03:59 UTC21
    paths,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC22
    proto::{self, Acquire, Event, IntentInfo, LeaseId, LeaseInfo, Reader, Request, Status},
6f5b84fad3efatagen2026-09-27 03:59 UTC23
    sched::{self, Capacity, Disk, FsId, Need, Queued, Reason, Running, Verdict},
6f5b84fad3efatagen2026-09-27 03:59 UTC24
    sys,
6f5b84fad3efatagen2026-09-27 03:59 UTC25
    units::Bytes,
6f5b84fad3efatagen2026-09-27 03:59 UTC26
};
6f5b84fad3efatagen2026-09-27 03:59 UTC27
6f5b84fad3efatagen2026-09-27 03:59 UTC28
struct Run {
6f5b84fad3efatagen2026-09-27 03:59 UTC29
    started: Instant,
6f5b84fad3efatagen2026-09-27 03:59 UTC30
    rss: Bytes,
6f5b84fad3efatagen2026-09-27 03:59 UTC31
    reminded: Option<Instant>,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC32
    /// asked-for plus bonus. only the asked-for part counts for admission,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC33
    /// oversubscribed cores just make everyone slower
ecc1cf8df0f5atagen2026-09-27 13:36 UTC34
    cores: u32,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC35
}
ecc1cf8df0f5atagen2026-09-27 13:36 UTC36
ecc1cf8df0f5atagen2026-09-27 13:36 UTC37
struct Intent {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC38
    need: Need,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC39
    until: Instant,
6f5b84fad3efatagen2026-09-27 03:59 UTC40
}
6f5b84fad3efatagen2026-09-27 03:59 UTC41
6f5b84fad3efatagen2026-09-27 03:59 UTC42
struct Lease {
6f5b84fad3efatagen2026-09-27 03:59 UTC43
    req: Acquire,
6f5b84fad3efatagen2026-09-27 03:59 UTC44
    heavy: bool,
6f5b84fad3efatagen2026-09-27 03:59 UTC45
    fs: Vec<FsId>,
6f5b84fad3efatagen2026-09-27 03:59 UTC46
    queued_at: Instant,
6f5b84fad3efatagen2026-09-27 03:59 UTC47
    run: Option<Run>,
6f5b84fad3efatagen2026-09-27 03:59 UTC48
    out: UnixStream,
6f5b84fad3efatagen2026-09-27 03:59 UTC49
    waiting: Option<Reason>,
6f5b84fad3efatagen2026-09-27 03:59 UTC50
    last_pos: usize,
6f5b84fad3efatagen2026-09-27 03:59 UTC51
    watchers: Vec<UnixStream>,
6f5b84fad3efatagen2026-09-27 03:59 UTC52
}
6f5b84fad3efatagen2026-09-27 03:59 UTC53
6f5b84fad3efatagen2026-09-27 03:59 UTC54
impl Lease {
6f5b84fad3efatagen2026-09-27 03:59 UTC55
    fn elapsed(&self) -> Duration {
6f5b84fad3efatagen2026-09-27 03:59 UTC56
        self.run
6f5b84fad3efatagen2026-09-27 03:59 UTC57
            .as_ref()
6f5b84fad3efatagen2026-09-27 03:59 UTC58
            .map_or(self.queued_at, |r| r.started)
6f5b84fad3efatagen2026-09-27 03:59 UTC59
            .elapsed()
6f5b84fad3efatagen2026-09-27 03:59 UTC60
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC61
6f5b84fad3efatagen2026-09-27 03:59 UTC62
    fn overdue(&self, grace: f64) -> bool {
6f5b84fad3efatagen2026-09-27 03:59 UTC63
        self.run.is_some() && self.elapsed() > self.req.need.time.mul_f64(grace)
6f5b84fad3efatagen2026-09-27 03:59 UTC64
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC65
}
6f5b84fad3efatagen2026-09-27 03:59 UTC66
6f5b84fad3efatagen2026-09-27 03:59 UTC67
struct State {
6f5b84fad3efatagen2026-09-27 03:59 UTC68
    cfg: Config,
6f5b84fad3efatagen2026-09-27 03:59 UTC69
    next: LeaseId,
6f5b84fad3efatagen2026-09-27 03:59 UTC70
    leases: BTreeMap<LeaseId, Lease>,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC71
    intents: BTreeMap<String, Intent>,
6f5b84fad3efatagen2026-09-27 03:59 UTC72
    cap: Capacity,
6f5b84fad3efatagen2026-09-27 03:59 UTC73
    baselines: BTreeMap<FsId, Bytes>,
6f5b84fad3efatagen2026-09-27 03:59 UTC74
    conns: usize,
6f5b84fad3efatagen2026-09-27 03:59 UTC75
    idle_since: Instant,
6f5b84fad3efatagen2026-09-27 03:59 UTC76
}
6f5b84fad3efatagen2026-09-27 03:59 UTC77
6f5b84fad3efatagen2026-09-27 03:59 UTC78
type Shared = Arc<Mutex<State>>;
6f5b84fad3efatagen2026-09-27 03:59 UTC79
6f5b84fad3efatagen2026-09-27 03:59 UTC80
fn lock(shared: &Shared) -> MutexGuard<'_, State> {
6f5b84fad3efatagen2026-09-27 03:59 UTC81
    shared
6f5b84fad3efatagen2026-09-27 03:59 UTC82
        .lock()
6f5b84fad3efatagen2026-09-27 03:59 UTC83
        .unwrap_or_else(std::sync::PoisonError::into_inner)
6f5b84fad3efatagen2026-09-27 03:59 UTC84
}
6f5b84fad3efatagen2026-09-27 03:59 UTC85
6f5b84fad3efatagen2026-09-27 03:59 UTC86
impl State {
6f5b84fad3efatagen2026-09-27 03:59 UTC87
    fn new(cfg: Config) -> Self {
56a853ebf254atagen2026-09-27 03:59 UTC88
        let mut st = Self {
6f5b84fad3efatagen2026-09-27 03:59 UTC89
            cfg,
6f5b84fad3efatagen2026-09-27 03:59 UTC90
            next: 1,
6f5b84fad3efatagen2026-09-27 03:59 UTC91
            leases: BTreeMap::new(),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC92
            intents: BTreeMap::new(),
56a853ebf254atagen2026-09-27 03:59 UTC93
            cap: Capacity {
56a853ebf254atagen2026-09-27 03:59 UTC94
                ram_budget: Bytes::ZERO,
56a853ebf254atagen2026-09-27 03:59 UTC95
                ram_live: Bytes::ZERO,
56a853ebf254atagen2026-09-27 03:59 UTC96
                ram_reserve: Bytes::ZERO,
56a853ebf254atagen2026-09-27 03:59 UTC97
                cores: 1,
56a853ebf254atagen2026-09-27 03:59 UTC98
                disks: BTreeMap::new(),
56a853ebf254atagen2026-09-27 03:59 UTC99
                pressure: false,
56a853ebf254atagen2026-09-27 03:59 UTC100
            },
6f5b84fad3efatagen2026-09-27 03:59 UTC101
            baselines: BTreeMap::new(),
6f5b84fad3efatagen2026-09-27 03:59 UTC102
            conns: 0,
6f5b84fad3efatagen2026-09-27 03:59 UTC103
            idle_since: Instant::now(),
56a853ebf254atagen2026-09-27 03:59 UTC104
        };
56a853ebf254atagen2026-09-27 03:59 UTC105
        st.apply_budget();
56a853ebf254atagen2026-09-27 03:59 UTC106
        st
56a853ebf254atagen2026-09-27 03:59 UTC107
    }
56a853ebf254atagen2026-09-27 03:59 UTC108
56a853ebf254atagen2026-09-27 03:59 UTC109
    fn apply_budget(&mut self) {
56a853ebf254atagen2026-09-27 03:59 UTC110
        let (total, live) = sys::memory();
56a853ebf254atagen2026-09-27 03:59 UTC111
        self.cap.ram_budget = self.cfg.budget.ram.resolve(total);
56a853ebf254atagen2026-09-27 03:59 UTC112
        self.cap.ram_live = live;
56a853ebf254atagen2026-09-27 03:59 UTC113
        self.cap.ram_reserve = self.cfg.budget.ram_reserve;
56a853ebf254atagen2026-09-27 03:59 UTC114
        self.cap.cores = self.cfg.budget.cores.resolve(sys::cores());
56a853ebf254atagen2026-09-27 03:59 UTC115
    }
56a853ebf254atagen2026-09-27 03:59 UTC116
56a853ebf254atagen2026-09-27 03:59 UTC117
    /// running leases keep the heaviness they started with, so a reload can't
56a853ebf254atagen2026-09-27 03:59 UTC118
    /// retroactively put two heavies side by side
56a853ebf254atagen2026-09-27 03:59 UTC119
    fn reload(&mut self, cfg: Config) {
56a853ebf254atagen2026-09-27 03:59 UTC120
        self.cfg = cfg;
56a853ebf254atagen2026-09-27 03:59 UTC121
        self.apply_budget();
56a853ebf254atagen2026-09-27 03:59 UTC122
        let queued: Vec<(LeaseId, Need)> = self
56a853ebf254atagen2026-09-27 03:59 UTC123
            .leases
56a853ebf254atagen2026-09-27 03:59 UTC124
            .iter()
56a853ebf254atagen2026-09-27 03:59 UTC125
            .filter(|(_, l)| l.run.is_none())
56a853ebf254atagen2026-09-27 03:59 UTC126
            .map(|(id, l)| (*id, l.req.need))
56a853ebf254atagen2026-09-27 03:59 UTC127
            .collect();
56a853ebf254atagen2026-09-27 03:59 UTC128
        for (id, need) in queued {
56a853ebf254atagen2026-09-27 03:59 UTC129
            let heavy = self.heavy(&need);
56a853ebf254atagen2026-09-27 03:59 UTC130
            if let Some(l) = self.leases.get_mut(&id) {
56a853ebf254atagen2026-09-27 03:59 UTC131
                l.heavy = heavy;
56a853ebf254atagen2026-09-27 03:59 UTC132
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC133
        }
56a853ebf254atagen2026-09-27 03:59 UTC134
        self.refresh();
56a853ebf254atagen2026-09-27 03:59 UTC135
        self.reschedule();
6f5b84fad3efatagen2026-09-27 03:59 UTC136
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC137
6f5b84fad3efatagen2026-09-27 03:59 UTC138
    fn heavy(&self, need: &Need) -> bool {
6f5b84fad3efatagen2026-09-27 03:59 UTC139
        let h = &self.cfg.heavy;
6f5b84fad3efatagen2026-09-27 03:59 UTC140
        need.ram >= h.ram || need.disk >= h.disk || need.cores >= h.cores.resolve(sys::cores())
6f5b84fad3efatagen2026-09-27 03:59 UTC141
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC142
6f5b84fad3efatagen2026-09-27 03:59 UTC143
    /// capped at a tenth of the fs, or a 16G tmpfs /tmp with a 20G reserve
6f5b84fad3efatagen2026-09-27 03:59 UTC144
    /// admits nothing ever
6f5b84fad3efatagen2026-09-27 03:59 UTC145
    fn reserve(&self, size: Bytes) -> Bytes {
6f5b84fad3efatagen2026-09-27 03:59 UTC146
        self.cfg.budget.disk_reserve.min(Bytes(size.0 / 10))
6f5b84fad3efatagen2026-09-27 03:59 UTC147
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC148
6f5b84fad3efatagen2026-09-27 03:59 UTC149
    fn fs_for(&mut self, cwd: &PathBuf) -> Vec<FsId> {
6f5b84fad3efatagen2026-09-27 03:59 UTC150
        let mut ids = Vec::new();
6f5b84fad3efatagen2026-09-27 03:59 UTC151
        for path in std::iter::once(cwd).chain(&self.cfg.budget.disk_paths) {
6f5b84fad3efatagen2026-09-27 03:59 UTC152
            if let Some(fs) = sys::fs(path) {
6f5b84fad3efatagen2026-09-27 03:59 UTC153
                if !ids.contains(&fs.id) {
6f5b84fad3efatagen2026-09-27 03:59 UTC154
                    ids.push(fs.id);
6f5b84fad3efatagen2026-09-27 03:59 UTC155
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC156
                let reserve = self.reserve(fs.size);
6f5b84fad3efatagen2026-09-27 03:59 UTC157
                self.cap.disks.entry(fs.id).or_insert(Disk {
6f5b84fad3efatagen2026-09-27 03:59 UTC158
                    free: fs.free,
6f5b84fad3efatagen2026-09-27 03:59 UTC159
                    reserve,
6f5b84fad3efatagen2026-09-27 03:59 UTC160
                    consumed: Bytes::ZERO,
6f5b84fad3efatagen2026-09-27 03:59 UTC161
                });
6f5b84fad3efatagen2026-09-27 03:59 UTC162
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC163
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC164
        ids
6f5b84fad3efatagen2026-09-27 03:59 UTC165
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC166
6f5b84fad3efatagen2026-09-27 03:59 UTC167
    fn refresh(&mut self) {
6f5b84fad3efatagen2026-09-27 03:59 UTC168
        let (_, live) = sys::memory();
6f5b84fad3efatagen2026-09-27 03:59 UTC169
        self.cap.ram_live = live;
6f5b84fad3efatagen2026-09-27 03:59 UTC170
        self.cap.pressure = self
6f5b84fad3efatagen2026-09-27 03:59 UTC171
            .cfg
6f5b84fad3efatagen2026-09-27 03:59 UTC172
            .budget
6f5b84fad3efatagen2026-09-27 03:59 UTC173
            .psi_max
6f5b84fad3efatagen2026-09-27 03:59 UTC174
            .is_some_and(|max| sys::pressure().is_some_and(|p| p > max));
6f5b84fad3efatagen2026-09-27 03:59 UTC175
6f5b84fad3efatagen2026-09-27 03:59 UTC176
        let mut paths: Vec<PathBuf> = self.leases.values().map(|l| l.req.cwd.clone()).collect();
6f5b84fad3efatagen2026-09-27 03:59 UTC177
        paths.extend(self.cfg.budget.disk_paths.iter().cloned());
6f5b84fad3efatagen2026-09-27 03:59 UTC178
        let mut disks = BTreeMap::new();
6f5b84fad3efatagen2026-09-27 03:59 UTC179
        for path in paths {
6f5b84fad3efatagen2026-09-27 03:59 UTC180
            if let Some(fs) = sys::fs(&path) {
6f5b84fad3efatagen2026-09-27 03:59 UTC181
                disks.insert(fs.id, (fs.free, self.reserve(fs.size)));
6f5b84fad3efatagen2026-09-27 03:59 UTC182
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC183
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC184
6f5b84fad3efatagen2026-09-27 03:59 UTC185
        let busy: Vec<FsId> = self
6f5b84fad3efatagen2026-09-27 03:59 UTC186
            .leases
6f5b84fad3efatagen2026-09-27 03:59 UTC187
            .values()
6f5b84fad3efatagen2026-09-27 03:59 UTC188
            .filter(|l| l.run.is_some())
6f5b84fad3efatagen2026-09-27 03:59 UTC189
            .flat_map(|l| l.fs.iter().copied())
6f5b84fad3efatagen2026-09-27 03:59 UTC190
            .collect();
6f5b84fad3efatagen2026-09-27 03:59 UTC191
        self.baselines.retain(|id, _| busy.contains(id));
6f5b84fad3efatagen2026-09-27 03:59 UTC192
        self.cap.disks = disks
6f5b84fad3efatagen2026-09-27 03:59 UTC193
            .into_iter()
6f5b84fad3efatagen2026-09-27 03:59 UTC194
            .map(|(id, (free, reserve))| {
6f5b84fad3efatagen2026-09-27 03:59 UTC195
                let consumed = if busy.contains(&id) {
6f5b84fad3efatagen2026-09-27 03:59 UTC196
                    self.baselines
6f5b84fad3efatagen2026-09-27 03:59 UTC197
                        .entry(id)
6f5b84fad3efatagen2026-09-27 03:59 UTC198
                        .or_insert(free)
6f5b84fad3efatagen2026-09-27 03:59 UTC199
                        .saturating_sub(free)
6f5b84fad3efatagen2026-09-27 03:59 UTC200
                } else {
6f5b84fad3efatagen2026-09-27 03:59 UTC201
                    Bytes::ZERO
6f5b84fad3efatagen2026-09-27 03:59 UTC202
                };
6f5b84fad3efatagen2026-09-27 03:59 UTC203
                (
6f5b84fad3efatagen2026-09-27 03:59 UTC204
                    id,
6f5b84fad3efatagen2026-09-27 03:59 UTC205
                    Disk {
6f5b84fad3efatagen2026-09-27 03:59 UTC206
                        free,
6f5b84fad3efatagen2026-09-27 03:59 UTC207
                        reserve,
6f5b84fad3efatagen2026-09-27 03:59 UTC208
                        consumed,
6f5b84fad3efatagen2026-09-27 03:59 UTC209
                    },
6f5b84fad3efatagen2026-09-27 03:59 UTC210
                )
6f5b84fad3efatagen2026-09-27 03:59 UTC211
            })
6f5b84fad3efatagen2026-09-27 03:59 UTC212
            .collect();
6f5b84fad3efatagen2026-09-27 03:59 UTC213
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC214
6f5b84fad3efatagen2026-09-27 03:59 UTC215
    fn running(&self) -> Vec<Running> {
6f5b84fad3efatagen2026-09-27 03:59 UTC216
        self.leases
6f5b84fad3efatagen2026-09-27 03:59 UTC217
            .values()
6f5b84fad3efatagen2026-09-27 03:59 UTC218
            .filter_map(|l| {
6f5b84fad3efatagen2026-09-27 03:59 UTC219
                let run = l.run.as_ref()?;
6f5b84fad3efatagen2026-09-27 03:59 UTC220
                Some(Running {
6f5b84fad3efatagen2026-09-27 03:59 UTC221
                    need: l.req.need,
6f5b84fad3efatagen2026-09-27 03:59 UTC222
                    heavy: l.heavy,
6f5b84fad3efatagen2026-09-27 03:59 UTC223
                    fs: l.fs.clone(),
6f5b84fad3efatagen2026-09-27 03:59 UTC224
                    rss: run.rss,
6f5b84fad3efatagen2026-09-27 03:59 UTC225
                    remaining: l.req.need.time.saturating_sub(run.started.elapsed()),
6f5b84fad3efatagen2026-09-27 03:59 UTC226
                })
6f5b84fad3efatagen2026-09-27 03:59 UTC227
            })
6f5b84fad3efatagen2026-09-27 03:59 UTC228
            .collect()
6f5b84fad3efatagen2026-09-27 03:59 UTC229
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC230
6f5b84fad3efatagen2026-09-27 03:59 UTC231
    fn reschedule(&mut self) {
6f5b84fad3efatagen2026-09-27 03:59 UTC232
        let queue: Vec<Queued> = self
6f5b84fad3efatagen2026-09-27 03:59 UTC233
            .leases
6f5b84fad3efatagen2026-09-27 03:59 UTC234
            .iter()
6f5b84fad3efatagen2026-09-27 03:59 UTC235
            .filter(|(_, l)| l.run.is_none())
6f5b84fad3efatagen2026-09-27 03:59 UTC236
            .map(|(id, l)| Queued {
6f5b84fad3efatagen2026-09-27 03:59 UTC237
                id: *id,
6f5b84fad3efatagen2026-09-27 03:59 UTC238
                need: l.req.need,
6f5b84fad3efatagen2026-09-27 03:59 UTC239
                heavy: l.heavy,
6f5b84fad3efatagen2026-09-27 03:59 UTC240
                fs: l.fs.clone(),
6f5b84fad3efatagen2026-09-27 03:59 UTC241
            })
6f5b84fad3efatagen2026-09-27 03:59 UTC242
            .collect();
6f5b84fad3efatagen2026-09-27 03:59 UTC243
        if queue.is_empty() {
6f5b84fad3efatagen2026-09-27 03:59 UTC244
            return;
6f5b84fad3efatagen2026-09-27 03:59 UTC245
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC246
        let verdicts = sched::plan(
6f5b84fad3efatagen2026-09-27 03:59 UTC247
            &queue,
6f5b84fad3efatagen2026-09-27 03:59 UTC248
            &self.running(),
6f5b84fad3efatagen2026-09-27 03:59 UTC249
            &self.cap,
6f5b84fad3efatagen2026-09-27 03:59 UTC250
            self.cfg.queue.backfill_max_time.0,
6f5b84fad3efatagen2026-09-27 03:59 UTC251
        );
6f5b84fad3efatagen2026-09-27 03:59 UTC252
        let cores = self.cap.cores;
6f5b84fad3efatagen2026-09-27 03:59 UTC253
        let mut position = 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC254
        for (id, verdict) in verdicts {
6f5b84fad3efatagen2026-09-27 03:59 UTC255
            match verdict {
6f5b84fad3efatagen2026-09-27 03:59 UTC256
                Verdict::Admit => {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC257
                    let (extra, extra_ram) = self.bonus(id);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC258
                    let Some(lease) = self.leases.get_mut(&id) else {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC259
                        continue;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC260
                    };
ecc1cf8df0f5atagen2026-09-27 13:36 UTC261
                    lease.req.need.ram += extra_ram;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC262
                    let granted = lease.req.need.cores.clamp(1, cores.max(1)) + extra;
6f5b84fad3efatagen2026-09-27 03:59 UTC263
                    lease.run = Some(Run {
6f5b84fad3efatagen2026-09-27 03:59 UTC264
                        started: Instant::now(),
6f5b84fad3efatagen2026-09-27 03:59 UTC265
                        rss: Bytes::ZERO,
6f5b84fad3efatagen2026-09-27 03:59 UTC266
                        reminded: None,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC267
                        cores: granted,
6f5b84fad3efatagen2026-09-27 03:59 UTC268
                    });
6f5b84fad3efatagen2026-09-27 03:59 UTC269
                    let _ = proto::send(
6f5b84fad3efatagen2026-09-27 03:59 UTC270
                        &mut lease.out,
6f5b84fad3efatagen2026-09-27 03:59 UTC271
                        &Event::Granted {
6f5b84fad3efatagen2026-09-27 03:59 UTC272
                            lease: id,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC273
                            cores: granted,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC274
                            ram: lease.req.need.ram,
6f5b84fad3efatagen2026-09-27 03:59 UTC275
                            heavy: lease.heavy,
6f5b84fad3efatagen2026-09-27 03:59 UTC276
                        },
6f5b84fad3efatagen2026-09-27 03:59 UTC277
                    );
6f5b84fad3efatagen2026-09-27 03:59 UTC278
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC279
                Verdict::Wait(reason) => {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC280
                    let Some(lease) = self.leases.get_mut(&id) else {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC281
                        continue;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC282
                    };
6f5b84fad3efatagen2026-09-27 03:59 UTC283
                    position += 1;
6f5b84fad3efatagen2026-09-27 03:59 UTC284
                    lease.waiting = Some(reason);
6f5b84fad3efatagen2026-09-27 03:59 UTC285
                    if lease.last_pos != position {
6f5b84fad3efatagen2026-09-27 03:59 UTC286
                        lease.last_pos = position;
6f5b84fad3efatagen2026-09-27 03:59 UTC287
                        let _ = proto::send(
6f5b84fad3efatagen2026-09-27 03:59 UTC288
                            &mut lease.out,
6f5b84fad3efatagen2026-09-27 03:59 UTC289
                            &Event::Queued {
6f5b84fad3efatagen2026-09-27 03:59 UTC290
                                lease: id,
6f5b84fad3efatagen2026-09-27 03:59 UTC291
                                position,
6f5b84fad3efatagen2026-09-27 03:59 UTC292
                                reason: Some(reason),
6f5b84fad3efatagen2026-09-27 03:59 UTC293
                            },
6f5b84fad3efatagen2026-09-27 03:59 UTC294
                        );
6f5b84fad3efatagen2026-09-27 03:59 UTC295
                    }
6f5b84fad3efatagen2026-09-27 03:59 UTC296
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC297
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC298
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC299
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC300
ecc1cf8df0f5atagen2026-09-27 13:36 UTC301
    fn held_by(&self, session: &str) -> (u32, Bytes) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC302
        self.leases
ecc1cf8df0f5atagen2026-09-27 13:36 UTC303
            .values()
ecc1cf8df0f5atagen2026-09-27 13:36 UTC304
            .filter(|l| l.req.session.as_deref() == Some(session))
ecc1cf8df0f5atagen2026-09-27 13:36 UTC305
            .fold((0, Bytes::ZERO), |(c, r), l| {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC306
                (
ecc1cf8df0f5atagen2026-09-27 13:36 UTC307
                    c + l.run.as_ref().map_or(l.req.need.cores, |run| run.cores),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC308
                    r + l.req.need.ram,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC309
                )
ecc1cf8df0f5atagen2026-09-27 13:36 UTC310
            })
ecc1cf8df0f5atagen2026-09-27 13:36 UTC311
    }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC312
ecc1cf8df0f5atagen2026-09-27 13:36 UTC313
    /// idle cores the lease can have on top of its ask, minus whatever other
ecc1cf8df0f5atagen2026-09-27 13:36 UTC314
    /// sessions said they'll want and don't hold yet. capped by ram at the
ecc1cf8df0f5atagen2026-09-27 13:36 UTC315
    /// lease's own ram per core, since every extra job eats its share
ecc1cf8df0f5atagen2026-09-27 13:36 UTC316
    fn bonus(&self, id: LeaseId) -> (u32, Bytes) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC317
        let Some(lease) = self.leases.get(&id) else {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC318
            return (0, Bytes::ZERO);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC319
        };
ecc1cf8df0f5atagen2026-09-27 13:36 UTC320
        if lease.req.exact || !self.cfg.queue.elastic {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC321
            return (0, Bytes::ZERO);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC322
        }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC323
        let need = lease.req.need;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC324
        let (taken, queued_ram) = self.leases.iter().filter(|(k, _)| **k != id).fold(
ecc1cf8df0f5atagen2026-09-27 13:36 UTC325
            (need.cores, need.ram),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC326
            |(c, r), (_, l)| match &l.run {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC327
                Some(run) => (c + run.cores, r),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC328
                None => (c + l.req.need.cores, r + l.req.need.ram),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC329
            },
ecc1cf8df0f5atagen2026-09-27 13:36 UTC330
        );
ecc1cf8df0f5atagen2026-09-27 13:36 UTC331
        let mut spare = self.cap.cores.saturating_sub(taken);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC332
        let mut ram_spare = sched::headroom(&self.running(), &self.cap)
ecc1cf8df0f5atagen2026-09-27 13:36 UTC333
            .0
ecc1cf8df0f5atagen2026-09-27 13:36 UTC334
            .saturating_sub(queued_ram);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC335
        for (session, intent) in &self.intents {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC336
            if lease.req.session.as_deref() == Some(session) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC337
                continue;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC338
            }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC339
            let (cores, ram) = self.held_by(session);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC340
            spare = spare.saturating_sub(intent.need.cores.saturating_sub(cores));
ecc1cf8df0f5atagen2026-09-27 13:36 UTC341
            ram_spare = ram_spare.saturating_sub(intent.need.ram.saturating_sub(ram));
ecc1cf8df0f5atagen2026-09-27 13:36 UTC342
        }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC343
        let per_core = need.ram.0 / u64::from(need.cores.max(1));
ecc1cf8df0f5atagen2026-09-27 13:36 UTC344
        let by_ram = ram_spare
ecc1cf8df0f5atagen2026-09-27 13:36 UTC345
            .0
ecc1cf8df0f5atagen2026-09-27 13:36 UTC346
            .checked_div(per_core)
ecc1cf8df0f5atagen2026-09-27 13:36 UTC347
            .map_or(spare, |n| n as u32);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC348
        let extra = spare.min(by_ram);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC349
        (extra, Bytes(per_core * u64::from(extra)))
ecc1cf8df0f5atagen2026-09-27 13:36 UTC350
    }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC351
ecc1cf8df0f5atagen2026-09-27 13:36 UTC352
    /// explicit intents set the size, builds only keep one alive or start a
ecc1cf8df0f5atagen2026-09-27 13:36 UTC353
    /// default one sized like themselves
ecc1cf8df0f5atagen2026-09-27 13:36 UTC354
    fn intend(&mut self, session: &str, need: Need, explicit: bool) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC355
        let until = Instant::now() + self.cfg.queue.intent_ttl.0;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC356
        match self.intents.get_mut(session) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC357
            Some(i) => {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC358
                i.until = until;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC359
                if explicit {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC360
                    i.need = need;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC361
                }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC362
            }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC363
            None => {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC364
                self.intents
ecc1cf8df0f5atagen2026-09-27 13:36 UTC365
                    .insert(session.to_owned(), Intent { need, until });
ecc1cf8df0f5atagen2026-09-27 13:36 UTC366
            }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC367
        }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC368
    }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC369
ecc1cf8df0f5atagen2026-09-27 13:36 UTC370
    fn expire_intents(&mut self) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC371
        let had = !self.intents.is_empty();
ecc1cf8df0f5atagen2026-09-27 13:36 UTC372
        let now = Instant::now();
ecc1cf8df0f5atagen2026-09-27 13:36 UTC373
        self.intents.retain(|_, i| i.until > now);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC374
        if had && self.intents.is_empty() && self.leases.is_empty() {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC375
            self.idle_since = now;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC376
        }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC377
    }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC378
6f5b84fad3efatagen2026-09-27 03:59 UTC379
    fn remove(&mut self, id: LeaseId) {
6f5b84fad3efatagen2026-09-27 03:59 UTC380
        if let Some(lease) = self.leases.remove(&id) {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC381
            if let Some(s) = &lease.req.session
ecc1cf8df0f5atagen2026-09-27 13:36 UTC382
                && let Some(i) = self.intents.get_mut(s)
ecc1cf8df0f5atagen2026-09-27 13:36 UTC383
            {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC384
                i.until = Instant::now() + self.cfg.queue.intent_ttl.0;
ecc1cf8df0f5atagen2026-09-27 13:36 UTC385
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC386
            for mut w in lease.watchers {
6f5b84fad3efatagen2026-09-27 03:59 UTC387
                let _ = proto::send(&mut w, &Event::Fired { overdue: false });
6f5b84fad3efatagen2026-09-27 03:59 UTC388
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC389
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC390
        if self.leases.is_empty() {
6f5b84fad3efatagen2026-09-27 03:59 UTC391
            self.idle_since = Instant::now();
6f5b84fad3efatagen2026-09-27 03:59 UTC392
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC393
        self.refresh();
6f5b84fad3efatagen2026-09-27 03:59 UTC394
        self.reschedule();
6f5b84fad3efatagen2026-09-27 03:59 UTC395
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC396
6f5b84fad3efatagen2026-09-27 03:59 UTC397
    fn fire_overdue(&mut self) {
6f5b84fad3efatagen2026-09-27 03:59 UTC398
        let grace = self.cfg.overrun.time_grace;
6f5b84fad3efatagen2026-09-27 03:59 UTC399
        for lease in self.leases.values_mut() {
6f5b84fad3efatagen2026-09-27 03:59 UTC400
            if !lease.watchers.is_empty() && lease.overdue(grace) {
6f5b84fad3efatagen2026-09-27 03:59 UTC401
                for mut w in lease.watchers.drain(..) {
6f5b84fad3efatagen2026-09-27 03:59 UTC402
                    let _ = proto::send(&mut w, &Event::Fired { overdue: true });
6f5b84fad3efatagen2026-09-27 03:59 UTC403
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC404
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC405
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC406
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC407
6f5b84fad3efatagen2026-09-27 03:59 UTC408
    fn info(&self) -> Vec<LeaseInfo> {
6f5b84fad3efatagen2026-09-27 03:59 UTC409
        let grace = self.cfg.overrun.time_grace;
6f5b84fad3efatagen2026-09-27 03:59 UTC410
        let mut position = 0;
6f5b84fad3efatagen2026-09-27 03:59 UTC411
        self.leases
6f5b84fad3efatagen2026-09-27 03:59 UTC412
            .iter()
6f5b84fad3efatagen2026-09-27 03:59 UTC413
            .map(|(id, l)| {
6f5b84fad3efatagen2026-09-27 03:59 UTC414
                if l.run.is_none() {
6f5b84fad3efatagen2026-09-27 03:59 UTC415
                    position += 1;
6f5b84fad3efatagen2026-09-27 03:59 UTC416
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC417
                LeaseInfo {
6f5b84fad3efatagen2026-09-27 03:59 UTC418
                    id: *id,
6f5b84fad3efatagen2026-09-27 03:59 UTC419
                    cmd: l.req.cmd.clone(),
6f5b84fad3efatagen2026-09-27 03:59 UTC420
                    cwd: l.req.cwd.clone(),
6f5b84fad3efatagen2026-09-27 03:59 UTC421
                    session: l.req.session.clone(),
6f5b84fad3efatagen2026-09-27 03:59 UTC422
                    need: l.req.need,
6f5b84fad3efatagen2026-09-27 03:59 UTC423
                    heavy: l.heavy,
6f5b84fad3efatagen2026-09-27 03:59 UTC424
                    running: l.run.is_some(),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC425
                    granted: l.run.as_ref().map_or(0, |r| r.cores),
6f5b84fad3efatagen2026-09-27 03:59 UTC426
                    elapsed: l.elapsed().as_secs(),
6f5b84fad3efatagen2026-09-27 03:59 UTC427
                    rss: l.run.as_ref().map_or(Bytes::ZERO, |r| r.rss),
6f5b84fad3efatagen2026-09-27 03:59 UTC428
                    overdue: l.overdue(grace),
6f5b84fad3efatagen2026-09-27 03:59 UTC429
                    waiting: l.waiting.filter(|_| l.run.is_none()),
6f5b84fad3efatagen2026-09-27 03:59 UTC430
                    position: if l.run.is_none() { position } else { 0 },
6f5b84fad3efatagen2026-09-27 03:59 UTC431
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC432
            })
6f5b84fad3efatagen2026-09-27 03:59 UTC433
            .collect()
6f5b84fad3efatagen2026-09-27 03:59 UTC434
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC435
6f5b84fad3efatagen2026-09-27 03:59 UTC436
    fn status(&self) -> Status {
6f5b84fad3efatagen2026-09-27 03:59 UTC437
        let (ram_free, cores_free) = sched::headroom(&self.running(), &self.cap);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC438
        let now = Instant::now();
6f5b84fad3efatagen2026-09-27 03:59 UTC439
        Status {
6f5b84fad3efatagen2026-09-27 03:59 UTC440
            leases: self.info(),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC441
            intents: self
ecc1cf8df0f5atagen2026-09-27 13:36 UTC442
                .intents
ecc1cf8df0f5atagen2026-09-27 13:36 UTC443
                .iter()
ecc1cf8df0f5atagen2026-09-27 13:36 UTC444
                .map(|(session, i)| IntentInfo {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC445
                    session: session.clone(),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC446
                    need: i.need,
ecc1cf8df0f5atagen2026-09-27 13:36 UTC447
                    expires_in: i.until.saturating_duration_since(now).as_secs(),
ecc1cf8df0f5atagen2026-09-27 13:36 UTC448
                })
ecc1cf8df0f5atagen2026-09-27 13:36 UTC449
                .collect(),
6f5b84fad3efatagen2026-09-27 03:59 UTC450
            ram_free,
6f5b84fad3efatagen2026-09-27 03:59 UTC451
            ram_budget: self.cap.ram_budget,
6f5b84fad3efatagen2026-09-27 03:59 UTC452
            cores_free,
6f5b84fad3efatagen2026-09-27 03:59 UTC453
            cores: self.cap.cores,
6f5b84fad3efatagen2026-09-27 03:59 UTC454
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC455
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC456
}
6f5b84fad3efatagen2026-09-27 03:59 UTC457
6f5b84fad3efatagen2026-09-27 03:59 UTC458
fn serve(shared: &Shared, mut stream: UnixStream) -> io::Result<()> {
6f5b84fad3efatagen2026-09-27 03:59 UTC459
    let mut reader = Reader::new(&stream)?;
6f5b84fad3efatagen2026-09-27 03:59 UTC460
    let Some(req) = reader.recv::<Request>()? else {
6f5b84fad3efatagen2026-09-27 03:59 UTC461
        return Ok(());
6f5b84fad3efatagen2026-09-27 03:59 UTC462
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC463
    match req {
6f5b84fad3efatagen2026-09-27 03:59 UTC464
        Request::Acquire(req) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC465
            let id = {
6f5b84fad3efatagen2026-09-27 03:59 UTC466
                let mut st = lock(shared);
6f5b84fad3efatagen2026-09-27 03:59 UTC467
                let id = st.next;
6f5b84fad3efatagen2026-09-27 03:59 UTC468
                st.next += 1;
6f5b84fad3efatagen2026-09-27 03:59 UTC469
                let fs = st.fs_for(&req.cwd);
6f5b84fad3efatagen2026-09-27 03:59 UTC470
                let heavy = st.heavy(&req.need);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC471
                if let Some(s) = req.session.clone() {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC472
                    st.intend(&s, req.need, false);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC473
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC474
                st.leases.insert(
6f5b84fad3efatagen2026-09-27 03:59 UTC475
                    id,
6f5b84fad3efatagen2026-09-27 03:59 UTC476
                    Lease {
6f5b84fad3efatagen2026-09-27 03:59 UTC477
                        req,
6f5b84fad3efatagen2026-09-27 03:59 UTC478
                        heavy,
6f5b84fad3efatagen2026-09-27 03:59 UTC479
                        fs,
6f5b84fad3efatagen2026-09-27 03:59 UTC480
                        queued_at: Instant::now(),
6f5b84fad3efatagen2026-09-27 03:59 UTC481
                        run: None,
6f5b84fad3efatagen2026-09-27 03:59 UTC482
                        out: stream.try_clone()?,
6f5b84fad3efatagen2026-09-27 03:59 UTC483
                        waiting: None,
6f5b84fad3efatagen2026-09-27 03:59 UTC484
                        last_pos: 0,
6f5b84fad3efatagen2026-09-27 03:59 UTC485
                        watchers: Vec::new(),
6f5b84fad3efatagen2026-09-27 03:59 UTC486
                    },
6f5b84fad3efatagen2026-09-27 03:59 UTC487
                );
6f5b84fad3efatagen2026-09-27 03:59 UTC488
                st.reschedule();
6f5b84fad3efatagen2026-09-27 03:59 UTC489
                id
6f5b84fad3efatagen2026-09-27 03:59 UTC490
            };
6f5b84fad3efatagen2026-09-27 03:59 UTC491
            hold(shared, id, &mut reader);
6f5b84fad3efatagen2026-09-27 03:59 UTC492
            lock(shared).remove(id);
6f5b84fad3efatagen2026-09-27 03:59 UTC493
            Ok(())
6f5b84fad3efatagen2026-09-27 03:59 UTC494
        }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC495
        Request::Expect { session, need } => {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC496
            let mut st = lock(shared);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC497
            if let Some(need) = need {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC498
                st.intend(&session, need, true);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC499
            } else {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC500
                st.intents.remove(&session);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC501
                if st.intents.is_empty() && st.leases.is_empty() {
ecc1cf8df0f5atagen2026-09-27 13:36 UTC502
                    st.idle_since = Instant::now();
ecc1cf8df0f5atagen2026-09-27 13:36 UTC503
                }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC504
            }
ecc1cf8df0f5atagen2026-09-27 13:36 UTC505
            drop(st);
ecc1cf8df0f5atagen2026-09-27 13:36 UTC506
            proto::send(&mut stream, &Event::Ok)
ecc1cf8df0f5atagen2026-09-27 13:36 UTC507
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC508
        Request::Status => {
6f5b84fad3efatagen2026-09-27 03:59 UTC509
            let status = lock(shared).status();
6f5b84fad3efatagen2026-09-27 03:59 UTC510
            proto::send(&mut stream, &Event::Status(status))
6f5b84fad3efatagen2026-09-27 03:59 UTC511
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC512
        Request::Kill { lease } => {
6f5b84fad3efatagen2026-09-27 03:59 UTC513
            let mut st = lock(shared);
6f5b84fad3efatagen2026-09-27 03:59 UTC514
            let reply = match st.leases.get_mut(&lease) {
6f5b84fad3efatagen2026-09-27 03:59 UTC515
                Some(l) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC516
                    let _ = proto::send(&mut l.out, &Event::Kill);
6f5b84fad3efatagen2026-09-27 03:59 UTC517
                    Event::Ok
6f5b84fad3efatagen2026-09-27 03:59 UTC518
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC519
                None => Event::Err {
6f5b84fad3efatagen2026-09-27 03:59 UTC520
                    msg: format!("no lease {lease}"),
6f5b84fad3efatagen2026-09-27 03:59 UTC521
                },
6f5b84fad3efatagen2026-09-27 03:59 UTC522
            };
6f5b84fad3efatagen2026-09-27 03:59 UTC523
            drop(st);
6f5b84fad3efatagen2026-09-27 03:59 UTC524
            proto::send(&mut stream, &reply)
6f5b84fad3efatagen2026-09-27 03:59 UTC525
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC526
        Request::Extend { lease, by } => {
6f5b84fad3efatagen2026-09-27 03:59 UTC527
            let mut st = lock(shared);
6f5b84fad3efatagen2026-09-27 03:59 UTC528
            let reply = match st.leases.get_mut(&lease) {
6f5b84fad3efatagen2026-09-27 03:59 UTC529
                Some(l) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC530
                    l.req.need.time += by.0;
6f5b84fad3efatagen2026-09-27 03:59 UTC531
                    if let Some(run) = &mut l.run {
6f5b84fad3efatagen2026-09-27 03:59 UTC532
                        run.reminded = None;
6f5b84fad3efatagen2026-09-27 03:59 UTC533
                    }
6f5b84fad3efatagen2026-09-27 03:59 UTC534
                    Event::Ok
6f5b84fad3efatagen2026-09-27 03:59 UTC535
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC536
                None => Event::Err {
6f5b84fad3efatagen2026-09-27 03:59 UTC537
                    msg: format!("no lease {lease}"),
6f5b84fad3efatagen2026-09-27 03:59 UTC538
                },
6f5b84fad3efatagen2026-09-27 03:59 UTC539
            };
6f5b84fad3efatagen2026-09-27 03:59 UTC540
            drop(st);
6f5b84fad3efatagen2026-09-27 03:59 UTC541
            proto::send(&mut stream, &reply)
6f5b84fad3efatagen2026-09-27 03:59 UTC542
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC543
        Request::Watch { lease } => {
6f5b84fad3efatagen2026-09-27 03:59 UTC544
            let mut st = lock(shared);
6f5b84fad3efatagen2026-09-27 03:59 UTC545
            let grace = st.cfg.overrun.time_grace;
6f5b84fad3efatagen2026-09-27 03:59 UTC546
            match st.leases.get_mut(&lease) {
6f5b84fad3efatagen2026-09-27 03:59 UTC547
                Some(l) if l.overdue(grace) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC548
                    proto::send(&mut stream, &Event::Fired { overdue: true })
6f5b84fad3efatagen2026-09-27 03:59 UTC549
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC550
                Some(l) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC551
                    l.watchers.push(stream);
6f5b84fad3efatagen2026-09-27 03:59 UTC552
                    Ok(())
6f5b84fad3efatagen2026-09-27 03:59 UTC553
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC554
                None => proto::send(&mut stream, &Event::Fired { overdue: false }),
6f5b84fad3efatagen2026-09-27 03:59 UTC555
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC556
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC557
        Request::Overdue { session } => {
6f5b84fad3efatagen2026-09-27 03:59 UTC558
            let mut st = lock(shared);
6f5b84fad3efatagen2026-09-27 03:59 UTC559
            let grace = st.cfg.overrun.time_grace;
6f5b84fad3efatagen2026-09-27 03:59 UTC560
            let every = st.cfg.overrun.remind_every.0;
6f5b84fad3efatagen2026-09-27 03:59 UTC561
            let mut due = Vec::new();
6f5b84fad3efatagen2026-09-27 03:59 UTC562
            for (id, l) in &mut st.leases {
6f5b84fad3efatagen2026-09-27 03:59 UTC563
                if l.req.session.as_deref() != Some(&session) || !l.overdue(grace) {
6f5b84fad3efatagen2026-09-27 03:59 UTC564
                    continue;
6f5b84fad3efatagen2026-09-27 03:59 UTC565
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC566
                let Some(run) = &mut l.run else { continue };
6f5b84fad3efatagen2026-09-27 03:59 UTC567
                if run.reminded.is_some_and(|t| t.elapsed() < every) {
6f5b84fad3efatagen2026-09-27 03:59 UTC568
                    continue;
6f5b84fad3efatagen2026-09-27 03:59 UTC569
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC570
                run.reminded = Some(Instant::now());
6f5b84fad3efatagen2026-09-27 03:59 UTC571
                due.push(*id);
6f5b84fad3efatagen2026-09-27 03:59 UTC572
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC573
            let leases = st
6f5b84fad3efatagen2026-09-27 03:59 UTC574
                .info()
6f5b84fad3efatagen2026-09-27 03:59 UTC575
                .into_iter()
6f5b84fad3efatagen2026-09-27 03:59 UTC576
                .filter(|i| due.contains(&i.id))
6f5b84fad3efatagen2026-09-27 03:59 UTC577
                .collect();
6f5b84fad3efatagen2026-09-27 03:59 UTC578
            drop(st);
6f5b84fad3efatagen2026-09-27 03:59 UTC579
            proto::send(&mut stream, &Event::Overdue { leases })
6f5b84fad3efatagen2026-09-27 03:59 UTC580
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC581
        Request::Started { .. } | Request::Usage { .. } | Request::Done => proto::send(
6f5b84fad3efatagen2026-09-27 03:59 UTC582
            &mut stream,
6f5b84fad3efatagen2026-09-27 03:59 UTC583
            &Event::Err {
6f5b84fad3efatagen2026-09-27 03:59 UTC584
                msg: "no lease on this connection".into(),
6f5b84fad3efatagen2026-09-27 03:59 UTC585
            },
6f5b84fad3efatagen2026-09-27 03:59 UTC586
        ),
6f5b84fad3efatagen2026-09-27 03:59 UTC587
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC588
}
6f5b84fad3efatagen2026-09-27 03:59 UTC589
6f5b84fad3efatagen2026-09-27 03:59 UTC590
/// a client that vanishes without saying done got sigkilled or similar, and
6f5b84fad3efatagen2026-09-27 03:59 UTC591
/// pdeathsig only reaches its direct child. take the rest of the group down
6f5b84fad3efatagen2026-09-27 03:59 UTC592
/// so a headless build doesn't keep eating ram off the books
6f5b84fad3efatagen2026-09-27 03:59 UTC593
fn hold(shared: &Shared, id: LeaseId, reader: &mut Reader) {
6f5b84fad3efatagen2026-09-27 03:59 UTC594
    let mut pgid = None;
6f5b84fad3efatagen2026-09-27 03:59 UTC595
    let clean = loop {
6f5b84fad3efatagen2026-09-27 03:59 UTC596
        match reader.recv::<Request>() {
6f5b84fad3efatagen2026-09-27 03:59 UTC597
            Ok(Some(Request::Usage { rss })) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC598
                if let Some(run) = lock(shared)
6f5b84fad3efatagen2026-09-27 03:59 UTC599
                    .leases
6f5b84fad3efatagen2026-09-27 03:59 UTC600
                    .get_mut(&id)
6f5b84fad3efatagen2026-09-27 03:59 UTC601
                    .and_then(|l| l.run.as_mut())
6f5b84fad3efatagen2026-09-27 03:59 UTC602
                {
6f5b84fad3efatagen2026-09-27 03:59 UTC603
                    run.rss = rss;
6f5b84fad3efatagen2026-09-27 03:59 UTC604
                }
6f5b84fad3efatagen2026-09-27 03:59 UTC605
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC606
            Ok(Some(Request::Started { pid })) => pgid = Some(pid),
6f5b84fad3efatagen2026-09-27 03:59 UTC607
            Ok(Some(Request::Done)) => break true,
6f5b84fad3efatagen2026-09-27 03:59 UTC608
            Ok(Some(_)) => {}
6f5b84fad3efatagen2026-09-27 03:59 UTC609
            Ok(None) | Err(_) => break false,
6f5b84fad3efatagen2026-09-27 03:59 UTC610
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC611
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC612
    if !clean && let Some(pgid) = pgid.filter(|p| *p > 1) {
6f5b84fad3efatagen2026-09-27 03:59 UTC613
        unsafe { libc::kill(-pgid, libc::SIGTERM) };
6f5b84fad3efatagen2026-09-27 03:59 UTC614
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC615
}
6f5b84fad3efatagen2026-09-27 03:59 UTC616
6f5b84fad3efatagen2026-09-27 03:59 UTC617
fn tick(shared: &Shared) {
6f5b84fad3efatagen2026-09-27 03:59 UTC618
    loop {
6f5b84fad3efatagen2026-09-27 03:59 UTC619
        thread::sleep(Duration::from_secs(1));
6f5b84fad3efatagen2026-09-27 03:59 UTC620
        let mut st = lock(shared);
6f5b84fad3efatagen2026-09-27 03:59 UTC621
        st.refresh();
6f5b84fad3efatagen2026-09-27 03:59 UTC622
        st.reschedule();
6f5b84fad3efatagen2026-09-27 03:59 UTC623
        st.fire_overdue();
ecc1cf8df0f5atagen2026-09-27 13:36 UTC624
        st.expire_intents();
ecc1cf8df0f5atagen2026-09-27 13:36 UTC625
        if st.conns == 0
ecc1cf8df0f5atagen2026-09-27 13:36 UTC626
            && st.leases.is_empty()
ecc1cf8df0f5atagen2026-09-27 13:36 UTC627
            && st.intents.is_empty()
ecc1cf8df0f5atagen2026-09-27 13:36 UTC628
            && st.idle_since.elapsed() >= st.cfg.queue.linger.0
6f5b84fad3efatagen2026-09-27 03:59 UTC629
        {
61c6e94355d9atagen2026-09-27 03:59 UTC630
            // systemd owns the socket and queues whoever knocks while we're
61c6e94355d9atagen2026-09-27 03:59 UTC631
            // leaving, so there's nothing to tidy
61c6e94355d9atagen2026-09-27 03:59 UTC632
            if ACTIVATED.load(Ordering::Relaxed) {
61c6e94355d9atagen2026-09-27 03:59 UTC633
                process::exit(0);
61c6e94355d9atagen2026-09-27 03:59 UTC634
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC635
            // a spawner holds this while it clears a stale socket, so taking it
6f5b84fad3efatagen2026-09-27 03:59 UTC636
            // here stops us unlinking our successor's socket on the way out
6f5b84fad3efatagen2026-09-27 03:59 UTC637
            if let Some(_guard) = try_spawn_lock() {
6f5b84fad3efatagen2026-09-27 03:59 UTC638
                let _ = fs::remove_file(paths::socket());
6f5b84fad3efatagen2026-09-27 03:59 UTC639
                process::exit(0);
6f5b84fad3efatagen2026-09-27 03:59 UTC640
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC641
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC642
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC643
}
6f5b84fad3efatagen2026-09-27 03:59 UTC644
56a853ebf254atagen2026-09-27 03:59 UTC645
/// watches the directory, not the file. editors save by renaming over it,
56a853ebf254atagen2026-09-27 03:59 UTC646
/// which would orphan a watch on the old inode after the first edit
56a853ebf254atagen2026-09-27 03:59 UTC647
#[cfg(target_os = "linux")]
56a853ebf254atagen2026-09-27 03:59 UTC648
fn watch_config(shared: &Shared) {
56a853ebf254atagen2026-09-27 03:59 UTC649
    use std::os::unix::ffi::OsStrExt;
56a853ebf254atagen2026-09-27 03:59 UTC650
56a853ebf254atagen2026-09-27 03:59 UTC651
    let path = paths::config_file();
56a853ebf254atagen2026-09-27 03:59 UTC652
    let (Some(dir), Some(name)) = (path.parent(), path.file_name()) else {
56a853ebf254atagen2026-09-27 03:59 UTC653
        return;
56a853ebf254atagen2026-09-27 03:59 UTC654
    };
56a853ebf254atagen2026-09-27 03:59 UTC655
    let _ = fs::create_dir_all(dir);
56a853ebf254atagen2026-09-27 03:59 UTC656
    let Ok(cdir) = std::ffi::CString::new(dir.as_os_str().as_bytes()) else {
56a853ebf254atagen2026-09-27 03:59 UTC657
        return;
56a853ebf254atagen2026-09-27 03:59 UTC658
    };
56a853ebf254atagen2026-09-27 03:59 UTC659
    let fd = unsafe { libc::inotify_init1(libc::IN_CLOEXEC) };
56a853ebf254atagen2026-09-27 03:59 UTC660
    if fd < 0 {
56a853ebf254atagen2026-09-27 03:59 UTC661
        return;
56a853ebf254atagen2026-09-27 03:59 UTC662
    }
56a853ebf254atagen2026-09-27 03:59 UTC663
    let mask = libc::IN_CLOSE_WRITE
56a853ebf254atagen2026-09-27 03:59 UTC664
        | libc::IN_MOVED_TO
56a853ebf254atagen2026-09-27 03:59 UTC665
        | libc::IN_MOVED_FROM
56a853ebf254atagen2026-09-27 03:59 UTC666
        | libc::IN_CREATE
56a853ebf254atagen2026-09-27 03:59 UTC667
        | libc::IN_DELETE;
56a853ebf254atagen2026-09-27 03:59 UTC668
    if unsafe { libc::inotify_add_watch(fd, cdir.as_ptr(), mask) } < 0 {
56a853ebf254atagen2026-09-27 03:59 UTC669
        return;
56a853ebf254atagen2026-09-27 03:59 UTC670
    }
56a853ebf254atagen2026-09-27 03:59 UTC671
56a853ebf254atagen2026-09-27 03:59 UTC672
    let header = std::mem::size_of::<libc::inotify_event>();
56a853ebf254atagen2026-09-27 03:59 UTC673
    let mut buf = [0u8; 4096];
56a853ebf254atagen2026-09-27 03:59 UTC674
    loop {
56a853ebf254atagen2026-09-27 03:59 UTC675
        let n = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) };
56a853ebf254atagen2026-09-27 03:59 UTC676
        let Ok(n) = usize::try_from(n) else {
56a853ebf254atagen2026-09-27 03:59 UTC677
            return;
56a853ebf254atagen2026-09-27 03:59 UTC678
        };
56a853ebf254atagen2026-09-27 03:59 UTC679
        let mut hit = false;
56a853ebf254atagen2026-09-27 03:59 UTC680
        let mut off = 0;
56a853ebf254atagen2026-09-27 03:59 UTC681
        while off + header <= n {
56a853ebf254atagen2026-09-27 03:59 UTC682
            let ev: libc::inotify_event =
56a853ebf254atagen2026-09-27 03:59 UTC683
                unsafe { std::ptr::read_unaligned(buf.as_ptr().add(off).cast()) };
56a853ebf254atagen2026-09-27 03:59 UTC684
            let len = ev.len as usize;
56a853ebf254atagen2026-09-27 03:59 UTC685
            let raw = &buf[off + header..(off + header + len).min(n)];
56a853ebf254atagen2026-09-27 03:59 UTC686
            let ev_name = raw.split(|b| *b == 0).next().unwrap_or_default();
56a853ebf254atagen2026-09-27 03:59 UTC687
            hit |= ev_name == name.as_bytes();
56a853ebf254atagen2026-09-27 03:59 UTC688
            off += header + len;
56a853ebf254atagen2026-09-27 03:59 UTC689
        }
56a853ebf254atagen2026-09-27 03:59 UTC690
        if !hit {
56a853ebf254atagen2026-09-27 03:59 UTC691
            continue;
56a853ebf254atagen2026-09-27 03:59 UTC692
        }
56a853ebf254atagen2026-09-27 03:59 UTC693
        thread::sleep(Duration::from_millis(100));
56a853ebf254atagen2026-09-27 03:59 UTC694
        match Config::load() {
56a853ebf254atagen2026-09-27 03:59 UTC695
            Ok(cfg) => {
56a853ebf254atagen2026-09-27 03:59 UTC696
                lock(shared).reload(cfg);
56a853ebf254atagen2026-09-27 03:59 UTC697
                eprintln!("roundabout: config reloaded");
56a853ebf254atagen2026-09-27 03:59 UTC698
            }
56a853ebf254atagen2026-09-27 03:59 UTC699
            Err(e) => eprintln!("roundabout: kept the old config, {e}"),
56a853ebf254atagen2026-09-27 03:59 UTC700
        }
56a853ebf254atagen2026-09-27 03:59 UTC701
    }
56a853ebf254atagen2026-09-27 03:59 UTC702
}
56a853ebf254atagen2026-09-27 03:59 UTC703
61c6e94355d9atagen2026-09-27 03:59 UTC704
/// creates the file if it's gone, else a wiped runtime dir leaves us idling
61c6e94355d9atagen2026-09-27 03:59 UTC705
/// forever unable to take the lock we need to exit
6f5b84fad3efatagen2026-09-27 03:59 UTC706
fn try_spawn_lock() -> Option<client::Flock> {
6f5b84fad3efatagen2026-09-27 03:59 UTC707
    let file = fs::OpenOptions::new()
61c6e94355d9atagen2026-09-27 03:59 UTC708
        .create(true)
61c6e94355d9atagen2026-09-27 03:59 UTC709
        .truncate(false)
6f5b84fad3efatagen2026-09-27 03:59 UTC710
        .write(true)
6f5b84fad3efatagen2026-09-27 03:59 UTC711
        .open(paths::spawn_lock())
6f5b84fad3efatagen2026-09-27 03:59 UTC712
        .ok()?;
6f5b84fad3efatagen2026-09-27 03:59 UTC713
    client::Flock::try_exclusive(file)
6f5b84fad3efatagen2026-09-27 03:59 UTC714
}
6f5b84fad3efatagen2026-09-27 03:59 UTC715
61c6e94355d9atagen2026-09-27 03:59 UTC716
static ACTIVATED: AtomicBool = AtomicBool::new(false);
61c6e94355d9atagen2026-09-27 03:59 UTC717
61c6e94355d9atagen2026-09-27 03:59 UTC718
fn inherited() -> Option<UnixListener> {
61c6e94355d9atagen2026-09-27 03:59 UTC719
    let ours = std::env::var("LISTEN_PID").ok()?.parse::<u32>().ok()? == process::id();
61c6e94355d9atagen2026-09-27 03:59 UTC720
    if !ours || std::env::var("LISTEN_FDS").ok()? != "1" {
61c6e94355d9atagen2026-09-27 03:59 UTC721
        return None;
61c6e94355d9atagen2026-09-27 03:59 UTC722
    }
61c6e94355d9atagen2026-09-27 03:59 UTC723
    // SAFETY: sd_listen_fds protocol, fd 3 is the listening socket
61c6e94355d9atagen2026-09-27 03:59 UTC724
    Some(unsafe { UnixListener::from_raw_fd(3) })
61c6e94355d9atagen2026-09-27 03:59 UTC725
}
61c6e94355d9atagen2026-09-27 03:59 UTC726
61c6e94355d9atagen2026-09-27 03:59 UTC727
fn bind() -> UnixListener {
6f5b84fad3efatagen2026-09-27 03:59 UTC728
    // fork once more so the client that spawned us can reap its child now
6f5b84fad3efatagen2026-09-27 03:59 UTC729
    // instead of carrying a zombie for the length of its build
6f5b84fad3efatagen2026-09-27 03:59 UTC730
    match unsafe { libc::fork() } {
6f5b84fad3efatagen2026-09-27 03:59 UTC731
        -1 => {
6f5b84fad3efatagen2026-09-27 03:59 UTC732
            eprintln!("roundabout: fork failed: {}", io::Error::last_os_error());
6f5b84fad3efatagen2026-09-27 03:59 UTC733
            process::exit(1);
6f5b84fad3efatagen2026-09-27 03:59 UTC734
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC735
        0 => {}
6f5b84fad3efatagen2026-09-27 03:59 UTC736
        _ => process::exit(0),
6f5b84fad3efatagen2026-09-27 03:59 UTC737
    }
61c6e94355d9atagen2026-09-27 03:59 UTC738
    match UnixListener::bind(paths::socket()) {
6f5b84fad3efatagen2026-09-27 03:59 UTC739
        Ok(l) => l,
6f5b84fad3efatagen2026-09-27 03:59 UTC740
        Err(e) if e.kind() == io::ErrorKind::AddrInUse && client::connect().is_some() => {
6f5b84fad3efatagen2026-09-27 03:59 UTC741
            process::exit(0)
6f5b84fad3efatagen2026-09-27 03:59 UTC742
        }
6f5b84fad3efatagen2026-09-27 03:59 UTC743
        Err(e) => {
6f5b84fad3efatagen2026-09-27 03:59 UTC744
            eprintln!("roundabout: bind: {e}");
6f5b84fad3efatagen2026-09-27 03:59 UTC745
            process::exit(1);
6f5b84fad3efatagen2026-09-27 03:59 UTC746
        }
61c6e94355d9atagen2026-09-27 03:59 UTC747
    }
61c6e94355d9atagen2026-09-27 03:59 UTC748
}
61c6e94355d9atagen2026-09-27 03:59 UTC749
61c6e94355d9atagen2026-09-27 03:59 UTC750
pub fn main(cfg: Config) -> ! {
61c6e94355d9atagen2026-09-27 03:59 UTC751
    let listener = if let Some(l) = inherited() {
61c6e94355d9atagen2026-09-27 03:59 UTC752
        ACTIVATED.store(true, Ordering::Relaxed);
61c6e94355d9atagen2026-09-27 03:59 UTC753
        l
61c6e94355d9atagen2026-09-27 03:59 UTC754
    } else {
61c6e94355d9atagen2026-09-27 03:59 UTC755
        bind()
6f5b84fad3efatagen2026-09-27 03:59 UTC756
    };
6f5b84fad3efatagen2026-09-27 03:59 UTC757
6f5b84fad3efatagen2026-09-27 03:59 UTC758
    let shared: Shared = Arc::new(Mutex::new(State::new(cfg)));
6f5b84fad3efatagen2026-09-27 03:59 UTC759
    {
6f5b84fad3efatagen2026-09-27 03:59 UTC760
        let shared = shared.clone();
6f5b84fad3efatagen2026-09-27 03:59 UTC761
        thread::spawn(move || tick(&shared));
6f5b84fad3efatagen2026-09-27 03:59 UTC762
    }
56a853ebf254atagen2026-09-27 03:59 UTC763
    #[cfg(target_os = "linux")]
56a853ebf254atagen2026-09-27 03:59 UTC764
    {
56a853ebf254atagen2026-09-27 03:59 UTC765
        let shared = shared.clone();
56a853ebf254atagen2026-09-27 03:59 UTC766
        thread::spawn(move || watch_config(&shared));
56a853ebf254atagen2026-09-27 03:59 UTC767
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC768
6f5b84fad3efatagen2026-09-27 03:59 UTC769
    for stream in listener.incoming() {
6f5b84fad3efatagen2026-09-27 03:59 UTC770
        let Ok(stream) = stream else { continue };
6f5b84fad3efatagen2026-09-27 03:59 UTC771
        lock(&shared).conns += 1;
6f5b84fad3efatagen2026-09-27 03:59 UTC772
        let shared = shared.clone();
6f5b84fad3efatagen2026-09-27 03:59 UTC773
        thread::spawn(move || {
6f5b84fad3efatagen2026-09-27 03:59 UTC774
            if let Err(e) = serve(&shared, stream) {
6f5b84fad3efatagen2026-09-27 03:59 UTC775
                eprintln!("roundabout: {e}");
6f5b84fad3efatagen2026-09-27 03:59 UTC776
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC777
            let mut st = lock(&shared);
6f5b84fad3efatagen2026-09-27 03:59 UTC778
            st.conns -= 1;
6f5b84fad3efatagen2026-09-27 03:59 UTC779
            if st.leases.is_empty() {
6f5b84fad3efatagen2026-09-27 03:59 UTC780
                st.idle_since = Instant::now();
6f5b84fad3efatagen2026-09-27 03:59 UTC781
            }
6f5b84fad3efatagen2026-09-27 03:59 UTC782
        });
6f5b84fad3efatagen2026-09-27 03:59 UTC783
    }
6f5b84fad3efatagen2026-09-27 03:59 UTC784
    process::exit(0)
6f5b84fad3efatagen2026-09-27 03:59 UTC785
}