Skip to content

ADFA-3191: warn users when the IDE is not allowed to access the local network#1088

Merged
itsaky-adfa merged 14 commits into
stagefrom
fix/ADFA-3191
Mar 31, 2026
Merged

ADFA-3191: warn users when the IDE is not allowed to access the local network#1088
itsaky-adfa merged 14 commits into
stagefrom
fix/ADFA-3191

Conversation

@itsaky-adfa
Copy link
Copy Markdown
Contributor

@itsaky-adfa itsaky-adfa commented Mar 18, 2026

See ADFA-3191 for more details.

Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
@itsaky-adfa itsaky-adfa requested a review from a team March 18, 2026 12:01
@itsaky-adfa itsaky-adfa self-assigned this Mar 18, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe83b4c9-d6e9-433d-8426-0af1e8f92b43

📥 Commits

Reviewing files that changed from the base of the PR and between b19ad56 and c3a4961.

📒 Files selected for processing (4)
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/ListenerState.kt
  • resources/src/main/res/values/strings.xml
✅ Files skipped from review due to trivial changes (1)
  • resources/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (3)
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/ListenerState.kt

📝 Walkthrough

Walkthrough

Migrates the language-server registry from a Java singleton to a Kotlin companion-backed registry, converts debug-connection APIs to suspending per-server results, adds debug-adapter readiness checks and aggregated startup error handling, updates call sites to the new registry API, and adds debugger-related string resources.

Changes

Cohort / File(s) Summary
Registry API & implementation
lsp/api/src/main/java/.../ILanguageServerRegistry.java, lsp/api/src/main/java/.../ILanguageServerRegistry.kt, lsp/api/src/main/java/.../DefaultLanguageServerRegistry.java, lsp/api/src/main/java/.../DefaultLanguageServerRegistry.kt
Removes Java singleton API and Java registry; adds Kotlin ILanguageServerRegistry with companion val default and a new DefaultLanguageServerRegistry implementation exposing suspend connectDebugClient that returns per-server results.
Debug result types & adapter API
lsp/api/src/main/java/.../lsp/debug/DebugClientConnectionResult.kt, lsp/api/src/main/java/.../lsp/debug/IDebugAdapter.kt, lsp/api/src/main/java/.../ILanguageServer.kt
Adds sealed DebugClientConnectionResult (Success/Failure); makes ILanguageServer.serverId non-null and connectDebugClient suspend-returning result; extends IDebugAdapter with isReady and multiple suspend debug operations.
Java language server & adapter implementations
lsp/java/src/main/java/.../JavaLanguageServer.kt, lsp/java/src/main/java/.../JavaDebugAdapter.kt, lsp/java/src/main/java/.../ListenerState.kt, lsp/java/src/main/java/.../BaseJavaCodeAction.kt
Converts connectDebugClient implementations to suspend-returning DebugClientConnectionResult; adds isReady, listener invalidation, safer listener lifecycle and registry access updates to ILanguageServerRegistry.default.
Call-site registry updates
editor/src/main/java/.../JavaLanguage.kt, editor/src/main/java/.../KotlinLanguage.kt, editor/src/main/java/.../XMLLanguage.kt, editor/src/main/java/.../EditorActionsMenu.kt, lsp/xml/src/main/java/.../AdvancedEditProvider.kt, lsp/java/src/main/java/.../BaseJavaCodeAction.kt, app/src/main/java/.../CodeEditorView.kt
Replaces ILanguageServerRegistry.getDefault() usages with ILanguageServerRegistry.default property across editor and LSP call sites; no behavioral logic changes beyond access pattern.
LSP handler & lifecycle
app/src/main/java/com/itsaky/androidide/handlers/LspHandler.kt
Makes connectDebugClient suspend and delegate to registry's suspend API; switches registry accesses to default; preserves lifecycle methods.
Activity startup & aggregated errors
app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt
Makes initLspClient suspend and invoked from coroutines; captures exceptions and aggregates per-server DebugClientConnectionResult.Failure into a consolidated MaterialDialog with mapped errno/context messaging.
Debug action readiness check
app/src/main/java/com/itsaky/androidide/actions/build/DebugAction.kt
Adds runtime check for Java language server / debug-adapter readiness in preExec; shows debugger_not_ready dialog and aborts debug action when adapter not ready.
Strings / resources
resources/src/main/res/values/strings.xml
Adds seven debugger-related string resources for network/errno messaging, startup failure, readiness guidance, and troubleshooting text.
Build / manifest edits
manifest_file, build.gradle.kts
Minor build/manifest line adjustments.

Sequence Diagram(s)

sequenceDiagram
  participant Activity as ProjectHandlerActivity
  participant Registry as ILanguageServerRegistry.default
  participant JavaLS as JavaLanguageServer
  participant JavaDA as JavaDebugAdapter
  participant UI as MaterialDialog

  Activity->>Registry: initLspClient() (suspend)
  Registry->>JavaLS: register/connect servers
  Activity->>Registry: connectDebugClient(IDebugClient) (suspend)
  Registry->>JavaLS: JavaLS.connectDebugClient(client) (suspend)
  JavaLS->>JavaDA: debugAdapter.connectDebugClient(client) (suspend)
  JavaDA-->>JavaLS: DebugClientConnectionResult (Success|Failure)
  JavaLS-->>Registry: per-server DebugClientConnectionResult
  Registry-->>Activity: Map<serverId, DebugClientConnectionResult>
  Activity->>Activity: aggregate failures
  alt Any Failures
    Activity->>UI: show aggregated failure dialog
  else All Success
    Activity->>Activity: continue startup
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • jatezzz
  • dara-abijo-adfa
  • jomen-adfa

Poem

🐰 I hopped through registries, old to new,

Kotlin's default now guides the crew,
Suspends I waited — results in tow,
Debuggers whispered success or woe,
A carrot-toast for safer startup flow!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: warning users when the IDE lacks local network access, which aligns with the substantial additions to debug client connection handling and error messaging throughout the codebase.
Description check ✅ Passed The description references ADFA-3191 Jira issue with a link, indicating the purpose is to warn users about local network access restrictions. This relates directly to the changeset's additions of debugger-ready checks, error handling, and user-facing messages.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ADFA-3191

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/KotlinLanguage.kt (1)

42-42: ⚠️ Potential issue | 🔴 Critical

Add missing Factory import.

Line 42 references Factory unqualified, but this file does not import TreeSitterLanguage.Factory. All peer language implementations (JavaLanguage, JsonLanguage, etc.) import it explicitly. Without the import, this is an unresolved reference and blocks compilation.

🔧 Proposed fix
import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage.Factory
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/KotlinLanguage.kt`
at line 42, KotlinLanguage references an unqualified Factory causing an
unresolved symbol; add the missing import for TreeSitterLanguage.Factory to the
top of the file (same style used in JavaLanguage/JsonLanguage) so the line "val
FACTORY = Factory { KotlinLanguage(it) }" compiles; locate the KotlinLanguage
class and add the import for TreeSitterLanguage.Factory to resolve Factory.
lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt (1)

147-153: ⚠️ Potential issue | 🟠 Major

Don’t swap listener state until the previous listener is handled.

This replaces _listenerState before the existing listener has been reused or shut down. app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt, Lines 970-979, calls connectDebugClient(...) directly, and app/src/main/java/com/itsaky/androidide/actions/build/DebugAction.kt, Lines 85-91, gates the flow on isReady. A second call here can leave the old JDWPListenerThread bound to the socket while the adapter now reports “not ready”. Please guard re-entry here or stop the old listener before swapping state.

Also applies to: 165-175

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt`
around lines 147 - 153, The code currently replaces _listenerState with a new
ListenerState(client, connector, args) immediately, which can leave an old
JDWPListenerThread bound; modify connectDebugClient (and the same pattern around
the other instance at the 165-175 area) to guard re-entry: if an existing
_listenerState exists, either stop/shutdown its JDWPListenerThread (call its
stop/shutdown method) and wait for it to fully exit before assigning the new
ListenerState, or return early if the adapter is not ready, using isReady to
gate the swap; ensure you reference and operate on the existing _listenerState
and its JDWPListenerThread rather than blindly overwriting it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/itsaky/androidide/actions/build/DebugAction.kt`:
- Around line 85-90: preExec calls showDebuggerNotReadyMessage(...) directly on
a potential background thread; wrap that call in
withContext(Dispatchers.Main.immediate) so the AlertDialog is created/shown on
the main thread. Locate the javaLsp check around
ILanguageServerRegistry.default.getServer(JavaLanguageServer.SERVER_ID) in
preExec and replace the direct showDebuggerNotReadyMessage(activity) invocation
with a withContext(Dispatchers.Main.immediate) {
showDebuggerNotReadyMessage(activity) } block to match the other dialog paths.

In
`@app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt`:
- Around line 973-979: The current catch in the debug-client handshake is too
broad (catching Throwable) and will swallow coroutine cancellations; change the
handler in the connectDebugClient(...) call so it only catches Exception, but
explicitly rethrow CancellationException if encountered (i.e., if (e is
CancellationException) throw e), then perform Sentry.captureException and
logger.error and return listOf(DebugClientConnectionResult.Failure(cause = e))
for other exceptions; update the try/catch around
connectDebugClient(debuggerViewModel.debugClient).values accordingly to use
Exception and rethrow CancellationException.

In
`@lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt`:
- Around line 125-130: The current write-lock block in
DefaultLanguageServerRegistry wrongly restores the old instance after inserting
the new one: the mRegister.put(server.serverId, server) result (old) is
immediately put back, preventing updates; fix by deciding intended behavior and
implementing it—either detect duplicate server.serverId and throw/return early
(e.g., check mRegister.containsKey(server.serverId) and fail fast) or keep the
new instance and remove/retire the old (remove the mRegister[old.serverId] = old
line so the new server remains and perform any retirement/cleanup on old).
Ensure changes touch the lock.writeLock() block that uses
mRegister.put(server.serverId, server) and the local variable old.
- Around line 136-140: The unregister(serverId: String) method currently removes
the server from mRegister and throws if absent but never calls shutdown() on the
removed instance; change it to acquire the write lock, remove the entry from
mRegister, if the removed value is null simply return (do not throw), release
the lock, and then call shutdown() on the removed server instance outside the
lock to avoid holding the lock during shutdown; ensure the logic references
unregister, mRegister, and shutdown() so the removed server is shut down after
removal.
- Around line 63-72: In DefaultLanguageServerRegistry.kt, in the block where you
call server.connectDebugClient(client) (which currently catches Throwable and
maps everything to DebugClientConnectionResult.Failure), change the error
handling to re-throw CancellationException immediately so coroutine cancellation
is preserved, and replace the broad catch(Throwable) with catches for the
concrete exceptions that connectDebugClient() can actually throw (log via
sLogger.error and convert those specific exceptions to
DebugClientConnectionResult.Failure); ensure you still log the server.serverId
and the exception when returning Failure but never swallow or convert
CancellationException.
- Around line 55-77: The method connectDebugClient currently holds
lock.readLock() across suspend calls (server.connectDebugClient), blocking
writers; instead, while holding lock.readLock() copy/snapshot the registry
entries (mRegister.values) into a local list and then release the read lock
before iterating and calling the suspend function
server.connectDebugClient(client); build the result map from that snapshot and
preserve the existing error handling (sLogger.error and mapping to
DebugClientConnectionResult.Failure) while ensuring lock.readLock().unlock()
happens immediately after taking the snapshot.
- Around line 111-117: In DefaultLanguageServerRegistry::onProjectInitialized,
acquire the registry read lock before reading mRegister to avoid races with
register()/unregister(); wrap the iteration over mRegister.values and the calls
to server.setupWithProject(project) inside lock.readLock() (or use the
readLock().lock()/unlock() pattern or a try/finally) so access is consistent
with other methods that guard mRegister.

In `@lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt`:
- Line 83: The default implementation of connectDebugClient currently always
returns DebugClientConnectionResult.Success causing false positives; change it
so the method delegates to debugAdapter?.connectDebugClient(client) and return
that result when debugAdapter is non-null, otherwise return an explicit failure
(e.g., DebugClientConnectionResult.Failure or a suitable error variant) so
callers see unsupported/uninitialized debug adapter; update the suspend fun
connectDebugClient(client: IDebugClient): DebugClientConnectionResult to perform
this delegation and null-check.

In
`@lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServerRegistry.kt`:
- Around line 71-81: The singleton getter for ILanguageServerRegistry (sRegistry
/ default) is not thread-safe; replace the nullable backing var and manual
check-then-set with a synchronized lazy initialization: remove sRegistry, make
default a val initialized via Kotlin's lazy with thread-safety (e.g., val
default: ILanguageServerRegistry by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
DefaultLanguageServerRegistry() }) so DefaultLanguageServerRegistry is created
once safely across threads.

In
`@lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt`:
- Around line 627-630: The recovery path in run() that calls
listenerState.startListening() when listenerState.isListening is false can
reopen the socket after a prior close(); before attempting to restart, check the
adapter's shutdown/interrupted state (e.g., a shutdownRequested flag or
Thread.currentThread().isInterrupted()) and skip the self-heal if shutdown was
requested, or alternatively remove the self-heal logic entirely; update the
block around listenerState.isListening and startListening() so it first
returns/exits the loop when a shutdown/interruption is observed, otherwise
proceed to call startListening().
- Around line 154-163: The current catch in the withContext block catches
Throwable and therefore swallows coroutine cancellation; change the error
handling around listenerState.startListening() so CancellationException is not
absorbed: add "import kotlinx.coroutines.CancellationException", narrow the
catch from Throwable to Exception (or explicitly catch Exception), and if you
must catch Throwable ensure you rethrow CancellationException (i.e., if (e is
CancellationException) throw e) before logging/returning the Failure; update the
block around listenerState.startListening() and the catch that currently logs
"Failed to listen for incoming JDWP connections" to follow this pattern.

---

Outside diff comments:
In
`@editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/KotlinLanguage.kt`:
- Line 42: KotlinLanguage references an unqualified Factory causing an
unresolved symbol; add the missing import for TreeSitterLanguage.Factory to the
top of the file (same style used in JavaLanguage/JsonLanguage) so the line "val
FACTORY = Factory { KotlinLanguage(it) }" compiles; locate the KotlinLanguage
class and add the import for TreeSitterLanguage.Factory to resolve Factory.

In
`@lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt`:
- Around line 147-153: The code currently replaces _listenerState with a new
ListenerState(client, connector, args) immediately, which can leave an old
JDWPListenerThread bound; modify connectDebugClient (and the same pattern around
the other instance at the 165-175 area) to guard re-entry: if an existing
_listenerState exists, either stop/shutdown its JDWPListenerThread (call its
stop/shutdown method) and wait for it to fully exit before assigning the new
ListenerState, or return early if the adapter is not ready, using isReady to
gate the swap; ensure you reference and operate on the existing _listenerState
and its JDWPListenerThread rather than blindly overwriting it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc0db31a-cc9b-42c1-b8b4-5787b722d825

📥 Commits

Reviewing files that changed from the base of the PR and between 1061433 and 4400551.

📒 Files selected for processing (20)
  • app/src/main/java/com/itsaky/androidide/actions/build/DebugAction.kt
  • app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt
  • app/src/main/java/com/itsaky/androidide/handlers/LspHandler.kt
  • app/src/main/java/com/itsaky/androidide/ui/CodeEditorView.kt
  • editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/JavaLanguage.kt
  • editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/KotlinLanguage.kt
  • editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/XMLLanguage.kt
  • editor/src/main/java/com/itsaky/androidide/editor/ui/EditorActionsMenu.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.java
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServerRegistry.java
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServerRegistry.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/debug/DebugClientConnectionResult.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/debug/IDebugAdapter.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/actions/BaseJavaCodeAction.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt
  • lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/AdvancedEditProvider.kt
  • resources/src/main/res/values/strings.xml
💤 Files with no reviewable changes (2)
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.java
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServerRegistry.java

Comment thread lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt Outdated
Comment thread lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServerRegistry.kt Outdated
Comment thread lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt Outdated
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/ListenerState.kt (1)

48-53: ⚠️ Potential issue | 🟠 Major

Flip invalidated before stopListening().

invalidate() currently closes the socket first and only then marks the state invalid. That leaves a race where JDWPListenerThread.run() can observe !isListening && !isInvalidated and reopen the listener during shutdown or replacement.

Suggested fix
 fun invalidate() {
-	if (isListening) {
-		stopListening()
-	}
-
-	invalidated.set(true)
+	if (!invalidated.compareAndSet(false, true)) {
+		return
+	}
+	if (isListening) {
+		stopListening()
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/ListenerState.kt`
around lines 48 - 53, The invalidate() method flips invalidated after
stopListening(), which allows JDWPListenerThread.run() to see !isListening &&
!isInvalidated and restart the listener; change invalidate() to set
invalidated.set(true) before calling stopListening() so the run loop sees the
invalidated flag immediately and will not reopen the listener during
shutdown/replacement (refer to invalidate(), invalidated, stopListening() and
JDWPListenerThread.run()).
lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt (2)

136-145: ⚠️ Potential issue | 🟠 Major

Keep unregister() a no-op when the server is already absent.

The API contract says “If any server is registered...”, so throwing on a miss makes teardown non-idempotent and can crash defensive cleanup paths. Return when remove() is null, then shut down only the removed instance.

Suggested fix
 override fun unregister(serverId: String) {
-	val registered = lock.writeLock().withLock {
-		mRegister.remove(serverId)
-	}
-
-	checkNotNull(registered) { "No server found for the given server ID" }
+	val registered = lock.writeLock().withLock {
+		mRegister.remove(serverId)
+	} ?: return
 
 	try {
 		registered.shutdown()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt`
around lines 136 - 145, The unregister method currently throws when
mRegister.remove(serverId) returns null; change it to be a no-op on missing
servers by returning early if the removed value is null. In the
lock.writeLock().withLock block call mRegister.remove(serverId) into registered
and, immediately after the lock, if registered is null simply return; only call
registered.shutdown() inside the try/catch for a non-null registered instance
(keeping the existing try/catch around registered.shutdown()) so teardown is
idempotent.

62-78: ⚠️ Potential issue | 🟠 Major

Don't convert fatal errors into per-server Failure results.

This loop already preserves CancellationException, but catch (e: Throwable) still absorbs fatal JVM errors such as OutOfMemoryError or LinkageError and keeps the coroutine running. Catch Exception (or the concrete connection exceptions) here instead of normalizing every Throwable.

Suggested fix
 		return buildMap {
 			for (server in servers) {
 				try {
 					this[server.serverId] = server.connectDebugClient(client)
-				} catch (e: Throwable) {
-					if (e is CancellationException) {
-						throw e
-					}
-
+				} catch (e: CancellationException) {
+					throw e
+				} catch (e: Exception) {
 					sLogger.error(
 						"Unable to connect LSP server '{}' to debug client",
 						server.serverId,
Based on learnings: In Kotlin files across the AndroidIDE project, prefer narrow exception handling that catches only the specific exception type reported in crashes instead of a broad catch-all.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt`
around lines 62 - 78, The loop in DefaultLanguageServerRegistry.kt currently
catches Throwable and converts fatal JVM errors into per-server Failure; change
the catch to catch Exception instead of Throwable (but keep the existing check
that rethrows CancellationException) so we only handle normal exceptions from
server.connectDebugClient and still rethrow CancellationException; update the
catch block that logs via sLogger.error for server.serverId and assigns
DebugClientConnectionResult.Failure(cause = e) to use the Exception type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt`:
- Around line 83-84: Make the EventBus subscription lifecycle idempotent and
thread-safe by (1) guarding EventBus.getDefault().unregister(this) inside
destroy() with EventBus.getDefault().isRegistered(this) so unregister is only
called when subscribed, and (2) moving the isRegistered() check currently in
register() into the same write-lock critical section where the registry map is
updated so registration and the map update occur atomically; update the
register() method to acquire the write lock, check isRegistered(), then call
register(this) and update the map only if not already registered.

In
`@lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt`:
- Around line 148-178: Currently you reassign _listenerState before tearing down
the previous JDWPListenerThread which can cause the new client to be used when
the old thread accepts a connection; fix by first stopping/interrupting/joining
the existing listenerThread (reference listenerThread and JDWPListenerThread)
and only after it has terminated assign the new _listenerState =
ListenerState(...) and start a new JDWPListenerThread, or alternatively change
the accept callback (onConnectedToVm) to receive the accepted ListenerState
instance instead of reading the mutable _listenerState field so the accepted
connection uses the correct ListenerState/client.

---

Duplicate comments:
In
`@lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt`:
- Around line 136-145: The unregister method currently throws when
mRegister.remove(serverId) returns null; change it to be a no-op on missing
servers by returning early if the removed value is null. In the
lock.writeLock().withLock block call mRegister.remove(serverId) into registered
and, immediately after the lock, if registered is null simply return; only call
registered.shutdown() inside the try/catch for a non-null registered instance
(keeping the existing try/catch around registered.shutdown()) so teardown is
idempotent.
- Around line 62-78: The loop in DefaultLanguageServerRegistry.kt currently
catches Throwable and converts fatal JVM errors into per-server Failure; change
the catch to catch Exception instead of Throwable (but keep the existing check
that rethrows CancellationException) so we only handle normal exceptions from
server.connectDebugClient and still rethrow CancellationException; update the
catch block that logs via sLogger.error for server.serverId and assigns
DebugClientConnectionResult.Failure(cause = e) to use the Exception type.

In
`@lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/ListenerState.kt`:
- Around line 48-53: The invalidate() method flips invalidated after
stopListening(), which allows JDWPListenerThread.run() to see !isListening &&
!isInvalidated and restart the listener; change invalidate() to set
invalidated.set(true) before calling stopListening() so the run loop sees the
invalidated flag immediately and will not reopen the listener during
shutdown/replacement (refer to invalidate(), invalidated, stopListening() and
JDWPListenerThread.run()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2d277436-f07a-456e-9928-24d4d23cd4e6

📥 Commits

Reviewing files that changed from the base of the PR and between 4400551 and b19ad56.

📒 Files selected for processing (7)
  • app/src/main/java/com/itsaky/androidide/actions/build/DebugAction.kt
  • app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/DefaultLanguageServerRegistry.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServerRegistry.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/JavaDebugAdapter.kt
  • lsp/java/src/main/java/com/itsaky/androidide/lsp/java/debug/ListenerState.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • lsp/api/src/main/java/com/itsaky/androidide/lsp/api/ILanguageServer.kt
  • app/src/main/java/com/itsaky/androidide/actions/build/DebugAction.kt

Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
@itsaky-adfa itsaky-adfa merged commit e853bb6 into stage Mar 31, 2026
2 checks passed
@itsaky-adfa itsaky-adfa deleted the fix/ADFA-3191 branch March 31, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants