Skip to content

fix: resolve DeadObjectException, OOM, and ANR from Sentry#502

Merged
torlando-tech merged 7 commits intomainfrom
fix/sentry-recent-issues
Feb 21, 2026
Merged

fix: resolve DeadObjectException, OOM, and ANR from Sentry#502
torlando-tech merged 7 commits intomainfrom
fix/sentry-recent-issues

Conversation

@torlando-tech
Copy link
Copy Markdown
Owner

@torlando-tech torlando-tech commented Feb 19, 2026

Summary

Fixes three high-impact Sentry issues from the last 7 days (84 events / 22 users total):

  • COLUMBA-14 (77 events / 17 users): DeadObjectException crashes the service process when a client dies during async initialization. Wraps all 4 IInitializationCallback invocations in try-catch(RemoteException), matching the existing CallbackBroadcaster pattern.

  • COLUMBA-P (3 events / 2 users): OOM in toHexString() when sending large file attachments. A 111MB file creates a 222MB CharArray on top of the source ByteArray. Replaced with streaming hex-to-disk via a new ByteArray.streamHexToFile() extension that uses O(1) memory. Uses the existing _data_ref file-path pattern already supported by the read side (MessageMapper.loadFileAttachmentData()).

  • COLUMBA-3K/2Z/2R (4 events / 3 users): ANR in announceDestination() — missing withContext(Dispatchers.IO) caused the synchronous AIDL binder call (which blocks on Python's GIL) to run on the main thread. All 15+ other binder methods in the file already use Dispatchers.IO; this one was missed.

Changes

File Change
ReticulumServiceBinder.kt Wrap 4 callback sites in try-catch(RemoteException)
FileUtils.kt Add top-level ByteArray.streamHexToFile() extension
MessagingViewModel.kt Inject @ApplicationContext, stream file hex to disk via _data_ref, extract buildFileAttachmentsArray() and buildAppExtensions() from buildFieldsJson()
ConversationRepository.kt Pass through existing _data_ref in extractFileAttachmentsForSent()
ServiceReticulumProtocol.kt Add withContext(Dispatchers.IO) to announceDestination()

Manual test plan

Fix 1: DeadObjectException (COLUMBA-14)

  • Install on device, open app, wait for initialization to complete
  • Force-stop the app (Settings > Apps > Force Stop) immediately after launching, before "READY" status appears
  • Check logcat for Client died before initialization warning (not a crash)
  • Verify the service process stays alive and does not crash
  • Re-open the app — should reconnect and initialize normally

Fix 2: OOM in file attachments (COLUMBA-P)

  • Open a conversation, attach a large file (50MB+) and send
  • Verify the message sends successfully without OOM crash
  • Check that a .hex temp file was created in the cache directory (adb shell ls /data/data/com.lxmf.messenger/cache/outgoing_*.hex)
  • Verify the recipient receives the file attachment correctly
  • Attach and send a small file (<1MB) — should also work (same code path)
  • Attach and send an image — image path (Field 6) should still use inline hex (not _data_ref)
  • Verify temp .hex files are cleaned up by the existing cleanupAllTempFiles() routine

Fix 3: ANR in announceDestination (COLUMBA-3K/2Z/2R)

  • Open Debug screen, tap "Announce" button
  • Verify the announce completes without ANR dialog
  • Monitor logcat for ANR or Input dispatching timed out — should see none
  • Repeat the announce 3-5 times rapidly — no ANR should occur
  • Verify the announce still succeeds (check Sentry or logcat for success)

Regression checks

  • Send a text-only message — should work as before
  • Send a message with an image attachment — should work as before
  • Send a message with a file attachment — should work, with _data_ref in fieldsJson
  • Receive a message with a file attachment — should display correctly (read path unchanged)
  • Cold start the app — initialization should complete normally

🤖 Generated with Claude Code

Fix three high-impact Sentry issues:

- COLUMBA-14: Wrap IInitializationCallback invocations in try-catch
  for RemoteException to prevent service crash when client dies during
  async initialization (77 events / 17 users)

- COLUMBA-P: Stream file attachment hex encoding to disk instead of
  allocating CharArray(size*2) in memory, preventing OOM on large
  files. Uses existing _data_ref pattern. (3 events / 2 users)

- COLUMBA-3K/2Z/2R: Add missing withContext(Dispatchers.IO) to
  announceDestination() to move synchronous AIDL binder call off
  main thread, preventing ANR (4 events / 3 users)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 19, 2026

Greptile Summary

This PR addresses three high-impact production issues identified in Sentry with targeted, well-scoped fixes:

Fix 1: DeadObjectException (COLUMBA-14) - Wrapped all 4 IInitializationCallback invocations in try-catch blocks to handle RemoteException when clients die during async service initialization. This follows the existing pattern used in CallbackBroadcaster and prevents service process crashes.

Fix 2: OOM in file attachments (COLUMBA-P) - Introduced ByteArray.streamHexToFile() extension that uses a BufferedWriter to stream hex-encoded data directly to disk with O(1) memory overhead, replacing the previous in-memory toHexString() approach that allocated a CharArray double the size of the input. For a 111MB file, this eliminates the 222MB allocation. The implementation correctly integrates with the existing _data_ref pattern already supported by MessageMapper.loadFileAttachmentData().

Fix 3: ANR in announceDestination (COLUMBA-3K/2Z/2R) - Added withContext(Dispatchers.IO) to move the synchronous AIDL binder call off the main thread, consistent with all other binder methods in the file.

Key observations:

  • The dispatcher change from Dispatchers.Default to Dispatchers.IO in buildFieldsJson is correct since the function now performs file I/O operations
  • The ConversationRepository correctly passes through existing _data_ref paths to avoid re-processing already-streamed data
  • Test files properly updated with mocked applicationContext parameter
  • One issue identified: hex temp files won't be cleaned up by the existing cleanup routine

Confidence Score: 4/5

  • Safe to merge after addressing the temp file cleanup issue
  • The fixes are well-implemented and follow existing patterns. The DeadObjectException and ANR fixes are straightforward defensive programming. The OOM fix correctly integrates with the existing _data_ref infrastructure. However, the score is 4 instead of 5 due to the temp file cleanup gap - .hex files in root cacheDir won't be automatically cleaned up by the existing cleanup routine, which could lead to disk space accumulation over time.
  • Pay attention to MessagingViewModel.kt line 2267 - ensure the hex temp file cleanup strategy is addressed before merging

Important Files Changed

Filename Overview
app/src/main/java/com/lxmf/messenger/service/binder/ReticulumServiceBinder.kt Wraps 4 IInitializationCallback invocations in try-catch(RemoteException) to prevent DeadObjectException crashes when client dies during async initialization
app/src/main/java/com/lxmf/messenger/util/FileUtils.kt Adds ByteArray.streamHexToFile() extension for O(1) memory hex encoding to prevent OOM on large file attachments
app/src/main/java/com/lxmf/messenger/viewmodel/MessagingViewModel.kt Injects ApplicationContext, streams file hex to disk via _data_ref, refactors buildFieldsJson into smaller functions, changes Dispatchers.Default to IO for file operations
data/src/main/java/com/lxmf/messenger/data/repository/ConversationRepository.kt Passes through existing _data_ref in extractFileAttachmentsForSent to preserve hex file paths already written to disk
app/src/main/java/com/lxmf/messenger/reticulum/protocol/ServiceReticulumProtocol.kt Adds withContext(Dispatchers.IO) to announceDestination to prevent ANR from synchronous AIDL binder call on main thread

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User sends message with file attachment] --> B{File size check}
    B --> C[buildFieldsJson with cacheDir]
    C --> D[buildFileAttachmentsArray]
    D --> E{cacheDir != null?}
    E -->|Yes| F[Create hex file: outgoing_timestamp_index.hex]
    F --> G[streamHexToFile - O1 memory]
    G --> H[Store _data_ref path in JSON]
    E -->|No| I[Fallback to toHexString - in-memory]
    I --> H
    H --> J[ConversationRepository.extractFileAttachmentsForSent]
    J --> K{Has _data_ref?}
    K -->|Yes, data empty| L[Pass through existing _data_ref]
    K -->|No, has data| M[Extract to disk via saveAttachment]
    L --> N[Message ready for transmission]
    M --> N
    
    style F fill:#e1f5e1
    style G fill:#e1f5e1
    style L fill:#e1f5e1
Loading

Last reviewed commit: 22a0fc4

@sentry
Copy link
Copy Markdown
Contributor

sentry bot commented Feb 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The PR added applicationContext: Context as the first constructor parameter
but the test files weren't updated, causing compilation failures in all 4 CI
test shards.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
streamHexToFile performs blocking file I/O which should not run on
Dispatchers.Default (CPU-bound pool sized to core count). Dispatchers.IO
shares threads with Default for the CPU work but can expand beyond the
core count when threads block on write syscalls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@torlando-tech
Copy link
Copy Markdown
Owner Author

@greptileai

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

torlando-tech and others added 2 commits February 20, 2026 18:35
Hex files from streamHexToFile were written directly to cacheDir root,
so cleanupAllTempFiles never removed them. Move to outgoing_hex/
subdirectory and add it to the cleanup list.

Also stub applicationContext.cacheDir in tests to return a real temp
directory so streamHexToFile can write during send tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cancel serverScope before calling server.stop() to prevent queued
coroutines from re-acquiring isRunning after stop clears it. Add
withTimeout safety net to prevent CI hangs if teardown blocks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
torlando-tech and others added 2 commits February 20, 2026 21:17
If streamHexToFile throws (disk full, permission error) during
buildFieldsJson, the exception previously propagated past
saveMessageToDatabase — the message was delivered to the recipient
but disappeared from the sender's conversation history.

Wrap buildFieldsJson in try-catch(IOException) and fall back to
text-only fields so the message is always saved locally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Images using the ORIGINAL preset can be up to 25MB, which would
allocate a 50MB CharArray in toHexString(). Use streamHexToFile()
with _file_ref for images above 512KB, matching the file attachment
pattern. The read side already handles _file_ref for field 6.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@torlando-tech torlando-tech merged commit dfc2c44 into main Feb 21, 2026
13 checks passed
@torlando-tech torlando-tech deleted the fix/sentry-recent-issues branch February 21, 2026 04:47
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.

1 participant