Fast startup: defer bootstrap to background, lazy feed load

- Node::open_with_bind no longer runs bootstrap (anchor connect, NAT
  probe, referrals). New run_bootstrap() method called from background
  task after UI is live.
- Background tasks (pull cycle, diff cycle, etc.) start after bootstrap
  completes, not during block_on.
- Feed no longer pre-loaded during welcome screen readiness check.
  Ready button enables immediately after get_node_info succeeds.
- Feed loads on tab switch, not during startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-04-16 17:19:05 -04:00
parent 19a95b7c45
commit 5e7eed9638
3 changed files with 79 additions and 53 deletions

View file

@ -29,7 +29,7 @@ pub struct Node {
pub node_id: NodeId,
pub blob_store: Arc<BlobStore>,
secret_seed: [u8; 32],
bootstrap_anchors: Vec<(NodeId, iroh::EndpointAddr)>,
bootstrap_anchors: tokio::sync::Mutex<Vec<(NodeId, iroh::EndpointAddr)>>,
#[allow(dead_code)]
profile: DeviceProfile,
pub activity_log: Arc<std::sync::Mutex<ActivityLog>>,
@ -113,6 +113,48 @@ impl Node {
s.add_follow(&node_id)?;
}
// Build the node (fast path — no network I/O beyond endpoint creation)
let activity_log_ref = Arc::clone(&activity_log);
let last_rebalance_ms = Arc::new(AtomicU64::new(0));
let last_anchor_register_ms = Arc::new(AtomicU64::new(0));
let role = network.device_role();
let (replication_budget, delivery_budget) = (role.replication_limit(), role.delivery_limit());
let replication_budget_remaining = Arc::new(AtomicU64::new(replication_budget));
let delivery_budget_remaining = Arc::new(AtomicU64::new(delivery_budget));
let budget_last_reset_ms = Arc::new(AtomicU64::new(
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default().as_millis() as u64
));
blob_store.set_delivery_budget(delivery_budget);
let mut node = Self {
data_dir: data_dir.clone(),
storage: Arc::clone(&storage),
network: Arc::clone(&network),
node_id,
blob_store,
secret_seed,
bootstrap_anchors: tokio::sync::Mutex::new(Vec::new()),
profile,
activity_log: activity_log_ref,
last_rebalance_ms,
last_anchor_register_ms,
replication_budget_remaining,
delivery_budget_remaining,
budget_last_reset_ms,
};
Ok(node)
}
/// Bootstrap: connect to anchors, pull initial data, NAT probe, referrals.
/// Can be called during open_with_bind (blocking startup) or deferred to background.
pub async fn run_bootstrap(&self, data_dir: &Path) -> anyhow::Result<()> {
let storage = &self.storage;
let network = &self.network;
let node_id = self.node_id;
// Bootstrap: if peers table is empty, try bootstrap.json then default anchor
{
let s = storage.get().await;
@ -426,35 +468,10 @@ impl Node {
}
}
// Initialize CDN replication budgets based on device role
let role = network.device_role();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let replication_budget_remaining = Arc::new(AtomicU64::new(role.replication_limit()));
let delivery_budget_remaining = Arc::new(AtomicU64::new(role.delivery_limit()));
let budget_last_reset_ms = Arc::new(AtomicU64::new(now));
// Store bootstrap anchors on the node
*self.bootstrap_anchors.lock().await = bootstrap_anchors;
// Set delivery budget on blob store (shared with ConnectionManager)
blob_store.set_delivery_budget(role.delivery_limit());
Ok(Self {
data_dir,
storage,
network,
node_id,
blob_store,
secret_seed,
bootstrap_anchors,
profile,
activity_log,
last_rebalance_ms,
last_anchor_register_ms,
replication_budget_remaining,
delivery_budget_remaining,
budget_last_reset_ms,
})
Ok(())
}
/// Get recent activity events (for diagnostics UI).
@ -2487,8 +2504,8 @@ impl Node {
storage.list_peer_records()
}
pub fn list_bootstrap_anchors(&self) -> &[(NodeId, iroh::EndpointAddr)] {
&self.bootstrap_anchors
pub async fn list_bootstrap_anchors(&self) -> Vec<(NodeId, iroh::EndpointAddr)> {
self.bootstrap_anchors.lock().await.clone()
}
/// Get connection info for display: (node_id, slot_kind, connected_at)