time_store.rs
1 //! Saving/loading timestamps to disk 2 //! 3 //! Storing timestamps on disk is not so straightforward. 4 //! We need to use wall clock time in order to survive restarts. 5 //! But wall clocks can be wrong, 6 //! so we need at least to apply some sanity checks. 7 //! 8 //! This module encapsulates those checks, and some error handling choices. 9 //! It allows `Instant`s to be used while the system is running, 10 //! with bespoke types for loading/saving. 11 //! See [`Loading::load_future`] for the load/save guarantees provided. 12 //! 13 //! The initial entrypoints are [`Storing::start`] and [`Loading::start`]. 14 //! 15 //! Granularity is 1 second and the precise rounding behaviour is not specified. 16 //! 17 //! ### Data model 18 //! 19 //! To mitigate clock skew, we store the wall clock time at which 20 //! each timestamp was saved to disk ([`Reference`]) 21 //! and the offset from now to that timestamp ([`FutureTimestamp`]). 22 //! 23 //! The same storage time can be used for multiple timestamps that are stored together. 24 //! 25 //! ### Example 26 //! 27 //! ``` 28 //! use serde::{Serialize, Deserialize}; 29 //! use std::time::{Duration, Instant}; 30 //! use tor_rtcompat::{PreferredRuntime, SleepProvider as _}; 31 //! 32 //! # use tor_hsservice::time_store_for_doctests_unstable_no_semver_guarantees as time_store; 33 //! # #[cfg(all)] // works like #[cfg(FALSE)]. Instead, we have this workaround ^. 34 //! use crate::time_store; 35 //! 36 //! let runtime = PreferredRuntime::create().unwrap(); 37 //! 38 //! #[derive(Serialize, Deserialize, Debug)] 39 //! struct Stored { 40 //! time_ref: time_store::Reference, 41 //! t0: time_store::FutureTimestamp, 42 //! } 43 //! 44 //! let t0: Instant = runtime.now() + Duration::from_secs(60); 45 //! 46 //! let storing = time_store::Storing::start(&runtime); 47 //! let data = Stored { 48 //! time_ref: storing.store_ref(), 49 //! t0: storing.store_future(t0), 50 //! }; 51 //! 52 //! let json = serde_json::to_string(&data).unwrap(); 53 //! 54 //! // later: 55 //! 56 //! let data: Stored = serde_json::from_str(&json).unwrap(); 57 //! let loading = time_store::Loading::start(&runtime, data.time_ref); 58 //! let t0: Instant = loading.load_future(data.t0); 59 //! 60 //! assert!(t0 - runtime.now() <= Duration::from_secs(60)); 61 //! ``` 62 //! 63 //! ### Time arithmetic overflows and stupid system time settings 64 //! 65 //! Arithmetic is done with signed 64-bit numbers of seconds. 66 //! So overflow cannot occur unless the clock is completely ludicrous. 67 //! If the clock is ludicrous, time calculations are going to be a mess. 68 //! We treat this as clock skew, using saturating arithmetic, rather than returning errors. 69 //! Reasonable operation will resume when the clock becomes sane. 70 // 71 // We generally use u64 for values that can't, for our algorithms, be negative, 72 // but i64 for time_t's (even though negative time_t's can't happen on Unix). 73 74 // TODO - eventually we hope this will become pub, in another crate 75 76 // Rustdoc can complains if we link to these private docs from these docs which are 77 // themselves only formatted with --document-private-items. 78 // TODO - Remove when this is actually public 79 #![allow(rustdoc::private_intra_doc_links)] 80 81 use std::fmt::{self, Display}; 82 use std::str::FromStr; 83 use std::time::{Duration, Instant, SystemTime}; 84 85 use derive_deftly::{define_derive_deftly, Deftly}; 86 use serde::{Deserialize, Serialize}; 87 use serde::{Deserializer, Serializer}; 88 use thiserror::Error; 89 use tracing::warn; 90 91 use tor_rtcompat::SleepProvider; 92 93 //---------- derive-deftly macro for raw accessors, must come first ---------- 94 95 define_derive_deftly! { 96 /// Define `as_raw` and `from_raw` methods (for a struct with a single field) 97 // 98 // We provide these for the types which are serde, since we are already exposing 99 // and documenting their innards (and we don't want to force people to use serde 100 // trickery if they want to do something unusual). 101 RawConversions expect items: 102 103 impl $ttype { 104 ${for fields { // we have only one field; but d-a wants a context for "a specific field" 105 /// Returns the raw value, as would be serialised 106 pub fn as_raw(self) -> $ftype { 107 self.$fname 108 } 109 #[doc = concat!("/// Constructs a ",stringify!($tname)," from a raw value")] 110 pub fn from_raw(seconds: $ftype) -> $ttype { 111 Self { $fname: seconds } 112 } 113 }} 114 } 115 } 116 117 define_derive_deftly! { 118 /// Define [`Serialize`] and [`Deserialize`] via string rep or transparently, depending 119 /// 120 /// In human-readable formats, uses the [`Display`] and [`FromStr`]. 121 /// In non-human-readable formats, serialises as the single field. 122 /// 123 /// Uses serde's `is_human_readable` to decide. 124 /// structs which don't have exactly one field will cause a compile error. 125 // 126 // This has to be a macro rather than simply a helper newtype 127 // to implement the "transparent" binary version, 128 // since that involves looking into the struct's field. 129 SerdeStringOrTransparent for struct, expect items: 130 131 impl Serialize for $ttype { 132 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { 133 if s.is_human_readable() { 134 s.collect_str(self) 135 } else { 136 let Self { $( $fname: raw, ) } = self; 137 raw.serialize(s) 138 } 139 } 140 } 141 142 ${define STRING_VISITOR { $<Deserialize $ttype StringVisitor> }} 143 144 impl<'de> Deserialize<'de> for $ttype { 145 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> { 146 if d.is_human_readable() { 147 d.deserialize_str($STRING_VISITOR) 148 } else { 149 let raw = Deserialize::deserialize(d)?; 150 Ok(Self { $( $fname: raw, ) }) 151 } 152 } 153 } 154 155 /// Visitor for deserializing from a string 156 struct $STRING_VISITOR; 157 158 impl<'de> serde::de::Visitor<'de> for $STRING_VISITOR { 159 type Value = $ttype; 160 fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<$ttype, E> { 161 s.parse().map_err(|e| E::custom(e)) 162 } 163 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 164 write!(f, concat!("string representing ", stringify!($tname))) 165 } 166 } 167 } 168 169 //---------- data types ---------- 170 171 /// Representation of an absolute time, in the future, suitable for storing to disk 172 /// 173 /// Only meaningful in combination with a [`Reference`]. 174 /// 175 /// Obtain one of these from an `Instant` using [`Storing::store_future()`], 176 /// and convert it back to an `Instant` with [`Loading::load_future()`], 177 /// 178 /// (Serialises as a representation of how many seconds this was into the future, 179 /// when it was stored - ie, with respect to the corresponding [`Reference`]; 180 /// in binary as a `u64`, in human readable formats as 181 /// `T+` plus [`humantime`]'s formatting of the `Duration` in seconds.) 182 #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)] 183 #[derive_deftly(RawConversions, SerdeStringOrTransparent)] 184 pub struct FutureTimestamp { 185 /// How far this timestamp was in the future, when we stored it 186 offset: u64, 187 } 188 189 /// On-disk representation of a reference time, used as context for stored timestamps 190 /// 191 /// During store, obtained by [`Storing::store_ref`], and should then be serialised 192 /// along with the [`FutureTimestamp`]s. 193 /// 194 /// During load, should be passed to [`Loading::start`], to build a [`Loading`] 195 /// which is then used to convert the [`FutureTimestamp`]s back to `Instant`s. 196 /// 197 /// (Serialises as an absolute time: 198 /// in binary, as an `i64` representing the `time_t` (Unix Time); 199 /// in human-readable formats, an RFC3339 string with seconds precision and timezone `Z`.) 200 #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, derive_more::Display)] 201 #[display( 202 "{}", 203 humantime::format_rfc3339_seconds(time_t_to_system_time(*time_t)) 204 )] 205 #[derive(Deftly)] 206 #[derive_deftly(RawConversions, SerdeStringOrTransparent)] 207 pub struct Reference { 208 /// Unix time (at which the other timestamps were stored) 209 time_t: i64, 210 } 211 212 /// Context for storing `Instant`s to disk 213 /// 214 /// Obtained from [`Storing::start`]. 215 /// 216 /// Contains a reference time, which must also be serialised, 217 /// as `Reference` obtained from [`Storing::store_ref`], 218 /// and stored alongside the converted timestamps. 219 pub struct Storing(Now); 220 221 /// The two notions of the current time, for internal use 222 // 223 // This is a separate type from Storing so that Loading::start can call Now::new, 224 // without having to temporarily create a semantically inappropriate Storing. 225 struct Now { 226 /// Represents the current time as an opaque `Instant` 227 inst: Instant, 228 /// Represents the current time as a `time_t` 229 time_t: i64, 230 } 231 232 /// Context for loading `Instant`s from disk 233 /// 234 /// Obtained by [`Loading::start`], 235 /// from a [`Reference`] 236 /// (loaded from disk alongside the converted timestamps). 237 pub struct Loading { 238 /// The current time when this `Loading` was created 239 inst: Instant, 240 /// How long has elapsed, at `inst`, since the timestamps were stored 241 elapsed: u64, 242 } 243 244 //---------- implementation ---------- 245 246 /// Convert a `SystemTime` to an `i64` `time_t` 247 fn system_time_to_time_t(st: SystemTime) -> i64 { 248 if let Ok(d) = st.duration_since(SystemTime::UNIX_EPOCH) { 249 d.as_secs().try_into().unwrap_or(i64::MAX) 250 } else if let Ok(d) = SystemTime::UNIX_EPOCH.duration_since(st) { 251 d.as_secs().try_into().map(|v: i64| -v).unwrap_or(i64::MIN) 252 } else { 253 panic!("two SystemTimes are neither <= nor >=") 254 } 255 } 256 257 /// Minimum value of SystemTime 258 /// 259 /// Not a tight bound but tests guarantee no runtime panics. 260 fn system_time_min() -> SystemTime { 261 for attempt in [ 262 // 263 0x7fff_ffff_ffff_ffff, 264 0x7fff_ffff, 265 0, 266 ] { 267 if let Some(r) = SystemTime::UNIX_EPOCH.checked_sub(Duration::from_secs(attempt)) { 268 return r; 269 } 270 } 271 panic!("cannot calculate a minimum value for the SystemTime!"); 272 } 273 274 /// Maximum value of SystemTime 275 /// 276 /// Not a tight bound but tests guarantee no runtime panics. 277 fn system_time_max() -> SystemTime { 278 for attempt in [ 279 // 280 0x7fff_ffff_ffff_ffff, 281 0x7fff_ffff, 282 ] { 283 if let Some(r) = SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(attempt)) { 284 return r; 285 } 286 } 287 panic!("cannot calculate a maximum value for the SystemTime!"); 288 } 289 290 /// Convert a `SystemTime` to an `i64` `time_t` 291 // 292 // We need this to make the RFC3339 string using humantime. 293 // Otherwise we'd have to implement the whole calendar and leap days stuff ourselves. 294 // Probably there doesn't end up being any actual code here on Unix at least... 295 fn time_t_to_system_time(time_t: i64) -> SystemTime { 296 if let Ok(time_t) = u64::try_from(time_t) { 297 let d = Duration::from_secs(time_t); 298 SystemTime::UNIX_EPOCH 299 .checked_add(d) 300 .unwrap_or_else(system_time_max) 301 } else { 302 let rev: u64 = time_t 303 .saturating_neg() 304 .try_into() 305 .expect("-(-ve i64) not u64"); 306 let d = Duration::from_secs(rev); 307 SystemTime::UNIX_EPOCH 308 .checked_sub(d) 309 .unwrap_or_else(system_time_min) 310 } 311 } 312 313 impl Now { 314 /// Obtain `Now` from a runtime 315 fn new(runtime: &impl SleepProvider) -> Self { 316 let inst = runtime.now(); 317 let st = runtime.wallclock(); 318 let time_t = system_time_to_time_t(st); 319 Self { inst, time_t } 320 } 321 } 322 323 impl Storing { 324 /// Prepare to store timestamps, returning a context for serialising `Instant`s 325 /// 326 /// Incorporates reference times obtained from the runtime's clock. 327 // 328 // We don't provide `Storing::{to,from}_raw_parts` nor `Loading::from_raw_parts` 329 // because you can (sort of) do all of constructors with a suitable `SleepProvider`, 330 // and we don't want to give too much detail about the implementation and innards. 331 pub fn start(runtime: &impl SleepProvider) -> Self { 332 Storing(Now::new(runtime)) 333 } 334 335 /// Prepare a reference time for storage 336 pub fn store_ref(&self) -> Reference { 337 Reference { 338 time_t: self.0.time_t, 339 } 340 } 341 342 /// Convert an `Instant` in the future into a form suitable for saving on disk 343 /// 344 /// If `val` *isn't* in the future, the current time will be stored instead. 345 pub fn store_future(&self, val: Instant) -> FutureTimestamp { 346 let offset = val 347 .checked_duration_since(self.0.inst) 348 .unwrap_or_default() 349 .as_secs(); 350 FutureTimestamp { offset } 351 } 352 } 353 354 impl Loading { 355 /// Obtain a [`Loading`] from a stored [`Reference`], for deserialising `Instant`s 356 /// 357 /// Uses the runtime's clock as a basis for understanding the supplied `Reference` 358 /// and relating it to the current monotonic time (`Instant`) on this host. 359 pub fn start(runtime: &impl SleepProvider, stored: Reference) -> Loading { 360 let now = Now::new(runtime); 361 let elapsed = now.time_t.saturating_sub(stored.time_t); 362 // If time went backwards, pretend it stood still 363 let elapsed = elapsed.try_into().unwrap_or(0); 364 Loading { 365 inst: now.inst, 366 elapsed, 367 } 368 } 369 370 /// Convert a future `Instant` from a value saved on disk 371 /// 372 /// If the `Instant` that was saved has since passed, 373 /// the returned value is the current time. 374 /// 375 /// If the system clock is inaccurate (or was inaccurate when the timestamp was saved), 376 /// the value may be wrong: 377 /// but, regardless, the returned value will be no further in the future, 378 /// than how far it was in the future when it was saved. 379 /// 380 /// In other words, even in the presence of clock skew, the effect is, at worst, 381 /// as if the local system's clock has stood still, or has run very fast. 382 pub fn load_future(&self, stored: FutureTimestamp) -> Instant { 383 let offset = stored.offset.saturating_sub(self.elapsed); 384 self.inst 385 .checked_add(Duration::from_secs(offset)) 386 .unwrap_or_else(|| { 387 warn!("time overflow loading time_t now+{offset}!"); 388 // `Instant` is overflowing, which can only happen if something is 389 // very wrong with the system, or `stored.offset` was stupidly large. 390 // Using "now" is clearly wrong but there is no Instant::MAX, 391 // and this seems better than making this method fallible and bailing. 392 self.inst 393 }) 394 } 395 396 //----- accessors for Loading ----- 397 398 /// Returns how long has elapsed since the timestamps were stored 399 /// 400 /// This depends on the system wall clock being right both when we stored, and now. 401 /// In the presence of clock skew, may return a value which is far too large, 402 /// or too small. 403 /// 404 /// But, if the system wall clock seems to have gone backwards, returns zero. 405 /// 406 /// The time is measured from when [`start`](Loading::start) was called. 407 /// If you need to know that time as an `Instant`, 408 /// use [`as_raw_parts`](Loading::as_raw_parts). 409 // 410 // We provide this (and `as_raw_parts`) because some callers may 411 // actually have a good use for it. 412 pub fn elapsed(&self) -> Duration { 413 Duration::from_secs(self.elapsed) 414 } 415 416 /// Returns how long has elapsed, in seconds, and the `Instant` at which that was true 417 /// 418 /// Returns number of seconds elapsed, 419 /// between when the timestamps were stored, 420 /// and the returned `Instant`. 421 /// 422 /// See [`elapsed`](Loading::elapsed) for details of clock skew handling. 423 pub fn as_raw_parts(&self) -> (u64, Instant) { 424 (self.elapsed, self.inst) 425 } 426 } 427 428 //---------- formatting ---------- 429 430 /// Displays as `T+Duration`, where [`Duration`] is in seconds, formatted using [`humantime`] 431 // 432 // This format is a balance between human-readability and the desire to avoid 433 // allowing the possibility of invalid (corrupted) files whose `FutureTimestamp` 434 // is actually before the stored `Reference`. 435 impl Display for FutureTimestamp { 436 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 437 let d = humantime::format_duration(Duration::from_secs(self.offset)); 438 write!(f, "T+{}", d) 439 } 440 } 441 442 /// Error parsing a timestamp or reference 443 #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 444 #[non_exhaustive] 445 #[derive(Error)] 446 #[error("invalid timestamp or reference time format")] 447 pub struct ParseError { 448 // We probably don't need to say precisely what's wrong 449 } 450 451 impl FromStr for FutureTimestamp { 452 type Err = ParseError; 453 // Bespoke parser so we have control over our error/overflow cases 454 // (and also since ideally we don't want to deal with a complex HMS time API). 455 fn from_str(s: &str) -> Result<Self, Self::Err> { 456 let s = s.strip_prefix("T+").ok_or(ParseError {})?; 457 let offset = humantime::parse_duration(s) 458 .map_err(|_: humantime::DurationError| ParseError {})? 459 .as_secs(); 460 Ok(FutureTimestamp { offset }) 461 } 462 } 463 464 impl FromStr for Reference { 465 type Err = ParseError; 466 fn from_str(s: &str) -> Result<Self, Self::Err> { 467 let st = humantime::parse_rfc3339(s).map_err(|_| ParseError {})?; 468 let time_t = system_time_to_time_t(st); 469 Ok(Reference { time_t }) 470 } 471 } 472 473 #[cfg(test)] 474 mod test { 475 // @@ begin test lint list maintained by maint/add_warning @@ 476 #![allow(clippy::bool_assert_comparison)] 477 #![allow(clippy::clone_on_copy)] 478 #![allow(clippy::dbg_macro)] 479 #![allow(clippy::mixed_attributes_style)] 480 #![allow(clippy::print_stderr)] 481 #![allow(clippy::print_stdout)] 482 #![allow(clippy::single_char_pattern)] 483 #![allow(clippy::unwrap_used)] 484 #![allow(clippy::unchecked_duration_subtraction)] 485 #![allow(clippy::useless_vec)] 486 #![allow(clippy::needless_pass_by_value)] 487 //! <!-- @@ end test lint list maintained by maint/add_warning @@ --> 488 use super::*; 489 use humantime::parse_rfc3339; 490 use itertools::{chain, Itertools}; 491 use tor_rtmock::{simple_time::SimpleMockTimeProvider, MockRuntime}; 492 493 fn secs(s: u64) -> Duration { 494 Duration::from_secs(s) 495 } 496 497 #[test] 498 fn hms_fmt() { 499 let ft = FutureTimestamp { 500 offset: 2 * 86400 + 22 * 3600 + 4 * 60 + 5, 501 }; 502 503 assert_eq!(ft.to_string(), "T+2days 22h 4m 5s"); 504 505 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+2days 22h 4m 5s")); 506 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+70h 4m 5s")); 507 // we inherit rounding behaviour from humantime; we don't mind tolerating that 508 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+2days 22h 4m 5s 100ms")); 509 assert_eq!(Ok(ft), FutureTimestamp::from_str("T+2days 22h 4m 5s 900ms")); 510 let e = Err(ParseError {}); 511 assert_eq!(e, FutureTimestamp::from_str("2days")); 512 assert_eq!(e, FutureTimestamp::from_str("T+")); 513 assert_eq!(e, FutureTimestamp::from_str("T+ ")); 514 assert_eq!(e, FutureTimestamp::from_str("T+X")); 515 assert_eq!(e, FutureTimestamp::from_str("T+23kg")); 516 } 517 518 #[test] 519 #[allow(clippy::unusual_byte_groupings)] // we want them to line up, dammit! 520 fn system_time_conversions() { 521 assert!(system_time_min() <= SystemTime::UNIX_EPOCH); 522 assert!(system_time_max() > SystemTime::UNIX_EPOCH); 523 524 let p = |s| parse_rfc3339(s).expect(s); 525 let time_t_2st = time_t_to_system_time; 526 assert_eq!(p("1970-01-01T00:00:00Z"), time_t_2st(0)); 527 assert_eq!(p("2038-01-19T03:14:07Z"), time_t_2st(0x___7fff_ffff)); 528 assert_eq!(p("2038-01-19T03:14:08Z"), time_t_2st(0x___8000_0000)); 529 assert_eq!(p("2106-02-07T06:28:16Z"), time_t_2st(0x_1_0000_0000)); 530 assert_eq!(p("4147-08-20T07:32:16Z"), time_t_2st(0x10_0000_0000)); 531 } 532 533 #[test] 534 fn ref_fmt() { 535 let time_t = 1217635200; 536 let rf = Reference { time_t }; 537 let s = "2008-08-02T00:00:00Z"; 538 assert_eq!(rf.to_string(), s); 539 540 let p = Reference::from_str; 541 assert_eq!(Ok(rf), p(s)); 542 // we inherit rounding behaviour from humantime; we don't mind tolerating that 543 assert_eq!(Ok(rf), p("2008-08-02T00:00:00.00Z")); 544 assert_eq!(Ok(rf), p("2008-08-02T00:00:00.999Z")); 545 let e = Err(ParseError {}); 546 assert_eq!(e, p("2008-08-02T00:00Z")); 547 assert_eq!(e, p("2008-08-02T00:00:00")); 548 assert_eq!(e, p("2008-08-02T00:00:00B")); 549 assert_eq!(e, p("2008-08-02T00:00:00+00:00")); 550 } 551 552 #[test] 553 fn basic() { 554 #[derive(Serialize, Deserialize, Debug)] 555 struct Stored { 556 stored: Reference, 557 s0: FutureTimestamp, 558 s1: FutureTimestamp, 559 s2: FutureTimestamp, 560 } 561 562 let real_instant = Instant::now(); 563 let test_systime = parse_rfc3339("2008-08-02T00:00:00Z").unwrap(); 564 565 let mk_runtime = |instant, systime| { 566 let times = SimpleMockTimeProvider::new(instant, systime); 567 MockRuntime::builder().sleep_provider(times).build() 568 }; 569 570 let stored = { 571 let runtime = mk_runtime(real_instant + secs(100_000), test_systime); 572 let now = Storing::start(&runtime); 573 574 let t0 = runtime.now() - secs(1000); 575 let t1 = runtime.now() + secs(10); 576 let t2 = runtime.now() + secs(3000); 577 578 Stored { 579 stored: now.store_ref(), 580 s0: now.store_future(t0), 581 s1: now.store_future(t1), 582 s2: now.store_future(t2), 583 } 584 }; 585 586 let json = serde_json::to_string(&stored).unwrap(); 587 println!("{json}"); 588 assert_eq!( 589 json, 590 format!(concat!( 591 r#"{{"stored":"2008-08-02T00:00:00Z","#, 592 r#""s0":"T+0s","s1":"T+10s","s2":"T+50m"}}"# 593 )) 594 ); 595 596 let mpack = rmp_serde::to_vec_named(&stored).unwrap(); 597 println!("{}", hex::encode(&mpack)); 598 assert_eq!( 599 mpack, 600 chain!( 601 &[132, 166], 602 b"stored", 603 &[206], 604 &[0x48, 0x93, 0xa3, 0x80], // 0x4893a380 1217635200 = 2008-08-02T00:00:00Z 605 &[162], 606 b"s0", 607 &[0], 608 &[162], 609 b"s1", 610 &[10], 611 &[162], 612 b"s2", 613 &[205], 614 &[0x0b, 0xb8], // 0xbb8 == 3000 615 ) 616 .cloned() 617 .collect_vec(), 618 ); 619 620 // Simulate a restart with an Instant which is *smaller* (maybe the host rebooted), 621 // but with a wall clock time 200s later. 622 { 623 let runtime = mk_runtime(real_instant, test_systime + secs(200)); 624 let now = Loading::start(&runtime, stored.stored); 625 626 let t0 = now.load_future(stored.s0); 627 let t1 = now.load_future(stored.s1); 628 let t2 = now.load_future(stored.s2); 629 630 assert_eq!(t0, runtime.now()); // was already in the past when stored 631 assert_eq!(t1, runtime.now()); // is now in the past 632 assert_eq!(t2, runtime.now() + secs(2800)); 633 } 634 635 // Simulate a restart with a later Instant 636 // and with a wall clock time 1200s *earlier* due to clock skew. 637 { 638 let runtime = mk_runtime(real_instant + secs(200_000), test_systime - secs(1200)); 639 let now = Loading::start(&runtime, stored.stored); 640 641 let t0 = now.load_future(stored.s0); 642 let t1 = now.load_future(stored.s1); 643 let t2 = now.load_future(stored.s2); 644 645 assert_eq!(t0, runtime.now()); // was already in the past when stored 646 assert_eq!(t1, runtime.now() + secs(10)); // well, it was only 10 even then 647 assert_eq!(t2, runtime.now() + secs(3000)); // can't be increased 648 } 649 } 650 }