liminal-server 0.25.4

Standalone server for the liminal messaging bus
Documentation
//! Kept tap records are published and failed records retained.

use super::{
    ConsoleError, ConsoleFailure, ConsoleHub, PumpReport, ServerPublisher, TAP_SUBJECT_PREFIX, add,
    canonical_bytes, failed, len_u64, lock, pending_total, set, tap_batch_record,
};

impl ConsoleHub {
    pub(super) fn pump_taps(
        &self,
        out: &dyn ServerPublisher,
        report: &mut PumpReport,
    ) -> Result<(), ConsoleError> {
        let mut due = Vec::new();
        // R9: one tap's failure is kept and returned after every other tap
        // has been drained and published; it never drops what they took.
        let mut first_error = None;
        let mut taps = lock(&self.taps, "console taps")?;
        for (subject, entry) in &mut taps.live {
            if entry.pending.is_empty() {
                let target = subject.strip_prefix(TAP_SUBJECT_PREFIX).unwrap_or(subject);
                #[cfg(test)]
                let taken = self.take_batches(subject, &entry.subscription);
                #[cfg(not(test))]
                let taken = entry.subscription.take_batches();
                match taken {
                    Ok(batches) => {
                        for batch in batches {
                            let encoded =
                                tap_batch_record(&self.device, target, &batch, &self.wall)
                                    .and_then(|record| canonical_bytes(&record, "tap.batch"));
                            match encoded {
                                Ok(bytes) => entry.pending.push_back(bytes),
                                Err(error) => {
                                    // Taken from the ring and not encodable:
                                    // the one loss besides eviction, counted.
                                    failed(
                                        &self.counts.console_records_lost,
                                        1,
                                        ConsoleFailure::ConsoleRecordsLost,
                                    );
                                    first_error.get_or_insert(error);
                                }
                            }
                        }
                    }
                    Err(error) => {
                        first_error.get_or_insert_with(|| error.into());
                    }
                }
            }
            if !entry.pending.is_empty() {
                due.push((
                    subject.clone(),
                    entry.generation,
                    std::mem::take(&mut entry.pending),
                ));
            }
        }
        drop(taps);

        let mut unsent = Vec::new();
        for (subject, generation, mut records) in due {
            while let Some(bytes) = records.front() {
                if !self.send(out, &subject, bytes) {
                    report.publish_failures += 1;
                    unsent.push((subject, generation, records));
                    break;
                }
                records.pop_front();
                add(&self.counts.tap_records_published, 1);
                report.tap_records += 1;
            }
        }

        let mut taps = lock(&self.taps, "console taps")?;
        for (subject, generation, records) in unsent {
            // Only to the tap they were taken from: one that detached
            // meanwhile, or was attached anew, has no reader for them.
            match taps.live.get_mut(&subject) {
                Some(entry) if entry.generation == generation => entry.pending = records,
                _ => add(&self.counts.tap_records_abandoned, len_u64(records.len())),
            }
        }
        let pending = pending_total(&taps.live);
        drop(taps);
        set(&self.counts.console_pending_records, pending);
        first_error.map_or(Ok(()), Err)
    }

    pub(super) fn retire(
        &self,
        out: &dyn ServerPublisher,
        report: &mut PumpReport,
    ) -> Result<(), ConsoleError> {
        let mut retiring = std::mem::take(&mut lock(&self.taps, "console taps")?.retiring);
        while let Some(subject) = retiring.first() {
            // The check and the roster removal share the taps lock, taken
            // before the roster's, so no subscribe can reserve or attach the
            // tap between them and find its channel gone (SRV-012 R6).
            let mut taps = lock(&self.taps, "console taps")?;
            // Attached or reserved again since it was queued: it stays.
            if taps.live.contains_key(subject) || taps.reserved.contains_key(subject) {
                drop(taps);
                retiring.remove(0);
                continue;
            }
            if let Err(error) = out.retire_console_channel(subject) {
                // Kept for the next pump unless it was attached again since.
                retiring.retain(|subject| !taps.live.contains_key(subject));
                retiring.append(&mut taps.retiring);
                taps.retiring = retiring;
                drop(taps);
                failed(
                    &self.counts.tap_detach_failures,
                    1,
                    ConsoleFailure::TapDetach,
                );
                return Err(error);
            }
            drop(taps);
            retiring.remove(0);
            report.retired += 1;
        }
        Ok(())
    }
}