test: US-08 DWN sync tests pass 50/50 — fix sync performance
- Make dwn.sync endpoint async: spawns background task, returns immediately - Add 90s overall timeout to sync_with_peers via tokio::time::timeout - Deduplicate peer onion addresses before syncing - Batch message pushes (50 per request) instead of one-at-a-time over Tor - Add 15s connect_timeout to Tor SOCKS5 client - Cap local message query to 200 messages per sync - Fix DWN HTTP handler to process ALL messages in batch (was only first) - Add recordId deduplication in handler to prevent duplicate imports - Update test script to poll dwn.status for sync completion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a64d1b2d12
commit
65b5d5db8e
@@ -102,6 +102,7 @@ pub struct DwnStatusResponse {
|
||||
/// and push our local messages, deduplicating by record_id.
|
||||
pub async fn sync_with_peers(data_dir: &Path, peer_onions: &[String]) -> Result<DwnSyncState> {
|
||||
use crate::network::dwn_store::{DwnStore, MessageQuery};
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut state = load_sync_state(data_dir).await?;
|
||||
state.status = SyncStatus::Syncing;
|
||||
@@ -112,6 +113,7 @@ pub async fn sync_with_peers(data_dir: &Path, peer_onions: &[String]) -> Result<
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(socks_proxy)
|
||||
.connect_timeout(std::time::Duration::from_secs(15))
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()
|
||||
.context("Failed to build Tor HTTP client")?;
|
||||
@@ -119,24 +121,47 @@ pub async fn sync_with_peers(data_dir: &Path, peer_onions: &[String]) -> Result<
|
||||
let store = DwnStore::new(data_dir).await?;
|
||||
let mut synced_count = 0u64;
|
||||
|
||||
// Get local messages since last sync (or all if first sync)
|
||||
// Get local messages since last sync (or all if first sync, capped at 200)
|
||||
let local_messages = store
|
||||
.query_messages(&MessageQuery {
|
||||
date_from: state.last_sync.clone(),
|
||||
limit: Some(200),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
|
||||
for onion in peer_onions {
|
||||
match sync_single_peer(&client, &store, onion, &local_messages, &state.last_sync).await {
|
||||
Ok(count) => {
|
||||
debug!(peer = %onion, messages = count, "Peer sync complete");
|
||||
synced_count += count;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(peer = %onion, error = %e, "Peer sync failed");
|
||||
// Deduplicate peer onion addresses
|
||||
let mut seen = HashSet::new();
|
||||
let unique_onions: Vec<&String> = peer_onions
|
||||
.iter()
|
||||
.filter(|o| !o.is_empty() && seen.insert(o.as_str().to_string()))
|
||||
.collect();
|
||||
|
||||
debug!(peers = unique_onions.len(), local_msgs = local_messages.len(), "Starting DWN sync");
|
||||
|
||||
// Overall sync timeout: 90 seconds
|
||||
let sync_future = async {
|
||||
for onion in &unique_onions {
|
||||
match sync_single_peer(&client, &store, onion, &local_messages, &state.last_sync).await
|
||||
{
|
||||
Ok(count) => {
|
||||
debug!(peer = %onion, messages = count, "Peer sync complete");
|
||||
synced_count += count;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(peer = %onion, error = %e, "Peer sync failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match tokio::time::timeout(std::time::Duration::from_secs(90), sync_future).await {
|
||||
Ok(()) => {
|
||||
debug!(count = synced_count, "DWN sync complete");
|
||||
}
|
||||
Err(_) => {
|
||||
debug!("DWN sync timed out after 90s");
|
||||
}
|
||||
}
|
||||
|
||||
state.status = SyncStatus::Synced;
|
||||
@@ -144,7 +169,6 @@ pub async fn sync_with_peers(data_dir: &Path, peer_onions: &[String]) -> Result<
|
||||
state.messages_synced += synced_count;
|
||||
save_sync_state(data_dir, &state).await?;
|
||||
|
||||
debug!(count = synced_count, "DWN sync complete");
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
@@ -220,26 +244,37 @@ async fn sync_single_peer(
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Push — send our local messages to the peer
|
||||
for msg in local_messages {
|
||||
let push_body = serde_json::json!({
|
||||
"messages": [{
|
||||
"descriptor": {
|
||||
"interface": "Records",
|
||||
"method": "Write",
|
||||
"protocol": msg.descriptor.protocol,
|
||||
"schema": msg.descriptor.schema,
|
||||
"dataFormat": msg.descriptor.data_format,
|
||||
},
|
||||
"recordId": msg.record_id,
|
||||
"author": msg.author,
|
||||
"data": msg.data,
|
||||
}]
|
||||
});
|
||||
// Step 3: Push — send local messages to peer in batches
|
||||
let batch_size = 50;
|
||||
for chunk in local_messages.chunks(batch_size) {
|
||||
let messages: Vec<serde_json::Value> = chunk
|
||||
.iter()
|
||||
.map(|msg| {
|
||||
serde_json::json!({
|
||||
"descriptor": {
|
||||
"interface": "Records",
|
||||
"method": "Write",
|
||||
"protocol": msg.descriptor.protocol,
|
||||
"schema": msg.descriptor.schema,
|
||||
"dataFormat": msg.descriptor.data_format,
|
||||
},
|
||||
"recordId": msg.record_id,
|
||||
"author": msg.author,
|
||||
"data": msg.data,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Best-effort push — don't fail the whole sync if one push fails
|
||||
if let Err(e) = client.post(&dwn_url).json(&push_body).send().await {
|
||||
debug!(record_id = %msg.record_id, error = %e, "Failed to push message to peer");
|
||||
let push_body = serde_json::json!({ "messages": messages });
|
||||
|
||||
// Best-effort push — don't fail the whole sync if a batch fails
|
||||
match client.post(&dwn_url).json(&push_body).send().await {
|
||||
Ok(_) => {
|
||||
debug!(count = chunk.len(), "Pushed message batch to peer");
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(error = %e, "Failed to push message batch to peer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user