mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 11:18:24 +00:00
fix: bound Arti bootstrap with a 60s timeout so a hostile network can't wedge Tor
A self-heal reset()/resetWithCleanState() (and the lifecycle serialization added in the previous commit) can only recover Tor if initialize() actually returns. ArtiNative.initialize() calls TorClient::create_bootstrapped, which on a hostile network (unreachable guards, wiped consensus) retries internally for many minutes. While it blocks it holds lifecycleMutex, so the watchdog's reset can never run — Tor stays wedged at Connecting. Wrap create_bootstrapped in a 60s tokio::time::timeout. On timeout the future is dropped (tearing down the half-built client) and initialize() returns -4; the JNI ABI is unchanged (still one String arg), so the checked-in CI host .so and TorArtiNativeIntegrationTest keep working without a rebuild. TorService treats -4 specially: drop the init flag and leave status Connecting (don't wipe+retry inline under the lock, don't go Off) so TorManager's self-heal watchdog resets and re-inits on its own cadence, and connectionFailure can still surface the "use regular connection" dialog. Rebuilt libarti_android.so for arm64-v8a + x86_64. Verified on device: a no-network cold-start bootstrap timed out at exactly 60s (previously hung 7+ min), released the lock, and on network restore the watchdog re-init'd and Tor reached Active. Addresses #3225. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
bdfc56cf23
commit
69b8ba9f23
@@ -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()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+37
-18
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user