From 8e6d2164d7d0703ecf67344742374aef8b2a978a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 17:54:52 +0000 Subject: [PATCH] fix: move ArtiNative initialization off main thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System.loadLibrary("arti_android") runs when ArtiNative is first accessed. Previously this happened in TorService.init (via setLogCallback), which ran on main thread during AppModules creation. Moved setLogCallback into start(), which runs on Dispatchers.IO. Now all JNI calls — including the native library load — happen off main thread. https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu --- .../amethyst/ui/tor/TorService.kt | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) 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 70f30901e..25d31fea1 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 @@ -39,6 +39,9 @@ private const val DEFAULT_SOCKS_PORT = 19050 * lifetime — its state file lock is never released until the process exits. * The SOCKS proxy can be started/stopped independently without affecting * the TorClient or its file locks. + * + * All JNI calls (including System.loadLibrary) run on [Dispatchers.IO] + * to avoid blocking the main thread. */ class TorService( val context: Context, @@ -50,28 +53,9 @@ class TorService( private val _status = MutableStateFlow(TorServiceStatus.Off) val status: StateFlow = _status.asStateFlow() - init { - ArtiNative.setLogCallback { text -> - Log.d("TorService") { - val newLine = text.indexOf('\n') - if (newLine > 1) { - "Arti: ${text.substring(0, newLine)}" - } else { - "Arti: $text" - } - } - - when { - text.contains("Sufficiently bootstrapped", ignoreCase = true) -> { - _status.value = TorServiceStatus.Active(socksPort) - Log.d("TorService") { "Arti SOCKS proxy active on port $socksPort" } - } - } - } - } - /** * Initialize the TorClient (once) and start the SOCKS proxy. + * Must be called from a coroutine on [Dispatchers.IO]. */ suspend fun start() { if (proxyRunning.get()) { @@ -83,8 +67,28 @@ class TorService( _status.value = TorServiceStatus.Connecting withContext(Dispatchers.IO) { - // Initialize TorClient once — this bootstraps the Tor network + // Initialize TorClient once — this bootstraps the Tor network. + // setLogCallback and initialize are the first ArtiNative calls, + // which triggers System.loadLibrary on this IO thread. if (initialized.compareAndSet(false, true)) { + ArtiNative.setLogCallback { text -> + Log.d("TorService") { + val newLine = text.indexOf('\n') + if (newLine > 1) { + "Arti: ${text.substring(0, newLine)}" + } else { + "Arti: $text" + } + } + + when { + text.contains("Sufficiently bootstrapped", ignoreCase = true) -> { + _status.value = TorServiceStatus.Active(socksPort) + Log.d("TorService") { "Arti SOCKS proxy active on port $socksPort" } + } + } + } + val dataDir = File(context.filesDir, "arti").absolutePath Log.d("TorService") { "Initializing Arti with data dir: $dataDir" }