@@ -9,7 +9,7 @@ use std::net::SocketAddr;
99use std:: sync:: Arc ;
1010
1111use tokio:: sync:: Mutex ;
12- use tracing:: { error, info} ;
12+ use tracing:: { debug , error, info} ;
1313
1414use crate :: metrics:: NoMetrics ;
1515use crate :: { Config , Node } ;
@@ -72,13 +72,32 @@ pub fn create_tun_fd(tun_name: &str) -> Result<i32, std::io::Error> {
7272
7373// ── NodeHandle ──────────────────────────────────────────────────────────────
7474
75+ /// Upper bound on how long node teardown waits for the background Tokio
76+ /// runtime to shut down. Asynchronous tasks (including the TUN reader/writer)
77+ /// are cancelled immediately when the runtime is dropped; this timeout only
78+ /// bounds the wait on any outstanding `spawn_blocking` work so a stuck
79+ /// blocking task cannot hang teardown forever.
80+ const SHUTDOWN_TIMEOUT : std:: time:: Duration = std:: time:: Duration :: from_secs ( 5 ) ;
81+
7582/// Handle to a running mycelium node. Holds a reference to the node for direct
7683/// method calls and the Tokio runtime handle to drive them from non-async
7784/// threads (e.g. Binder threads, C FFI callbacks).
7885pub struct NodeHandle {
7986 node : Arc < Mutex < Node < NoMetrics > > > ,
8087 rt_handle : tokio:: runtime:: Handle ,
8188 shutdown_tx : Option < tokio:: sync:: oneshot:: Sender < ( ) > > ,
89+ /// Background OS thread that owns the Tokio runtime. Joined on drop so
90+ /// teardown is synchronous: once the join completes the runtime is gone
91+ /// and any TUN interface the node created has been removed.
92+ thread : Option < std:: thread:: JoinHandle < ( ) > > ,
93+ /// On Android the `tun` crate does not close the TUN file descriptor on
94+ /// drop (it assumes the fd is owned by Android's `VpnService`). Mycelium
95+ /// opens this fd itself via [`create_tun_fd`], so it owns it and must
96+ /// close it during teardown — otherwise the kernel keeps the
97+ /// non-persistent TUN interface alive. Closed in [`Drop`], after the
98+ /// background runtime has been joined.
99+ #[ cfg( target_os = "android" ) ]
100+ tun_fd : Option < i32 > ,
82101}
83102
84103impl NodeHandle {
@@ -87,10 +106,15 @@ impl NodeHandle {
87106 /// Blocks the calling thread until the node is ready (or fails to start).
88107 /// The caller provides a fully constructed [`Config`].
89108 pub fn start ( config : Config < NoMetrics > ) -> Result < Self , NodeError > {
109+ // On Android mycelium opens the TUN fd itself (see `create_tun_fd`),
110+ // so the handle owns it and is responsible for closing it on drop.
111+ #[ cfg( target_os = "android" ) ]
112+ let tun_fd = config. tun_fd ;
113+
90114 let ( result_tx, result_rx) = std:: sync:: mpsc:: sync_channel ( 1 ) ;
91115 let ( shutdown_tx, shutdown_rx) = tokio:: sync:: oneshot:: channel :: < ( ) > ( ) ;
92116
93- std:: thread:: spawn ( move || {
117+ let thread = std:: thread:: spawn ( move || {
94118 let rt = match tokio:: runtime:: Builder :: new_multi_thread ( )
95119 . worker_threads ( 2 )
96120 . enable_all ( )
@@ -121,14 +145,44 @@ impl NodeHandle {
121145 }
122146 }
123147 } ) ;
148+
149+ // Explicit, bounded teardown. Dropping the runtime cancels every
150+ // asynchronous task and drops the TUN device, then waits up to
151+ // SHUTDOWN_TIMEOUT for any `spawn_blocking` work to finish.
152+ debug ! ( "shutting down node runtime" ) ;
153+ rt. shutdown_timeout ( SHUTDOWN_TIMEOUT ) ;
154+ debug ! ( "node runtime shut down" ) ;
124155 } ) ;
125156
126- let ( node, rt_handle) = result_rx. recv ( ) . map_err ( |_| NodeError :: ThreadPanic ) ??;
157+ let ( node, rt_handle) = match result_rx. recv ( ) {
158+ Ok ( Ok ( pair) ) => pair,
159+ other => {
160+ // The node failed to start. Wait for the background runtime
161+ // to finish tearing down, then release the TUN fd we opened
162+ // (on Android nothing else will close it).
163+ let _ = thread. join ( ) ;
164+ #[ cfg( target_os = "android" ) ]
165+ if let Some ( fd) = tun_fd {
166+ if fd >= 0 {
167+ // SAFETY: `fd` came from `create_tun_fd`; the runtime
168+ // that used it has been joined, so it is unreferenced.
169+ unsafe { libc:: close ( fd) } ;
170+ }
171+ }
172+ return Err ( match other {
173+ Ok ( Err ( e) ) => e,
174+ _ => NodeError :: ThreadPanic ,
175+ } ) ;
176+ }
177+ } ;
127178
128179 Ok ( NodeHandle {
129180 node,
130181 rt_handle,
131182 shutdown_tx : Some ( shutdown_tx) ,
183+ thread : Some ( thread) ,
184+ #[ cfg( target_os = "android" ) ]
185+ tun_fd,
132186 } )
133187 }
134188
@@ -158,6 +212,44 @@ impl NodeHandle {
158212 }
159213}
160214
215+ impl Drop for NodeHandle {
216+ /// Shut the node down and block until its background runtime — and any
217+ /// network interface it created — has been fully torn down.
218+ ///
219+ /// NOTE: this joins the background OS thread, so it must not be invoked
220+ /// from within the node's own Tokio runtime (a thread cannot join
221+ /// itself). Calling it from an external thread — as the C FFI layer
222+ /// does — is safe.
223+ fn drop ( & mut self ) {
224+ // Signal shutdown if `stop()` has not already done so.
225+ if let Some ( tx) = self . shutdown_tx . take ( ) {
226+ let _ = tx. send ( ( ) ) ;
227+ }
228+ // Block until the background thread has finished tearing down the
229+ // runtime. Dropping the runtime cancels every task, including the
230+ // TUN reader/writer that owns the tun device.
231+ if let Some ( thread) = self . thread . take ( ) {
232+ if let Err ( e) = thread. join ( ) {
233+ error ! ( "node background thread panicked during shutdown: {e:?}" ) ;
234+ }
235+ }
236+ // On Android the `tun` crate does not close the TUN fd on drop (it
237+ // assumes Android's `VpnService` owns it). Mycelium opened this fd
238+ // itself via `create_tun_fd`, so it must close it here — after the
239+ // join above guarantees the runtime, and the tun device using the
240+ // fd, are gone. The interface is non-persistent, so closing the last
241+ // fd makes the kernel remove it.
242+ #[ cfg( target_os = "android" ) ]
243+ if let Some ( fd) = self . tun_fd . take ( ) {
244+ if fd >= 0 {
245+ // SAFETY: `fd` came from `create_tun_fd`; the runtime that
246+ // used it has been joined, so nothing else references it.
247+ unsafe { libc:: close ( fd) } ;
248+ }
249+ }
250+ }
251+ }
252+
161253// ── Helpers ─────────────────────────────────────────────────────────────────
162254
163255/// Parse a proxy address string (`"ip:port"`) into a [`SocketAddr`].
0 commit comments