stamparm/maltrail · error

just inserted

Error message

just inserted

What it means

Internal invariant assertion in DnsExhaustion::observe: after the HEURISTIC_MAX_KEYS guard, the code inserts a fresh Window for `domain` and immediately retrieves it with get_mut().expect("just inserted"). Failure is impossible under normal concurrency (the whole observe body is behind the same lock), so a panic means the map was mutated between insert and get or the insert silently failed.

Solutions

  1. Verify the insert and get_mut still happen under the same lock scope with no intervening mutation of self.domains
  2. Replace insert+get_mut with the entry() API, which cannot miss
  3. Re-check the HEURISTIC_MAX_KEYS guard still returns before the insert path is reached

Example fix

// before
self.domains.insert(domain.into(), Window { start: sec, subdomains: StrSet::default() });
self.domains.get_mut(domain).expect("just inserted")
// after
match self.domains.entry(domain.into()) {
    std::collections::hash_map::Entry::Vacant(v) => v.insert(Window { start: sec, subdomains: StrSet::default() }),
    std::collections::hash_map::Entry::Occupied(o) => o.into_mut(),
}
Defensive patterns

Strategy: type-guard

Type guard

fn window_mut<'m>(m: &'m mut HashMap<String, Window>, k: &str, sec: u64) -> &'m mut Window {
    m.entry(k.to_owned()).or_insert(Window { start: sec, subdomains: StrSet::default() })
}

Prevention

When it happens

Trigger: The domain entry is removed or overwritten between insert and get_mut — e.g. code added later that clears/retains self.domains inside the same locked section, or a refactor that swaps insert for entry-API misuse.

Common situations: Refactoring observe() (e.g. introducing an eviction pass or a second lock scope) that breaks the insert-then-get invariant; never triggered by DNS traffic itself.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/47aab83ad9f3f5a9. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/heuristics/dns_exhaustion.rs:81

    }

    /// Record `subdomain_part` under `domain` and report what the caller should do.
    /// `threshold` is passed in so tests can lower it, exactly like the Python test does.
    pub fn observe(&mut self, domain: &str, subdomain_part: &str, sec: u64, threshold: usize) -> Outcome {
        // NOTE: membership test, not truthiness - an existing-but-empty set (just cleared
        // at the 60s boundary) must keep its window start.
        let window = match self.domains.get_mut(domain) {
            Some(w) => w,
            None => {
                // Bounded: the hourly reset is a TIME bound, and the parent domain is chosen by
                // whoever sends the query. Refuse new domains at the cap instead of evicting
                // tracked ones, so a flood cannot push an in-progress window out of memory.
                if self.domains.len() >= super::HEURISTIC_MAX_KEYS {
                    self.saturations += 1;
                    return Outcome::Continue;
                }
                self.domains.insert(domain.into(), Window { start: sec, subdomains: StrSet::default() });
                self.domains.get_mut(domain).expect("just inserted")
            }
        };

        if sec.saturating_sub(window.start) > 60 {
            window.start = sec;
            window.subdomains.clear();
            return Outcome::Continue;
        }
        if window.subdomains.len() < threshold {
            // `contains` first: `insert(subdomain_part.into())` would build an owned copy of the
            // label on EVERY query just to throw it away as a duplicate. Real traffic queries the
            // same handful of subdomains over and over, so that was an allocation per DNS packet.
            if !window.subdomains.contains(subdomain_part) {
                window.subdomains.insert(subdomain_part.into());
            }
            return Outcome::Continue;
        }
        if !self.exhausted.contains(domain) {

View on GitHub (pinned to 77cfb06d76)