refactor: replace blocking std::fs and TCP I/O with async tokio equivalents
- R6: Convert 6 std::fs calls in session.rs to tokio::fs async - R7: Convert std::fs::read_to_string in docker_packages.rs to async - R8: Convert 3 std::fs calls in port_allocator.rs to async, switch to tokio::sync::Mutex - R9+R10+R11: Fix blocking I/O in node_message.rs and nostr_discovery.rs - R12: Convert electrs_status.rs from sync TCP to async tokio::net with 5s timeouts - R4+R5: Spawn periodic cleanup tasks for endpoint and login rate limiters Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Serialize;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
const ELECTRUMX_HOST: &str = "127.0.0.1";
|
||||
const ELECTRUMX_PORT: u16 = 50001;
|
||||
@@ -35,16 +35,18 @@ pub struct ElectrsSyncStatus {
|
||||
}
|
||||
|
||||
/// Get the total size of a directory in bytes.
|
||||
fn dir_size_bytes(path: &str) -> u64 {
|
||||
async fn dir_size_bytes(path: &str) -> u64 {
|
||||
let mut total: u64 = 0;
|
||||
if let Ok(entries) = std::fs::read_dir(path) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
total += dir_size_bytes(&path.to_string_lossy());
|
||||
} else if let Ok(meta) = entry.metadata() {
|
||||
total += meta.len();
|
||||
}
|
||||
let mut entries = match tokio::fs::read_dir(path).await {
|
||||
Ok(entries) => entries,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
let entry_path = entry.path();
|
||||
if entry_path.is_dir() {
|
||||
total += Box::pin(dir_size_bytes(&entry_path.to_string_lossy())).await;
|
||||
} else if let Ok(meta) = entry.metadata().await {
|
||||
total += meta.len();
|
||||
}
|
||||
}
|
||||
total
|
||||
@@ -62,25 +64,39 @@ fn format_bytes(bytes: u64) -> String {
|
||||
}
|
||||
|
||||
/// Fetch ElectrumX indexed height via Electrum protocol (TCP JSON-RPC).
|
||||
fn electrumx_indexed_height() -> Result<u64> {
|
||||
let mut stream = TcpStream::connect((ELECTRUMX_HOST, ELECTRUMX_PORT))
|
||||
.context("Failed to connect to ElectrumX")?;
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(5)))
|
||||
.context("set_read_timeout")?;
|
||||
stream
|
||||
.set_write_timeout(Some(Duration::from_secs(5)))
|
||||
.context("set_write_timeout")?;
|
||||
async fn electrumx_indexed_height() -> Result<u64> {
|
||||
let timeout_duration = Duration::from_secs(5);
|
||||
|
||||
let stream = tokio::time::timeout(
|
||||
timeout_duration,
|
||||
TcpStream::connect((ELECTRUMX_HOST, ELECTRUMX_PORT)),
|
||||
)
|
||||
.await
|
||||
.context("ElectrumX connection timed out")?
|
||||
.context("Failed to connect to ElectrumX")?;
|
||||
|
||||
let (reader_half, mut writer_half) = tokio::io::split(stream);
|
||||
|
||||
// blockchain.headers.subscribe returns {"height": N, "hex": "..."}
|
||||
let req = r#"{"id":1,"method":"blockchain.headers.subscribe","params":[]}
|
||||
"#;
|
||||
stream.write_all(req.as_bytes())?;
|
||||
stream.flush()?;
|
||||
tokio::time::timeout(timeout_duration, writer_half.write_all(req.as_bytes()))
|
||||
.await
|
||||
.context("ElectrumX write timed out")?
|
||||
.context("Failed to write to ElectrumX")?;
|
||||
|
||||
let mut reader = BufReader::new(stream);
|
||||
tokio::time::timeout(timeout_duration, writer_half.flush())
|
||||
.await
|
||||
.context("ElectrumX flush timed out")?
|
||||
.context("Failed to flush ElectrumX stream")?;
|
||||
|
||||
let mut reader = BufReader::new(reader_half);
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line)?;
|
||||
tokio::time::timeout(timeout_duration, reader.read_line(&mut line))
|
||||
.await
|
||||
.context("ElectrumX read timed out")?
|
||||
.context("Failed to read from ElectrumX")?;
|
||||
|
||||
let line = line.trim();
|
||||
if line.is_empty() {
|
||||
anyhow::bail!("Empty response from ElectrumX");
|
||||
@@ -136,10 +152,10 @@ async fn bitcoin_network_height() -> Result<u64> {
|
||||
Ok(height)
|
||||
}
|
||||
|
||||
/// Get ElectrumX sync status. Runs blocking ElectrumX call in spawn_blocking.
|
||||
/// Get ElectrumX sync status.
|
||||
pub async fn get_electrs_sync_status() -> ElectrsSyncStatus {
|
||||
// Get index data size (non-blocking, fast filesystem stat)
|
||||
let data_bytes = dir_size_bytes(ELECTRUMX_DATA_DIR);
|
||||
// Get index data size
|
||||
let data_bytes = dir_size_bytes(ELECTRUMX_DATA_DIR).await;
|
||||
let index_size = if data_bytes > 0 {
|
||||
Some(format_bytes(data_bytes))
|
||||
} else {
|
||||
@@ -193,13 +209,13 @@ pub async fn get_electrs_sync_status() -> ElectrsSyncStatus {
|
||||
}
|
||||
};
|
||||
|
||||
let indexed_height = match tokio::task::spawn_blocking(electrumx_indexed_height).await {
|
||||
Ok(Ok(h)) => h,
|
||||
Ok(Err(e)) => {
|
||||
let indexed_height = match electrumx_indexed_height().await {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
// ElectrumX may not be ready on 50001 during initial sync
|
||||
let err_msg = e.to_string();
|
||||
let err_lower = err_msg.to_lowercase();
|
||||
let (status, error) = if err_lower.contains("connect") || err_lower.contains("reset") || err_lower.contains("refused") {
|
||||
let (status, error) = if err_lower.contains("connect") || err_lower.contains("reset") || err_lower.contains("refused") || err_lower.contains("timed out") {
|
||||
// Estimate progress from data directory size
|
||||
let _est_pct = if data_bytes > 0 {
|
||||
((data_bytes as f64 / ESTIMATED_FULL_INDEX_BYTES) * 100.0).min(99.0)
|
||||
@@ -233,17 +249,6 @@ pub async fn get_electrs_sync_status() -> ElectrsSyncStatus {
|
||||
tor_onion: tor_onion.clone(),
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
return ElectrsSyncStatus {
|
||||
indexed_height: 0,
|
||||
network_height,
|
||||
progress_pct: 0.0,
|
||||
status: "error".to_string(),
|
||||
error: Some(format!("Task: {}", e)),
|
||||
index_size,
|
||||
tor_onion: tor_onion.clone(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let progress_pct = if network_height > 0 {
|
||||
|
||||
Reference in New Issue
Block a user