ADFA-4330: Fix TermuxService shellManager NPE on OS service auto-restart#1413
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 Walkthrough
Walkthrough
ChangesTermuxShellManager NPE fix and regression test
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java (1)
42-47: ⚡ Quick winAdd an
@Aftermethod to ensure proper cleanup even if the test fails.The
@Beforehook correctly resets the singleton, but if the test fails before line 67,controller.destroy()won't execute, potentially leaving resources uncleaned. Add an@Aftermethod to guarantee cleanup runs after every test.♻️ Proposed refactor to add `@After` cleanup
+ private ServiceController<TermuxService> controller; + /** * Reset the static singleton to null to mimic a fresh process where * TermuxApplication.onCreate() (which would normally call init()) never ran. */ `@Before` public void clearShellManagerSingleton() throws Exception { Field f = TermuxShellManager.class.getDeclaredField("shellManager"); f.setAccessible(true); f.set(null, null); } + `@After` + public void cleanup() { + if (controller != null) { + controller.destroy(); + controller = null; + } + } + /** Service onCreate() with a null shell-manager singleton must not NPE and must expose usable sessions. */ `@Test` public void onCreateWithNullSingleton_doesNotNpe_andSessionsAreUsable() { // Sanity: the auto-restart precondition — singleton is null going in. assertEquals(null, TermuxShellManager.getShellManager()); // Drive the REAL service lifecycle. On stage this throws NullPointerException inside // onCreate() -> runStartForeground() -> buildNotification() -> getTermuxSessionsSize(). - ServiceController<TermuxService> controller = + controller = Robolectric.buildService(TermuxService.class).create(); TermuxService service = controller.get(); // After a clean onCreate(), the shell manager must be wired up and queryable. assertNotNull("mShellManager must be initialized after onCreate()", TermuxShellManager.getShellManager()); assertEquals("A freshly created service manages zero sessions", 0, service.getTermuxSessionsSize()); - - controller.destroy(); }Also applies to: 67-67
🤖 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 `@termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java` around lines 42 - 47, Add an `@After` method to guarantee resource cleanup after every test execution, regardless of whether the test passes or fails. This method should contain the same cleanup logic that is currently at line 67 (controller.destroy()), ensuring that resources are properly released even if the test throws an exception before reaching that line. This complements the existing `@Before` method clearShellManagerSingleton() to provide comprehensive test lifecycle management.
🤖 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 `@termux/termux-app/src/main/java/com/termux/app/TermuxService.java`:
- Around line 125-128: The TermuxShellManager.init() method lacks
synchronization, creating a race condition where multiple threads could bypass
the null check and create duplicate singleton instances. Add the synchronized
keyword to the init() method declaration to ensure thread-safe access to the
singleton creation logic. This will prevent multiple threads from concurrently
executing the method and creating competing instances of the TermuxShellManager.
---
Nitpick comments:
In
`@termux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java`:
- Around line 42-47: Add an `@After` method to guarantee resource cleanup after
every test execution, regardless of whether the test passes or fails. This
method should contain the same cleanup logic that is currently at line 67
(controller.destroy()), ensuring that resources are properly released even if
the test throws an exception before reaching that line. This complements the
existing `@Before` method clearShellManagerSingleton() to provide comprehensive
test lifecycle management.
🪄 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: d6111333-1495-4bac-b5e3-5dbf392c89f1
📒 Files selected for processing (2)
termux/termux-app/src/main/java/com/termux/app/TermuxService.javatermux/termux-app/src/test/java/com/termux/app/TermuxServiceShellManagerNpeTest.java
Sentry APPDEVFORALL-BR. Use TermuxShellManager.init() instead of getShellManager() so the singleton exists after OS service restart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n auto-restart Reproduces the OS service auto-restart scenario where TermuxApplication.onCreate() never ran, leaving the static TermuxShellManager singleton null. Drives the real TermuxService.onCreate() via Robolectric; on the pre-fix baseline this NPEs in onCreate -> runStartForeground -> buildNotification -> getTermuxSessionsSize when mShellManager is null. Passes on the fix (init() lazily creates the singleton). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
665eb9f to
a495d61
Compare
Jira Ticket: https://appdevforall.atlassian.net/browse/ADFA-4330
Sentry Issue: https://appdevforall-inc-9p.sentry.io/issues/APPDEVFORALL-BR
Reproduction Details
When the OS auto-restarts
TermuxServiceafter the app process was killed,TermuxApplication.onCreate()(which creates theTermuxShellManagersingleton) never runs, soTermuxService.onCreate()reads a nullshellManagerand NPEs while building its foreground notification.Stack Trace
User Steps
User steps leading up to crash, based on Sentry breadcrumbs:
Was able to reproduce in a unit test?
Yes.
TermuxServiceShellManagerNpeTest(:termux:termux-app, Robolectric) forces the staticTermuxShellManagersingleton to null (simulating the no-Application-init restart), drives the realTermuxService.onCreate()viaServiceController, and assertsgetTermuxSessionsSize()returns 0 without NPE. Baseline: FAILS with the exact NPE path; branch: passes.What Was Fixed
onCreate()usesTermuxShellManager.init(getApplicationContext())(lazily creates the singleton) instead ofgetShellManager()(returns null when Application init never ran).Testing
:termux:termux-app:testV8DebugUnitTest→ green (red on baseline). Local CodeRabbit review: no findings. Reviewer note (local):TermuxShellManager.init()is unsynchronized while sibling mutators aresynchronized— the new service path slightly widens a pre-existing init race; consider synchronizinginit(). Add an@Afterresetting the static singleton for test hygiene.Fixes APPDEVFORALL-BR