sepuri sai krishna created KAFKA-20967:
------------------------------------------

             Summary: RemoteLogManager.read() leaks the remote segment 
InputStream when reading a batch fails
                 Key: KAFKA-20967
                 URL: https://issues.apache.org/jira/browse/KAFKA-20967
             Project: Kafka
          Issue Type: Bug
          Components: Tiered-Storage
            Reporter: sepuri sai krishna
            Assignee: sepuri sai krishna


RemoteLogManager.read() 
(storage/src/main/java/org/apache/kafka/server/log/remote/storage/RemoteLogManager.java,
 lines 1857-1949) opens an InputStream from the pluggable RemoteStorageManager 
and closes it in a finally block that is gated on a condition which is not 
satisfied when an exception is thrown:

    EnrichedRecordBatch enrichedRecordBatch = new EnrichedRecordBatch(null, 0);
    InputStream remoteSegInputStream = null;
    try {
        while (enrichedRecordBatch.batch == null && 
rlsMetadataOptional.isPresent()) {
            ...
            remoteSegInputStream = 
remoteStorageManagerPlugin.get().fetchLogSegment(remoteLogSegmentMetadata, 
startPos);
            RemoteLogInputStream remoteLogInputStream = 
getRemoteLogInputStream(remoteSegInputStream);
            enrichedRecordBatch = findFirstBatch(remoteLogInputStream, offset); 
   // can throw
            ...
        }
        ...
    } finally {
        if (enrichedRecordBatch.batch != null) {
            Utils.closeQuietly(remoteSegInputStream, 
"RemoteLogSegmentInputStream");
        }
    }

findFirstBatch() calls RemoteLogInputStream.nextBatch() in a loop. That method 
is declared "throws IOException" and can also throw the unchecked 
CorruptRecordException. If either is thrown, the assignment to 
enrichedRecordBatch never completes, so it retains its stale value with a null 
batch -- either the initial "new EnrichedRecordBatch(null, 0)" on the first 
iteration, or the previous iteration's null-batch value.

The finally block then evaluates that same stale "enrichedRecordBatch.batch != 
null" as false and skips closing remoteSegInputStream, even though the stream 
was successfully opened moments earlier in that iteration. The stream is leaked.

For real tiered-storage plugins (S3/GCS/HDFS-backed and similar) the underlying 
resource is typically a socket or file handle, so each failed read leaks one. 
This is on the consumer fetch path -- RemoteLogManager.read() is invoked for 
every fetch that misses the local log and falls through to tiered storage -- so 
under sustained remote I/O flakiness or a corrupted segment being repeatedly 
requested, the broker leaks a handle per failed read, risking file-descriptor 
or connection-pool exhaustion.

The sibling method lookupTimestamp() in the same class closes its stream 
unconditionally in its finally block and does not have this problem. The 
conditional guard in read() appears to have been intended to avoid a double 
close on the success path, where the stream is consumed later via 
Utils.readFully(), but it does not account for the exception path.

I'm happy to submit a PR for this fix.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to