diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-10 12:43:12 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-10 12:43:12 +0000 |
| commit | d3c7de6cc1c265d0ea05e59c86543fb03870ae6f (patch) | |
| tree | 65a60f2dbd68b93441a5e5f3371680715d00e87c /src | |
| parent | 433d78640e887a5503afa3cda1840c8047fcb6d0 (diff) | |
sync: implement relay removal for empty non-bootstrap relays
Diffstat (limited to 'src')
| -rw-r--r-- | src/sync/mod.rs | 125 |
1 files changed, 124 insertions, 1 deletions
diff --git a/src/sync/mod.rs b/src/sync/mod.rs index a3189a3..108ebe9 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs | |||
| @@ -407,6 +407,32 @@ async fn run_daily_timer(sync_manager: Arc<Mutex<SyncManager>>) { | |||
| 407 | } | 407 | } |
| 408 | } | 408 | } |
| 409 | 409 | ||
| 410 | // ============================================================================= | ||
| 411 | // Disconnect Checker (Phase 8) | ||
| 412 | // ============================================================================= | ||
| 413 | |||
| 414 | /// Check interval for empty relay cleanup in seconds | ||
| 415 | const DISCONNECT_CHECK_INTERVAL_SECS: u64 = 60; | ||
| 416 | |||
| 417 | /// Run the disconnect checker for periodic cleanup of empty relays | ||
| 418 | /// | ||
| 419 | /// This function runs in a loop, checking every 60 seconds for relays | ||
| 420 | /// that have no repos or root events to sync. Non-bootstrap relays | ||
| 421 | /// that are empty will be disconnected to free up resources. | ||
| 422 | /// | ||
| 423 | /// Bootstrap relays are never disconnected, even if empty. | ||
| 424 | async fn run_disconnect_checker(sync_manager: Arc<Mutex<SyncManager>>) { | ||
| 425 | loop { | ||
| 426 | // Check every 60 seconds | ||
| 427 | tokio::time::sleep(Duration::from_secs(DISCONNECT_CHECK_INTERVAL_SECS)).await; | ||
| 428 | |||
| 429 | tracing::debug!("Disconnect checker running"); | ||
| 430 | |||
| 431 | let mut manager = sync_manager.lock().await; | ||
| 432 | manager.check_disconnects().await; | ||
| 433 | } | ||
| 434 | } | ||
| 435 | |||
| 410 | /// Manages proactive synchronization with external relays | 436 | /// Manages proactive synchronization with external relays |
| 411 | /// | 437 | /// |
| 412 | /// The SyncManager runs as a background task, subscribing to repository | 438 | /// The SyncManager runs as a background task, subscribing to repository |
| @@ -693,7 +719,13 @@ impl SyncManager { | |||
| 693 | run_daily_timer(timer_manager).await; | 719 | run_daily_timer(timer_manager).await; |
| 694 | }); | 720 | }); |
| 695 | 721 | ||
| 696 | // 9. Main loop - handle actions from self-subscriber, disconnect, EOSE, and connect notifications | 722 | // 9. Spawn disconnect checker task |
| 723 | let checker_manager = Arc::clone(&sync_manager); | ||
| 724 | tokio::spawn(async move { | ||
| 725 | run_disconnect_checker(checker_manager).await; | ||
| 726 | }); | ||
| 727 | |||
| 728 | // 10. Main loop - handle actions from self-subscriber, disconnect, EOSE, and connect notifications | ||
| 697 | loop { | 729 | loop { |
| 698 | // Wait for an event without holding the lock | 730 | // Wait for an event without holding the lock |
| 699 | tokio::select! { | 731 | tokio::select! { |
| @@ -1566,4 +1598,95 @@ impl SyncManager { | |||
| 1566 | 1598 | ||
| 1567 | Ok(()) | 1599 | Ok(()) |
| 1568 | } | 1600 | } |
| 1601 | |||
| 1602 | /// Check for relays that should be disconnected | ||
| 1603 | /// | ||
| 1604 | /// This method is called periodically by run_disconnect_checker. | ||
| 1605 | /// It identifies non-bootstrap relays that have no repos or root events | ||
| 1606 | /// to sync and disconnects them to free up resources. | ||
| 1607 | /// | ||
| 1608 | /// Bootstrap relays are NEVER disconnected, even if empty. | ||
| 1609 | async fn check_disconnects(&mut self) { | ||
| 1610 | // Collect relays to disconnect | ||
| 1611 | let to_disconnect: Vec<String> = { | ||
| 1612 | let index = self.relay_sync_index.read().await; | ||
| 1613 | index | ||
| 1614 | .iter() | ||
| 1615 | .filter_map(|(relay_url, state)| { | ||
| 1616 | // Skip bootstrap relays - they stay connected | ||
| 1617 | if state.is_bootstrap { | ||
| 1618 | return None; | ||
| 1619 | } | ||
| 1620 | |||
| 1621 | // Disconnect if no repos and no root events | ||
| 1622 | if state.repos.is_empty() && state.root_events.is_empty() { | ||
| 1623 | Some(relay_url.clone()) | ||
| 1624 | } else { | ||
| 1625 | None | ||
| 1626 | } | ||
| 1627 | }) | ||
| 1628 | .collect() | ||
| 1629 | }; | ||
| 1630 | |||
| 1631 | if to_disconnect.is_empty() { | ||
| 1632 | tracing::trace!("No empty relays to disconnect"); | ||
| 1633 | return; | ||
| 1634 | } | ||
| 1635 | |||
| 1636 | tracing::info!( | ||
| 1637 | count = to_disconnect.len(), | ||
| 1638 | relays = ?to_disconnect, | ||
| 1639 | "Found empty non-bootstrap relays to disconnect" | ||
| 1640 | ); | ||
| 1641 | |||
| 1642 | // Disconnect empty relays | ||
| 1643 | for relay_url in to_disconnect { | ||
| 1644 | self.disconnect_relay(&relay_url).await; | ||
| 1645 | } | ||
| 1646 | } | ||
| 1647 | |||
| 1648 | /// Disconnect a relay and clean up all associated state | ||
| 1649 | /// | ||
| 1650 | /// This method: | ||
| 1651 | /// - Removes the relay from relay_sync_index | ||
| 1652 | /// - Removes the relay from pending_sync_index | ||
| 1653 | /// - Disconnects the connection if it exists | ||
| 1654 | /// | ||
| 1655 | /// Used by check_disconnects for cleanup of empty relays. | ||
| 1656 | async fn disconnect_relay(&mut self, relay_url: &str) { | ||
| 1657 | tracing::info!(relay = %relay_url, "Disconnecting empty relay"); | ||
| 1658 | |||
| 1659 | // Remove from relay_sync_index | ||
| 1660 | { | ||
| 1661 | let mut index = self.relay_sync_index.write().await; | ||
| 1662 | if index.remove(relay_url).is_some() { | ||
| 1663 | tracing::debug!( | ||
| 1664 | relay = %relay_url, | ||
| 1665 | "Removed relay from relay_sync_index" | ||
| 1666 | ); | ||
| 1667 | } | ||
| 1668 | } | ||
| 1669 | |||
| 1670 | // Remove from pending_sync_index | ||
| 1671 | { | ||
| 1672 | let mut pending = self.pending_sync_index.write().await; | ||
| 1673 | if pending.remove(relay_url).is_some() { | ||
| 1674 | tracing::debug!( | ||
| 1675 | relay = %relay_url, | ||
| 1676 | "Removed relay from pending_sync_index" | ||
| 1677 | ); | ||
| 1678 | } | ||
| 1679 | } | ||
| 1680 | |||
| 1681 | // Disconnect the connection if it exists | ||
| 1682 | if let Some(connection) = self.connections.remove(relay_url) { | ||
| 1683 | connection.disconnect().await; | ||
| 1684 | tracing::debug!( | ||
| 1685 | relay = %relay_url, | ||
| 1686 | "Disconnected connection" | ||
| 1687 | ); | ||
| 1688 | } | ||
| 1689 | |||
| 1690 | tracing::info!(relay = %relay_url, "Relay disconnected and cleaned up"); | ||
| 1691 | } | ||
| 1569 | } \ No newline at end of file | 1692 | } \ No newline at end of file |