Skip to content

Replace IllegalStateException with EOFException in ProtobufTools.readRawVarint32 to enable graceful error handling #2684

@DmytroBorysovSpotOn

Description

@DmytroBorysovSpotOn

Problem

The ProtobufTools.readRawVarint32 method throws IllegalStateException when encountering corrupted or incomplete data in disk-buffered files. Since IllegalStateException is an unchecked exception, it propagates up the call stack unhandled, causing the entire application to crash.

Stack Trace

io.opentelemetry.contrib.disk.buffering.internal.utils.ProtobufTools.readRawVarint32 (ProtobufTools.java:32)
io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.DelimitedProtoStreamReader.getNextItemSize (DelimitedProtoStreamReader.java:46)
io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.DelimitedProtoStreamReader.readNext (DelimitedProtoStreamReader.java:23)
io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile.readNext (ReadableFile.java:77)
io.opentelemetry.contrib.disk.buffering.internal.storage.Storage.doReadNext (Storage.java:120)
io.opentelemetry.contrib.disk.buffering.internal.storage.Storage.readNext (Storage.java:94)
io.opentelemetry.contrib.disk.buffering.internal.storage.StorageIterator.findNext (StorageIterator.java:80)
io.opentelemetry.contrib.disk.buffering.internal.storage.StorageIterator.hasNext (StorageIterator.java:41)
io.opentelemetry.android.features.diskbuffering.SignalFromDiskExporter.export (SignalFromDiskExporter.kt:73)
io.opentelemetry.android.features.diskbuffering.SignalFromDiskExporter.exportBatchOfLogs (SignalFromDiskExporter.kt:65)
io.opentelemetry.android.features.diskbuffering.SignalFromDiskExporter.exportBatchOfEach (SignalFromDiskExporter.kt:96)
io.opentelemetry.android.features.diskbuffering.scheduler.DefaultExportScheduler.onRun (DefaultExportScheduler.kt:31)
io.opentelemetry.android.internal.services.periodicwork.PeriodicRunnable.run (PeriodicRunnable.kt:24)
java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
java.lang.Thread.run (Thread.java:920)

Root Cause

The crash occurs when:

  1. A varint is partially written to disk (e.g., app killed during write operation)
  2. On next app start, readRawVarint32 attempts to read the incomplete varint
  3. The first byte has continuation bit set (0x80-0xFF), indicating more bytes follow
  4. input.read() returns -1 (EOF) because the file is truncated
  5. The method throws IllegalStateException, which is not caught anywhere

Current Code

public static int readRawVarint32(int firstByte, InputStream input) throws IOException {
    // ...
    for (; offset < 32; offset += 7) {
        int b = input.read();
        if (b == -1) {
            throw new IllegalStateException();  // ❌ Unchecked - causes app crash
        }
        // ...
    }
    // ...
}

Proposed Solution
Replace IllegalStateException with EOFException (or StreamCorruptedException), which is a checked exception that extends IOException. This forces callers to handle the error gracefully.

Benefits
Graceful Degradation: Callers can catch EOFException and skip corrupted messages instead of crashing
Better Error Messages: Provides context about where corruption occurred
Semantic Correctness: EOFException accurately describes the problem (unexpected end of file)
Forced Error Handling: Checked exception ensures all callers handle the error appropriately

Environment
Library: io.opentelemetry.android:okhttp-3.0-agent:0.9.1-alpha
Platform: Android
Occurrence: When reading corrupted/incomplete disk-buffered telemetry data

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions