Skip to content

Inefficient unconditional sleeps in Zebrad::launch (~10s per launch, std::thread::sleep blocks tokio worker) #240

Description

@zancas

Summary

zcash_local_net::validator::zebrad::Zebrad::launch contains two unconditional std::thread::sleep(Duration::from_secs(5)) calls — 10 seconds of pure wait per launch — with no commit-history rationale. They are also std::thread::sleep (not tokio::time::sleep) inside an async fn, so they park the entire tokio worker thread for the duration. On a multi_thread(2) runtime that's half the runtime offline for 10s straight per launch.

Cumulative impact for downstream consumers: zaino's integration-tests stable profile has ~25 Zebrad-backed tests. Each pays the 10s tax. That's ~4-5 minutes of pure wall-clock waste per full suite run, plus loss of runtime parallelism during the sleeps.

Where the sleeps live

# Location (pre-fix) Duration Type Context
1 zcash_local_net/src/validator/zebrad.rs:234 5 s std::thread::sleep Right after launch::wait returned success
2 zcash_local_net/src/validator/zebrad.rs:255 5 s std::thread::sleep Right after generate_blocks(1) (post-genesis-mine)

Both were introduced at file creation (bc63bdd refactor compiles). git log -L returns only the single creation commit — no rationale.

Why each is wasteful

  • Sleep update name, and make test compare expected deviation between lwd and… #1: launch::wait (in launch.rs) already polls zebrad's stdout for the success indicators "Opened RPC endpoint at ", "zebra_rpc::indexer::server: Opened RPC endpoint at ", and "spawned initial Zebra tasks". By the time wait() returns, the listener is bound. Adding a 5-second wall-time fudge afterward gates on nothing observable — pure "wait some more, just in case."
  • Sleep Break zcash_local_net into two crates #2: generate_blocks calls poll_chain_height(target_height) internally (loops getchaintips until the new tip is reflected). When generate_blocks(1) returns, the validator demonstrably sees the new block. The 5 s after is gating on nothing.
  • Both use std::thread::sleep: parks the worker thread instead of yielding. With multi_thread(2) (typical in zaino's integration tests) half the runtime is offline; other tasks can't make progress. tokio::time::sleep would yield correctly.

Proposed fix (work in progress on branch speed_zcash_local_net_up)

  1. Delete sleep Break zcash_local_net into two crates #2 entirely. generate_blocks + poll_chain_height already provide a stricter "block is observable" signal than a fixed wait.
  2. Replace sleep update name, and make test compare expected deviation between lwd and… #1 with a bounded retry inside generate_blocks around (getblocktemplate → build proposal → submitblock). Each retry re-reads the template (fresh state, fresh timestamp). Bounded so a real consensus rejection surfaces deterministically. Currently 30 attempts × 100 ms = 3 s ceiling; in the happy path the first or second attempt succeeds.
  3. Add LaunchError::RpcReadinessTimeout for the case where the RPC framework genuinely never comes up (rather than panicking deep inside getblocktemplate).

Empirical impact

Test Before After (best case)
launch_zebrad (cold, full suite) ~15 s ~5-7 s

Most of the remaining ~5-7 s is zebrad's own process boot (cold binary load, listener bind), which lives behind launch::wait already.

Companion issue

A diagnostic run during this work surfaced a separate, unrelated consensus failure: \"missing lockbox disbursements for NU6.1 activation block\". That is not a sleep / readiness bug — it's a real consensus-rule gap in proposal_block_from_template for NU6.1 activation blocks. Filed separately so the two threads don't get conflated.

Out of scope (lower-priority sleep sites)

For completeness, other sleep-like sites in zcash_local_net:

  • validator/zcashd.rs 1.5 s tokio::time::sleep inside opt-in generate_blocks_with_delay — intentional.
  • validator/zcashd.rs 500 ms tick in poll_chain_height — tunable.
  • validator/zebrad.rs 1.5 s in generate_blocks_with_delay — intentional.
  • validator/zebrad.rs 100 ms tick in poll_chain_height — tunable.
  • validator.rs 3 s in cache_chain after stop() — test-cache build only.
  • launch.rs 100 ms tick in launch::wait log-poll — appropriately scoped.

These are either hot-path-irrelevant or appropriately scoped polls. The two 5 s sleeps in Zebrad::launch were the outliers worth fixing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions