diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt index bc295d223e..f2bffb3b6d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt @@ -36,6 +36,13 @@ import java.util.concurrent.atomic.AtomicBoolean private const val DEFAULT_SOCKS_PORT = 17392 private const val MAX_PORT_RETRIES = 10 +/** + * Return code from [ArtiNative.initialize] when the native bootstrap exceeds its + * internal timeout. The native side has already torn down the half-built client; + * we treat this differently from a hard failure (see [TorService.start]). + */ +private const val ARTI_ERROR_BOOTSTRAP_TIMEOUT = -4 + /** * Manages the Arti Tor client via custom JNI bindings. * @@ -220,6 +227,19 @@ class TorService( Log.d("TorService") { "Initializing Arti with data dir: $dataDir" } var initResult = ArtiNative.initialize(dataDir) + + if (initResult == ARTI_ERROR_BOOTSTRAP_TIMEOUT) { + // The native bootstrap hit its timeout (hostile network) and + // already tore down the half-built client. Don't wipe state or + // retry inline — that would hold lifecycleMutex for another full + // timeout. Drop the init flag and leave status at Connecting so + // TorManager's self-heal watchdog resets and retries on its own + // cadence (and the connection-failure dialog can still surface). + Log.w("TorService") { "Arti bootstrap timed out — leaving Connecting for the self-heal watchdog to retry" } + initialized.set(false) + return@withContext + } + if (initResult != 0) { Log.e("TorService") { "Failed to initialize Arti: error $initResult, clearing data and retrying" } clearAllArtiData() diff --git a/amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so b/amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so index b29a78dbe1..11c72aad8d 100755 Binary files a/amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so and b/amethyst/src/main/jniLibs/arm64-v8a/libarti_android.so differ diff --git a/amethyst/src/main/jniLibs/x86_64/libarti_android.so b/amethyst/src/main/jniLibs/x86_64/libarti_android.so index 4a473042a9..2922d9be71 100755 Binary files a/amethyst/src/main/jniLibs/x86_64/libarti_android.so and b/amethyst/src/main/jniLibs/x86_64/libarti_android.so differ diff --git a/tools/arti-build/src/lib.rs b/tools/arti-build/src/lib.rs index 81883a31da..2d90ae5693 100644 --- a/tools/arti-build/src/lib.rs +++ b/tools/arti-build/src/lib.rs @@ -167,7 +167,15 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_initialize( std::fs::create_dir_all(&cache_dir).ok(); std::fs::create_dir_all(&state_dir).ok(); - let result: Result<()> = runtime.block_on(async { + // Bound the bootstrap so a hostile network (unreachable guards, wiped + // consensus) can't block this JNI call — and the Kotlin-side lifecycle lock + // it holds — indefinitely. On timeout the `create_bootstrapped` future is + // dropped, tearing down the partially built client, and -4 is returned so + // TorService can leave status Connecting and let the self-heal watchdog + // retry instead of wedging. The ABI is unchanged (still one String arg). + const BOOTSTRAP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60); + + let outcome: jint = runtime.block_on(async { log_info!("Creating Arti client..."); let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir); @@ -184,27 +192,38 @@ pub extern "C" fn Java_com_vitorpamplona_amethyst_ui_tor_ArtiNative_initialize( builder.storage().permissions().dangerously_trust_everyone(); } - let config = builder.build()?; + let config = match builder.build() { + Ok(c) => c, + Err(e) => { + log_error!("Failed to build Tor config: {:?}", e); + return -3; + } + }; - let client = TorClient::create_bootstrapped(config).await?; - - log_info!("Arti client created and bootstrapped"); - - *ARTI_CLIENT.lock().unwrap() = Some(Arc::new(client)); - - Ok(()) + match tokio::time::timeout(BOOTSTRAP_TIMEOUT, TorClient::create_bootstrapped(config)).await { + Ok(Ok(client)) => { + log_info!("Arti client created and bootstrapped"); + *ARTI_CLIENT.lock().unwrap() = Some(Arc::new(client)); + 0 + } + Ok(Err(e)) => { + log_error!("Failed to bootstrap Tor client: {:?}", e); + -3 + } + Err(_elapsed) => { + log_error!( + "Tor bootstrap timed out after {}s — aborting so the client can be retried", + BOOTSTRAP_TIMEOUT.as_secs() + ); + -4 + } + } }); - match result { - Ok(_) => { - log_info!("Arti initialized successfully"); - 0 - } - Err(e) => { - log_error!("Failed to initialize Arti: {:?}", e); - -3 - } + if outcome == 0 { + log_info!("Arti initialized successfully"); } + outcome } /// Start the SOCKS5 proxy on the specified port.