Skip to content

Use time-paused tokio for zero sleeps#309

Merged
carlaKC merged 4 commits into
bitcoin-dev-project:mainfrom
carlaKC:des-tokio
Jul 8, 2026
Merged

Use time-paused tokio for zero sleeps#309
carlaKC merged 4 commits into
bitcoin-dev-project:mainfrom
carlaKC:des-tokio

Conversation

@carlaKC

@carlaKC carlaKC commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Our current solution to waits is to use a sped up wall clock, which has all sorts of limitations.

Using a paused, single-threaded tokio runtime we can get discrete-event-like functionality without doing a large refactor.

carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jun 23, 2026
Switch from sim-ln's old wall-clock SimulationClock (a fixed u16 speedup that
still slept on real time) to its reimplemented SimulationClock, which advances
virtual time on a paused single-threaded runtime. A run now completes as fast as
the CPU allows instead of sleeping on real time, and is reproducible for a fixed
seed.

Tracks bitcoin-dev-project/sim-ln#309.

- Pin simln-lib/sim-cli to the PR head.
- Drive all three binaries through runtime::block_on_sped_up_clock, which owns
  the paused runtime; main() is therefore synchronous.
- Reimplement the local InstantClock trait on tokio::time::Instant so that
  elapsed time tracks virtual time (no ln-resource-mgr changes required).
- Seed the latency interceptor from a shared SIM_SEED, matching sim-ln's seeded
  RNG.
- Remove the obsolete --clock-speedup multiplier flag; SimulationClock no longer
  takes one.
- Bound the run at one virtual year as a safeguard, since virtual time has no
  wall-clock limit if an attack never triggers shutdown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@carlaKC carlaKC force-pushed the des-tokio branch 5 times, most recently from bd2a169 to 2b7c4d5 Compare June 25, 2026 20:15
@carlaKC carlaKC marked this pull request as ready for review June 25, 2026 20:16
carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jun 25, 2026
Switch from sim-ln's old wall-clock SimulationClock (a fixed u16 speedup that
still slept on real time) to its reimplemented SimulationClock, which advances
virtual time on a paused single-threaded runtime. A run now completes as fast as
the CPU allows instead of sleeping on real time, and is reproducible for a fixed
seed.

Tracks bitcoin-dev-project/sim-ln#309.

- Pin simln-lib/sim-cli to the PR head, enabling simln-lib's virtual-time
  feature (required for runtime::block_on_virtual_time, which pulls in tokio's
  test-util to drive the paused runtime).
- Drive all three binaries through runtime::block_on_virtual_time, which owns
  the paused runtime; main() is therefore synchronous.
- Reimplement the local InstantClock trait on tokio::time::Instant so that
  elapsed time tracks virtual time (no ln-resource-mgr changes required).
- Seed the simulation config and latency interceptor from a shared SIM_SEED so
  that payment generation and sampled delays are reproducible.
- Remove the obsolete --clock-speedup multiplier flag; SimulationClock no longer
  takes one.
- Bound the run at one virtual year as a safeguard, since virtual time has no
  wall-clock limit if an attack never triggers shutdown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@carlaKC carlaKC changed the title [WIP]: use time-paused tokio for zero sleeps Use time-paused tokio for zero sleeps Jun 25, 2026
@carlaKC carlaKC requested a review from elnosh June 26, 2026 13:35
carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jun 26, 2026
Move off sim-ln's old wall-clock SimulationClock (a fixed speedup multiplier
that still slept on real time) onto its reimplemented virtual-time
SimulationClock, which advances time on a paused single-threaded runtime so a
run completes as fast as the CPU allows.

- Pin simln-lib/sim-cli to the virtual-time PR head and enable simln-lib's
  virtual-time feature, which provides runtime::block_on_virtual_time.
- Drive all three binaries through block_on_virtual_time, which owns the paused
  runtime and hands the simulation its clock; main() is therefore synchronous.
- Adopt the new SimulationClock::new(start_time) constructor in place of the
  speedup-multiplier form across the binaries, tests and graph setup.
- Reimplement the local InstantClock trait on tokio::time::Instant so that
  elapsed time tracks virtual time.
- Pass None to the latency interceptor's new seed parameter for now; seeding
  follows in a later commit.

Tracks bitcoin-dev-project/sim-ln#309.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jun 26, 2026
Pin simln-lib/sim-cli to the virtual-time PR head (enabling simln-lib's
virtual-time feature) and adapt to the reimplemented SimulationClock:

- Construct the clock with the new SimulationClock::new(start_time) signature in
  place of the speedup multiplier, across the binaries, tests and graph setup.
- Reimplement the local InstantClock trait on tokio::time::Instant so elapsed
  time tracks virtual time.
- Pass the latency interceptor's new seed parameter (None for now; seeding
  follows in a later commit).

The binaries still build their own clock and run on a standard runtime; moving
them onto the paused virtual-time runtime happens next.

Tracks bitcoin-dev-project/sim-ln#309.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jun 26, 2026
Pin simln-lib/sim-cli to the virtual-time PR head (enabling simln-lib's
virtual-time feature) and adapt to the reimplemented SimulationClock:

- Construct the clock with the new SimulationClock::new(start_time) signature in
  place of the speedup multiplier, across the binaries, tests and graph setup.
- Reimplement the local InstantClock trait on tokio::time::Instant so elapsed
  time tracks virtual time.
- Pass the latency interceptor's new seed parameter (None for now; seeding
  follows in a later commit).

The binaries still build their own clock and run on a standard runtime; moving
them onto the paused virtual-time runtime happens next.

Tracks bitcoin-dev-project/sim-ln#309.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@elnosh elnosh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ran a couple of simulations and it is indeed faster - a 6-month simulation that previously took hours now ran in 27 mins. However, I did run into an issue explained here carlaKC/jam-ln#133 (review) which is that any polling loop could cause issues if the comments on clock.rs are accurate

If the system never reaches the state where all tasks are waiting, the clock will not
advance. This may happen if there is an always-pollable loop, for example.

carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jul 7, 2026
Pin simln-lib/sim-cli to the virtual-time PR head (enabling simln-lib's
virtual-time feature) and adapt to the reimplemented SimulationClock:

- Construct the clock with the new SimulationClock::new(start_time) signature in
  place of the speedup multiplier, across the binaries, tests and graph setup.
- Reimplement the local InstantClock trait on tokio::time::Instant so elapsed
  time tracks virtual time.
- Pass the latency interceptor's new seed parameter (None for now; seeding
  follows in a later commit).

The binaries still build their own clock and run on a standard runtime; moving
them onto the paused virtual-time runtime happens next.

Tracks bitcoin-dev-project/sim-ln#309.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jul 7, 2026
Pin simln-lib/sim-cli to the virtual-time PR head (enabling simln-lib's
virtual-time feature) and adapt to the reimplemented SimulationClock:

- Construct the clock with the new SimulationClock::new(start_time) signature in
  place of the speedup multiplier, across the binaries, tests and graph setup.
- Reimplement the local InstantClock trait on tokio::time::Instant so elapsed
  time tracks virtual time.
- Pass the latency interceptor's new seed parameter (None for now; seeding
  follows in a later commit).

The binaries still build their own clock and run on a standard runtime; moving
them onto the paused virtual-time runtime happens next.

Tracks bitcoin-dev-project/sim-ln#309.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@elnosh elnosh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline and runtime introduced here doesn't seem to be the culprit for that specific simulation taking longer.

LGTM - some small nits

Comment thread simln-lib/src/sim_node.rs Outdated
Comment on lines +1167 to +1168
// LDK only allows channel announcements up to 24h in the future. Use a fixed timestamp so that even if virtual
// time has advanced dramatically, we won't hit that limit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I'm understanding the behavior of SimulationClock correctly, then this comment is inaccurate (?) Line below calls clocks.now() which if running with virtual time runtime then this could return something far in the future. I guess it works fine because this method is called at the beginning when setting up the network so virtual time prob has not advanced much but still comment seems false.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah comment needs to go.

I guess it works fine because this method is called at the beginning when setting up the network so virtual time prob has not advanced much

We don't have any sleeps between clock creation and calling this, so the clock will be exactly at the wall clock time we started it with.

Comment thread simln-lib/Cargo.toml Outdated
mockall = "0.13.1"
futures = "0.3.31"
tempfile = "3"
# Features used only by the test suite: macros for #[tokio::test] and rt-multi-thread for multi-threaded test runtimes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i don't love these self-explanatory claude comments. Feel free to ignore tho

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, I was on the fence about them - will remove.

Comment thread sim-cli/src/parsing.rs
Ok(validated_activities)
}

async fn read_sim_path(data_dir: PathBuf, sim_file: PathBuf) -> anyhow::Result<PathBuf> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool

Comment thread sim-cli/src/main.rs Outdated
Comment on lines +37 to +41
let interceptors = if latency > 0 {
vec![Arc::new(LatencyIntercepor::new_poisson(
latency as f32,
cli.fix_seed,
)?) as Arc<dyn Interceptor>]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be in the branch where we call create_simulation_with_network since we only use it there.

Comment thread sim-cli/src/parsing.rs
"Virtual time is only allowed when running on a simulated network; real nodes run on wall time"
));
}
if self.total_time.is_none() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can fix this comment

/// Total simulation time. The simulation will run forever if undefined.

carlaKC and others added 3 commits July 8, 2026 09:11
Replace the wall-time multiplier SimulationClock (a u16 speedup capped at
1000x) and the separate SystemClock with a single SimulationClock that
reports time relative to a SystemTime start anchor. now() is the start time
plus a captured tokio::time::Instant's elapsed(), and sleep() defers to the
tokio timer.

How the speedup works: when this clock is driven on a paused tokio runtime
(start_paused), the runtime freezes the Instant clock and, once no task is
runnable, auto-advances it to the nearest pending timer. So sleep() resolves
with no real time elapsed and now() jumps straight to the next scheduled
event, letting a simulation run as fast as the CPU allows while staying
reproducible. On a regular (non-paused) runtime the exact same clock simply
tracks real time relative to its start, which is how real-node runs use it.
The paused runtime needs tokio's test-util at runtime, so it is gated behind
the new opt-in `virtual-time` feature rather than always compiled in.

Tests construct the clock with SystemTime::now() (live time) rather than a
fixed epoch: LDK's network-graph gossip validation checks channel_update
timestamps against the real wall clock and rejects anything older than two
weeks, so a hardcoded past start time makes populate_network_graph fail.

This commit only lands the clock itself. The runtime helper that drives it on
a paused runtime and the CLI opt-in are removed here for the sake of atomic
commits and reintroduced in later commits; the old --speedup-clock flag and
its validation are dropped accordingly, so for now every run tracks real
time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Running a simulation on virtual time needs a single-threaded, time-paused
runtime: single threaded so scheduling is deterministic, paused so virtual time
auto-advances to the next timer instead of waiting on the wall clock. Leaving
callers to set this up invites silently losing determinism or the virtual-time
advance.

Add runtime::block_on_virtual_time, which owns that runtime, constructs a
SimulationClock on it, and runs a caller-provided build-and-run closure to
completion. It rejects being called from within an existing runtime (returning
the new SimulationError::RuntimeError) rather than panicking on a nested
block_on. The module is gated behind the virtual-time feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nothing in the parse_sim_params call stack actually awaits: it only
does blocking std::fs reads and a blocking dialoguer prompt. Drop the
vestigial async/await from parse_sim_params, read_sim_path and
select_sim_file.

This is preparation for the following commit, which runs the
simulation on a paused virtual-time runtime. There, main() becomes a
plain sync fn that picks a runtime only after parsing, so parsing must
not require an async context of its own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@carlaKC

carlaKC commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Sorry for the slop! here and here

Add --virtual-time, an opt-in flag for simulated networks. When set, the
simulation runs on the library's paused single-threaded runtime, so virtual time
advances instantly to the next event: a multi-day run finishes in seconds and is
reproducible for a given seed. When absent, the simulation runs on a regular
multi-threaded runtime in real time, as before.

main parses and validates the simulation params once up front, then selects the
runtime by the flag: virtual-time runs go through block_on_virtual_time (which
owns the paused runtime and hands back a SimulationClock); regular runs use a
standard runtime. The flag is validated to require a simulated network and
--total-time (an unbounded run would otherwise advance virtual time forever).
sim-cli enables simln-lib's virtual-time feature so the flag is always available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jul 8, 2026
Pin simln-lib/sim-cli to the virtual-time PR head (enabling simln-lib's
virtual-time feature) and adapt to the reimplemented SimulationClock:

- Construct the clock with the new SimulationClock::new(start_time) signature in
  place of the speedup multiplier, across the binaries, tests and graph setup.
- Reimplement the local InstantClock trait on tokio::time::Instant so elapsed
  time tracks virtual time.
- Pass the latency interceptor's new seed parameter (None for now; seeding
  follows in a later commit).

The binaries still build their own clock and run on a standard runtime; moving
them onto the paused virtual-time runtime happens next.

Tracks bitcoin-dev-project/sim-ln#309.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@carlaKC carlaKC merged commit cefd0c4 into bitcoin-dev-project:main Jul 8, 2026
2 checks passed
carlaKC added a commit to carlaKC/jam-ln that referenced this pull request Jul 8, 2026
Pin simln-lib/sim-cli to the virtual-time PR head (enabling simln-lib's
virtual-time feature) and adapt to the reimplemented SimulationClock:

- Construct the clock with the new SimulationClock::new(start_time) signature in
  place of the speedup multiplier, across the binaries, tests and graph setup.
- Reimplement the local InstantClock trait on tokio::time::Instant so elapsed
  time tracks virtual time.
- Pass the latency interceptor's new seed parameter (None for now; seeding
  follows in a later commit).

The binaries still build their own clock and run on a standard runtime; moving
them onto the paused virtual-time runtime happens next.

Tracks bitcoin-dev-project/sim-ln#309.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants