Skip to content

Commit 946c655

Browse files
authored
feat(network/onion): detect liveness via TcpStream close (#5168)
<!-- Thank you for contributing to nervosnetwork/ckb! If you haven't already, please read [CONTRIBUTING](https://github.com/nervosnetwork/ckb/blob/develop/CONTRIBUTING.md) document. If you're unsure about anything, just ask; somebody should be along to answer within a day or two. **Important**: We use Squash Merge to merge PRs, so the PR title will become the commit message. Please ensure your PR title follows the [Conventional Commit Messages](https://www.conventionalcommits.org/) format. The most important prefixes you should use: - `fix:`: represents bug fixes, and results in a SemVer patch bump. - `feat:`: represents a new feature, and results in a SemVer minor bump. - `<prefix>!:` (e.g. `feat!:`): represents a breaking change (indicated by the !) and results in a SemVer major bump. Other conventional prefixes are also acceptable (e.g., `docs:`, `refactor:`, `test:`, `chore:`, etc.). --> ### What problem does this PR solve? Issue Number: close #5161 <!-- REMOVE this line if no issue to close --> Follow-up to #5271. Problem Summary: The onion service currently polls the Tor daemon every 3 seconds via `get_uptime()` to detect connection loss. This adds unnecessary latency (up to 3s for failure detection) and constant I/O overhead. A passive, event-driven approach would react instantly and reduce resource usage. ### What is changed and how it works? What's Changed: - An `oneshot` channel notifies `TorController` when either copy direction terminates (EOF or error). - Replaced the 3-second ticker loop in `OnionService` with a branch awaiting `wait_for_disconnect()`. ### Related changes ### Check List <!--REMOVE the items that are not applicable--> Tests <!-- At least one of them must be included. --> - [ ] Unit test - [ ] Integration test - [x] Manual test (add detailed scripts or steps below) - [ ] No code Manual test steps: 1. `ckb run` with config listen_on_onion = true. 2. Observe the onion service starts successfully. 3. Kill/exit the Tor process. 4. Verify the node logs `"Tor control connection lost"` within milliseconds, not after a multi-second delay. 5. Restart the Tor daemon and verify the onion service can be re-established. Side effects - ~~Performance regression~~ - ~~Breaking backward compatibility~~
1 parent 8ddf15a commit 946c655

3 files changed

Lines changed: 33 additions & 18 deletions

File tree

util/onion/src/onion_service.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -128,22 +128,15 @@ impl OnionService {
128128
);
129129

130130
self.handle.spawn(async move {
131-
let mut ticker = tokio::time::interval(tokio::time::Duration::from_secs(3));
132-
loop {
133-
tokio::select! {
134-
_ = ticker.tick() => {
135-
let uptime = tor_controller.get_uptime().await;
136-
if let Err(err) = uptime {
137-
error!("Failed to get tor server uptime: {:?}", err);
138-
drop(tor_server_alive_tx);
139-
return;
140-
}
141-
}
142-
_ = stop_rx.cancelled() => {
143-
info!("OnionService received stop signal, exiting...");
144-
drop(tor_server_alive_tx);
145-
return;
146-
}
131+
tokio::select! {
132+
_ = tor_controller.wait_for_disconnect() => {
133+
error!("OnionService disconnected, retrying...");
134+
drop(tor_server_alive_tx);
135+
136+
}
137+
_ = stop_rx.cancelled() => {
138+
info!("OnionService received stop signal, exiting...");
139+
drop(tor_server_alive_tx);
147140
}
148141
}
149142
});

util/onion/src/tor_connection.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use strum::FromRepr;
1111
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
1212
use tokio::net::tcp::OwnedWriteHalf;
1313
use tokio::net::TcpStream;
14-
use tokio::sync::mpsc;
14+
use tokio::sync::{mpsc, oneshot};
1515

1616
/// Tor control protocol status codes.
1717
///
@@ -206,6 +206,7 @@ impl Response {
206206
pub struct TorConnection {
207207
write: OwnedWriteHalf,
208208
line_rx: mpsc::UnboundedReceiver<String>,
209+
disconnect_rx: oneshot::Receiver<()>,
209210
}
210211

211212
impl TorConnection {
@@ -215,6 +216,7 @@ impl TorConnection {
215216
pub(crate) fn connect(stream: TcpStream, handle: Handle) -> Self {
216217
let (read, write) = stream.into_split();
217218
let (line_tx, line_rx) = mpsc::unbounded_channel();
219+
let (disconnect_tx, disconnect_rx) = oneshot::channel();
218220

219221
handle.spawn(async move {
220222
let mut reader = BufReader::new(read);
@@ -237,9 +239,19 @@ impl TorConnection {
237239
}
238240
}
239241
drop(line_tx);
242+
_ = disconnect_tx.send(());
240243
});
241244

242-
TorConnection { write, line_rx }
245+
TorConnection {
246+
write,
247+
line_rx,
248+
disconnect_rx,
249+
}
250+
}
251+
252+
/// Wait for the underlying TCP connection to close.
253+
pub async fn wait_for_disconnect(self) -> bool {
254+
self.disconnect_rx.await.is_ok()
243255
}
244256

245257
/// Load protocol information from the Tor controller.

util/onion/src/tor_controller.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ impl TorController {
101101
Ok(())
102102
}
103103

104+
/// Waits asynchronously until the underlying TCP connection to the Tor
105+
/// controller is severed (either cleanly closed or due to an I/O error).
106+
///
107+
/// Returns `Some(())` when the connection is lost, or `None` if the internal
108+
/// notification channel was closed unexpectedly (which should not occur during
109+
/// normal operation).
110+
pub async fn wait_for_disconnect(self) -> bool {
111+
self.inner.wait_for_disconnect().await
112+
}
113+
104114
/// Add a new v3 onion service to the Tor server.
105115
pub async fn add_onion_v3(
106116
&mut self,

0 commit comments

Comments
 (0)