Skip to content

ADFA-4010: close indexing services on background thread#1308

Merged
itsaky-adfa merged 4 commits into
stagefrom
fix/ADFA-4010
May 19, 2026
Merged

ADFA-4010: close indexing services on background thread#1308
itsaky-adfa merged 4 commits into
stagefrom
fix/ADFA-4010

Conversation

@itsaky-adfa
Copy link
Copy Markdown
Contributor

@itsaky-adfa itsaky-adfa commented May 14, 2026

See ADFA-4010 for more details.

Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
@itsaky-adfa itsaky-adfa requested a review from a team May 14, 2026 13:06
@itsaky-adfa itsaky-adfa self-assigned this May 14, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Review Change Stack

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: 39bb2f77-5b81-4e97-bcd0-da19970162be

📥 Commits

Reviewing files that changed from the base of the PR and between 386b467 and 65c3240.

📒 Files selected for processing (1)
  • lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt

📝 Walkthrough

Release Notes

Features & Improvements

  • Non-blocking Service Shutdown: IndexingServiceManager.close() now closes indexing services on a background thread, preventing the caller from being blocked during shutdown
  • Concurrent Shutdown with Timeout: Indexing services are now closed concurrently (in reverse registration order) with a 10-second timeout per service (SERVICE_CLOSE_TIMEOUT), improving shutdown performance
  • Proper Exception Handling: Added explicit handling of CancellationException during shutdown with rethrow behavior to maintain exception semantics

Technical Changes

  • Refactored IndexingServiceManager.close() from sequential to coroutine-based shutdown
  • Added joinAll() and withTimeoutOrNull() coroutine utilities for concurrent close operations
  • Registry is now closed in a separate timed job
  • In-flight work is cancelled via scope.coroutineContext.cancelChildren()
  • Updated import in ProjectManagerImpl.kt to use IndexingServiceManager instead of IndexingService

Risks & Considerations

  • Timeout Adequacy: The 10-second timeout per service may be insufficient for services with slower shutdown paths; monitor for timeout-related failures in production
  • Concurrent vs. Sequential Trade-offs: Concurrent shutdown may mask or delay detection of individual service shutdown issues, making debugging harder
  • Complexity: Coroutine-based shutdown adds significant complexity compared to the previous sequential approach; high code review effort warranted

Walkthrough

IndexingServiceManager.close() is rewritten to concurrently shut down registered services and the registry with a 10s per-target timeout, await join of close jobs, then cancel remaining work and clear state. ProjectManagerImpl updates an import to use IndexingServiceManager.

Changes

Graceful service shutdown with timeouts

Layer / File(s) Summary
Coroutine imports and shutdown timeout constant
lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt
Adds coroutine join/timeout helpers and a companion SERVICE_CLOSE_TIMEOUT set to 10 seconds.
Concurrent graceful service close with per-service timeout
lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt
close() launches service close jobs concurrently (reverse registration order), wraps each service.close() in withTimeoutOrNull(SERVICE_CLOSE_TIMEOUT), runs a timed registry close job, awaits all jobs via joinAll, logs outcomes, cancels in-flight work, clears services, and sets initialized = false.
Manager import update in ProjectManagerImpl
subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt
Replaces IndexingService import with IndexingServiceManager in the import list.

Sequence Diagram

sequenceDiagram
  participant Caller as IndexingServiceManager.close()
  participant Service as IndexingService
  participant Registry as ServiceRegistry
  participant Scope as CoroutineScope
  Caller->>Service: launch close() with withTimeoutOrNull(SERVICE_CLOSE_TIMEOUT)
  Caller->>Registry: launch registry.close() with timeout
  Caller->>Caller: collect close jobs
  Caller->>Caller: runCatching { joinAll(closeJobs, registryJob) }
  Caller->>Scope: scope.coroutineContext.cancelChildren() (if needed)
  Caller->>Caller: clear services, set initialized = false, log shutdown
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • jomen-adfa
  • jatezzz

Poem

🐰 I nudge the threads to take a rest,
Timed goodbyes so none are pressed,
Close calls launched in thoughtful race,
Each one bounded, kept in place.
Logs whisper, "All cleared" — a tidy nest.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: refactoring IndexingServiceManager.close() to use coroutine-based concurrent shutdown on background threads instead of sequential shutdown.
Description check ✅ Passed The description references the ADFA-4010 Jira issue which provides context, and the commit messages clarify the purpose: rethrow cancellation exceptions and close registry on background thread.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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-4010

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: 1

Caution

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

⚠️ Outside diff range comments (1)
lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt (1)

143-157: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Finish async close work before clearing manager state.

Lines 153-157 run immediately after the jobs are scheduled, not after they finish. That lets service.close() race registry.close() and makes the final shutdown log fire too early.

Suggested fix
 override fun close() {
 	log.info("Shutting down indexing services")
+	initialized = false
 
@@
 	scope.launch {
 		runCatching { joinAll(*cancellationJobs.toTypedArray()) }
 			.onFailure { err ->
 				log.error("Failed to close indexing services", err)
 			}
 
 		// Cancel in-flight work
 		scope.coroutineContext.cancelChildren()
+
+		services.clear()
+		registry.close()
+		log.info("Indexing services shut down")
 	}
-
-	services.clear()
-	registry.close()
-	initialized = false
-
-	log.info("Indexing services shut down")
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt`
around lines 143 - 157, The shutdown schedules a coroutine with scope.launch but
then immediately clears state (services.clear, registry.close,
initialized=false) and logs shutdown before the async work finishes; change the
logic to wait for the launched coroutine to complete before mutating
state—either by making the surrounding function suspend and using runCatching {
joinAll(*cancellationJobs.toTypedArray());
scope.coroutineContext.cancelChildren() } directly, or by capturing the Job
returned from scope.launch (the launch call around joinAll) and calling
job.join() (or awaiting it in a runBlocking) before calling services.clear,
registry.close, setting initialized = false and logging; ensure the join/await
happens on the same coroutine scope so service.close()/registry.close() cannot
race with state clearing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt`:
- Around line 130-136: The catch block inside the
withTimeoutOrNull(SERVICE_CLOSE_TIMEOUT) around service.close() is catching
CancellationException and masking timeout cancellations; update the exception
handling in IndexingServiceManager (the withTimeoutOrNull block that calls
service.close() and logs via log.debug/log.error) to rethrow
CancellationException (or specifically catch CancellationException first and
throw it) and only handle other exceptions (e.g., Exception) for logging, so a
timeout cancellation propagates as intended to the timeout path.

---

Outside diff comments:
In
`@lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt`:
- Around line 143-157: The shutdown schedules a coroutine with scope.launch but
then immediately clears state (services.clear, registry.close,
initialized=false) and logs shutdown before the async work finishes; change the
logic to wait for the launched coroutine to complete before mutating
state—either by making the surrounding function suspend and using runCatching {
joinAll(*cancellationJobs.toTypedArray());
scope.coroutineContext.cancelChildren() } directly, or by capturing the Job
returned from scope.launch (the launch call around joinAll) and calling
job.join() (or awaiting it in a runBlocking) before calling services.clear,
registry.close, setting initialized = false and logging; ensure the join/await
happens on the same coroutine scope so service.close()/registry.close() cannot
race with state clearing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf20a36b-6c35-4403-84c7-b4b729134cf7

📥 Commits

Reviewing files that changed from the base of the PR and between b85bbc2 and 386b467.

📒 Files selected for processing (2)
  • lsp/indexing/src/main/kotlin/org/appdevforall/codeonthego/indexing/service/IndexingServiceManager.kt
  • subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt
💤 Files with no reviewable changes (1)
  • subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt

Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Copy link
Copy Markdown
Collaborator

@jatezzz jatezzz left a comment

Choose a reason for hiding this comment

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

Take a look at coderabbit comment.

@itsaky-adfa itsaky-adfa merged commit 57faa9a into stage May 19, 2026
2 checks passed
@itsaky-adfa itsaky-adfa deleted the fix/ADFA-4010 branch May 19, 2026 18:43
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