vi/websocat · warning

Time went backwards

Error message

Time went backwards

What it means

When reading from this timestamping peer, the code stamps each chunk with the current time. If self.1 (a captured baseline Instant) is None, it converts SystemTime::now() from UNIX_EPOCH with an expect. On platforms where the wall clock is adjusted (NTP step, manual set, virtual machine resume, boot with RTC unset) SystemTime can appear to be before the epoch, and duration_since returns Err — the expect turns that into a panic aborting the read.

Solutions

  1. Fix the system clock (run NTP/chrony/ntpd) so it is after the Unix epoch, then restart the process
  2. Prefer the monotonic path: always seed self.1 with an Instant baseline at peer creation so the expect branch is never reached
  3. Replace the expect with handling of the Err case, e.g. default to 0.0 seconds or return an io::ErrorKind::Other instead of panicking
  4. Use Instant::now() exclusively for the timestamp if absolute wall-clock time is not required

Example fix

// before
(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards")).as_secs_f64()
// after
SystemTime::now().duration_since(UNIX_EPOCH)
    .unwrap_or_default() // clock before epoch: stamp 0.0 instead of panicking
    .as_secs_f64()
Defensive patterns

Strategy: fallback

Validate before calling

// check clock before starting timestamped reads
let ok = SystemTime::now().duration_since(UNIX_EPOCH).is_ok();
if !ok { eprintln!("system clock is before the Unix epoch; fix time sync"); }

Type guard

fn clock_is_sane() -> bool {
    SystemTime::now().duration_since(UNIX_EPOCH).is_ok()
}

Try / catch

// panic cannot be caught by Result; fence the read loop or fix the environment
assert!(clock_is_sane(), "clock before epoch — enable NTP before running");
// or run the reader under catch_unwind as a stopgap
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| read_loop(&mut peer)));

Prevention

When it happens

Trigger: System clock set to a time before 1970-01-01 (or the OS reporting such a time) while this peer reads data and no basetime was recorded; also triggered by clock stepping during container/VM start when the RTC is invalid.

Common situations: Embedded boards or VMs with dead RTC batteries booting with clock at 0 or negative-adjusted values; NTP forcing a large backwards step mid-session; Docker containers started before the host syncs its clock.

Related errors


AI-assisted analysis of vi/websocat@3a3574cd2f (2026-09-12). Data as JSON: /api/errors/0359a7db09ecedf7. Report an issue: GitHub.

Appendix: source

Thrown at src/timestamp_peer.rs:63

struct TimestampWrapper(Box<dyn AsyncRead>, Option<Instant>);

impl Read for TimestampWrapper {
    fn read(&mut self, b: &mut [u8]) -> Result<usize, IoError> {
        let l = b.len();
        assert!(l > 1);
        let n = self.0.read(&mut b[..l])?;
        if n == 0 {
            return Ok(0);
        }

        let mut v: Vec<u8> = Vec::with_capacity(n + 50);
        {
            let mut vv = ::std::io::Cursor::new(&mut v);
            use std::io::Write;
            let x = if let Some(basetime) = self.1 {
                Instant::now().duration_since(basetime).as_secs_f64()
            } else {
                (SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards")).as_secs_f64()
            };
            let _ = write!(vv, "{} ", x);
            let _ = vv.write_all(&b[..n]);
        }
        
        if v.len() > l {
            warn!("Buffer too small, timstamp-prepended message may be truncated.");
        }
        let ll = v.len().min(l);
        b[..ll].copy_from_slice(&v[..ll]);
        Ok(ll)
    }
}
impl AsyncRead for TimestampWrapper {}

View on GitHub (pinned to 3a3574cd2f)