This is an automated email from the ASF dual-hosted git repository.

singhpk234 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new e4bcaa40cb Dell: Validate ECS stream seeks and closed state (#17962)
e4bcaa40cb is described below

commit e4bcaa40cbb4de6431584cb2a5816cb626c34ca5
Author: Charlie Chen <[email protected]>
AuthorDate: Sun Sep 27 17:34:32 2026 -0700

    Dell: Validate ECS stream seeks and closed state (#17962)
    
    Generated-by: Codex
---
 .../iceberg/dell/ecs/EcsSeekableInputStream.java   |  6 +++
 .../dell/ecs/TestEcsSeekableInputStream.java       | 51 ++++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git 
a/dell/src/main/java/org/apache/iceberg/dell/ecs/EcsSeekableInputStream.java 
b/dell/src/main/java/org/apache/iceberg/dell/ecs/EcsSeekableInputStream.java
index 6d32d3ba98..ea8b4e7dd7 100644
--- a/dell/src/main/java/org/apache/iceberg/dell/ecs/EcsSeekableInputStream.java
+++ b/dell/src/main/java/org/apache/iceberg/dell/ecs/EcsSeekableInputStream.java
@@ -27,6 +27,7 @@ import org.apache.iceberg.io.SeekableInputStream;
 import org.apache.iceberg.metrics.Counter;
 import org.apache.iceberg.metrics.MetricsContext;
 import org.apache.iceberg.metrics.MetricsContext.Unit;
+import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
 
 /**
  * A {@link SeekableInputStream} implementation that warp {@link 
S3Client#readObjectStream(String,
@@ -51,6 +52,7 @@ class EcsSeekableInputStream extends SeekableInputStream {
   private long pos = -1;
 
   private InputStream internalStream;
+  private boolean closed;
 
   private final Counter readBytes;
   private final Counter readOperations;
@@ -69,6 +71,8 @@ class EcsSeekableInputStream extends SeekableInputStream {
 
   @Override
   public void seek(long inputNewPos) {
+    Preconditions.checkState(!closed, "already closed");
+    Preconditions.checkArgument(inputNewPos >= 0, "position is negative: %s", 
inputNewPos);
     if (pos == inputNewPos) {
       return;
     }
@@ -96,6 +100,7 @@ class EcsSeekableInputStream extends SeekableInputStream {
   }
 
   private void checkAndUseNewPos() throws IOException {
+    Preconditions.checkState(!closed, "Cannot read: already closed");
     if (newPos < 0) {
       return;
     }
@@ -116,6 +121,7 @@ class EcsSeekableInputStream extends SeekableInputStream {
 
   @Override
   public void close() throws IOException {
+    closed = true;
     if (internalStream != null) {
       internalStream.close();
     }
diff --git 
a/dell/src/test/java/org/apache/iceberg/dell/ecs/TestEcsSeekableInputStream.java
 
b/dell/src/test/java/org/apache/iceberg/dell/ecs/TestEcsSeekableInputStream.java
index de97aa8d6f..154a4a0765 100644
--- 
a/dell/src/test/java/org/apache/iceberg/dell/ecs/TestEcsSeekableInputStream.java
+++ 
b/dell/src/test/java/org/apache/iceberg/dell/ecs/TestEcsSeekableInputStream.java
@@ -19,6 +19,7 @@
 package org.apache.iceberg.dell.ecs;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import com.emc.object.s3.request.PutObjectRequest;
 import java.io.IOException;
@@ -27,6 +28,8 @@ import org.apache.iceberg.dell.mock.ecs.EcsS3MockRule;
 import org.apache.iceberg.metrics.MetricsContext;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 public class TestEcsSeekableInputStream {
 
@@ -90,4 +93,52 @@ public class TestEcsSeekableInputStream {
           .isEqualTo("012");
     }
   }
+
+  @Test
+  void seekRejectsNegativePositionWithoutChangingState() throws IOException {
+    String objectName = rule.randomObjectName();
+    rule.client()
+        .putObject(new PutObjectRequest(rule.bucket(), objectName, 
"0123456789".getBytes()));
+
+    try (EcsSeekableInputStream input =
+        new EcsSeekableInputStream(
+            rule.client(), new EcsURI(rule.bucket(), objectName), 
MetricsContext.nullMetrics())) {
+      input.seek(2);
+      assertThatThrownBy(() -> input.seek(-1))
+          .isInstanceOf(IllegalArgumentException.class)
+          .hasMessage("position is negative: -1");
+      assertThat(input.getPos()).isEqualTo(2);
+      assertThat(input.read()).isEqualTo('2');
+    }
+  }
+
+  @ParameterizedTest
+  @ValueSource(booleans = {false, true})
+  void rejectsSeekAndReadAfterClose(boolean readBeforeClose) throws 
IOException {
+    String objectName = rule.randomObjectName();
+    rule.client()
+        .putObject(new PutObjectRequest(rule.bucket(), objectName, 
"0123456789".getBytes()));
+
+    try (EcsSeekableInputStream input =
+        new EcsSeekableInputStream(
+            rule.client(), new EcsURI(rule.bucket(), objectName), 
MetricsContext.nullMetrics())) {
+      if (readBeforeClose) {
+        assertThat(input.read()).isEqualTo('0');
+      }
+
+      long position = input.getPos();
+      input.close();
+
+      assertThatThrownBy(() -> input.seek(2))
+          .isInstanceOf(IllegalStateException.class)
+          .hasMessage("already closed");
+      assertThatThrownBy(input::read)
+          .isInstanceOf(IllegalStateException.class)
+          .hasMessage("Cannot read: already closed");
+      assertThatThrownBy(() -> input.read(new byte[1], 0, 1))
+          .isInstanceOf(IllegalStateException.class)
+          .hasMessage("Cannot read: already closed");
+      assertThat(input.getPos()).isEqualTo(position);
+    }
+  }
 }

Reply via email to