22
33import com .sap .cds .sdm .constants .SDMConstants ;
44import com .sap .cds .sdm .service .exceptions .InsufficientDataException ;
5- import io .reactivex .Flowable ;
65import java .io .*;
7- import java .util .List ;
86import java .util .concurrent .*;
97import java .util .concurrent .atomic .AtomicBoolean ;
108import java .util .concurrent .atomic .AtomicLong ;
@@ -25,7 +23,8 @@ public class ReadAheadInputStream extends InputStream {
2523 private final ExecutorService executor =
2624 Executors .newFixedThreadPool (2 ); // Thread pool to Read next chunk
2725 private final BlockingQueue <byte []> chunkQueue =
28- new LinkedBlockingQueue <>(50 ); // Next chunk is read to a queue
26+ new LinkedBlockingQueue <>(
27+ 4 ); // Reduced from 50 to 4 (80MB) - balances read-ahead performance with heap constraints
2928
3029 public ReadAheadInputStream (InputStream inputStream , long totalSize ) throws IOException {
3130 if (inputStream == null ) {
@@ -91,42 +90,50 @@ private void preloadChunks() {
9190
9291 private void readChunk (AtomicReference <byte []> bufferRef , AtomicLong bytesReadAtomic )
9392 throws IOException {
93+ int maxRetries = 5 ;
94+ int retryCount = 0 ;
95+
9496 while (bytesReadAtomic .get () < CHUNK_SIZE ) {
9597 try {
96- List <Integer > results =
97- Flowable .fromCallable (
98- () -> {
99- byte [] buffer = bufferRef .get ();
100- // Read from stream and update bytesReadAtomic
101- int result =
102- originalStream .read (
103- buffer ,
104- (int ) bytesReadAtomic .get (),
105- CHUNK_SIZE - (int ) bytesReadAtomic .get ());
106- if (result > 0 ) {
107- bytesReadAtomic .addAndGet (result );
108- } else if (result == 0 ) {
109- throw new InsufficientDataException ("Read returned 0 bytes" );
110- }
111- return result ;
112- })
113- .retryWhen (RetryUtils .retryLogic (5 )) // Apply retry logic with 5 attempts
114- .toList ()
115- .blockingGet ();
116-
117- if (results == null || results .isEmpty ())
118- throw new IOException ("Failed to read chunk: results is null or empty" );
119- // Check if the read was successful
120-
121- int readAttempt = results .get (0 );
122-
123- if (readAttempt == -1 ) {
98+ byte [] buffer = bufferRef .get ();
99+ int result =
100+ originalStream .read (
101+ buffer , (int ) bytesReadAtomic .get (), CHUNK_SIZE - (int ) bytesReadAtomic .get ());
102+
103+ if (result > 0 ) {
104+ bytesReadAtomic .addAndGet (result );
105+ retryCount = 0 ; // Reset retry count on successful read
106+ } else if (result == -1 ) {
124107 logger .info ("EOF reached while reading the stream." );
125108 break ;
109+ } else if (result == 0 ) {
110+ // Treat 0 bytes read as InsufficientDataException (matches original behavior)
111+ throw new InsufficientDataException ("Read returned 0 bytes" );
112+ }
113+ } catch (EOFException | InsufficientDataException e ) {
114+ // These exceptions should be retried (matching RetryUtils.shouldRetry())
115+ retryCount ++;
116+ if (retryCount >= maxRetries ) {
117+ logger .error ("Failed to read chunk after {} retries: {}" , maxRetries , e .getMessage (), e );
118+ throw new IOException ("Failed to read chunk after retries" , e );
119+ }
120+ long delaySeconds =
121+ (long ) Math .pow (2 , retryCount ); // Exponential backoff: 2, 4, 8, 16, 32 seconds
122+ logger .info (
123+ "Retry attempt {} failed. Retrying in {} seconds. Error: {}" ,
124+ retryCount ,
125+ delaySeconds ,
126+ e .getMessage ());
127+ try {
128+ Thread .sleep (delaySeconds * 1000 ); // Convert to milliseconds
129+ } catch (InterruptedException ie ) {
130+ Thread .currentThread ().interrupt ();
131+ throw new IOException ("Interrupted during retry backoff" , ie );
126132 }
127- } catch (Exception e ) {
128- logger .error ("Failed to read chunk after retries: {}" , e .getMessage (), e );
129- throw new IOException ("Failed to read chunk" , e );
133+ } catch (IOException e ) {
134+ // Other IOExceptions should fail immediately (not retried in original)
135+ logger .error ("Non-retryable IOException: {}" , e .getMessage (), e );
136+ throw e ;
130137 }
131138 }
132139 }
0 commit comments