All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.infinispan.client.hotrod.impl.protocol.PutOutputStream Maven / Gradle / Ivy

package org.infinispan.client.hotrod.impl.protocol;

import static org.infinispan.client.hotrod.impl.Util.await;

import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Semaphore;
import java.util.function.BiFunction;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;

public class PutOutputStream extends OutputStream {
   private static final int BUFFER_SIZE = 8 * 1024;
   private final BiFunction> consumer;
   private final ByteBufAllocator alloc;
   private final Semaphore pendingWrites = new Semaphore(2);
   private ByteBuf buf;
   private volatile Throwable throwable;

   public PutOutputStream(BiFunction> consumer, ByteBufAllocator alloc) {
      this.consumer = consumer;
      this.alloc = alloc;
   }

   private void alloc() {
      buf = alloc.buffer(BUFFER_SIZE, BUFFER_SIZE);
   }

   private void consume(ByteBuf buf, boolean complete) throws IOException {
      try {
         // Don't let more than 2 pending writes at any time
         pendingWrites.acquire();
      } catch (InterruptedException e) {
         throw new IOException(e);
      }
      var stage = consumer.apply(buf, complete)
            .whenComplete((___, t) -> {
               pendingWrites.release();
               if (t != null) {
                  throwable = t;
               }
            });
      // If complete then we have to wait until the last stage is complete. Note all prior stages are guaranteed
      // to be complete as well since they are ordered via single Channel event loop
      if (complete) {
         await(stage);
      }
   }

   @Override
   public synchronized void write(int b) throws IOException {
      if (throwable != null) {
         throw new IOException(throwable);
      }
      if (buf == null) {
         alloc();
      } else if (!buf.isWritable()) {
         consume(buf, false);
         alloc();
      }
      buf.writeByte(b);
   }

   @Override
   public synchronized void write(byte[] b, int off, int len) throws IOException {
      if (throwable != null) {
         throw new IOException(throwable);
      }
      if (buf == null) {
         handleNullByteBufWrite(b, off, len);
         return;
      }
      int writeableAmount = buf.writableBytes();
      if (len > writeableAmount) {
         buf.writeBytes(b, off, writeableAmount);
         consume(buf, false);
         buf = null;
         handleNullByteBufWrite(b, off + writeableAmount, len - writeableAmount);
      } else {
         buf.writeBytes(b, off, len);
      }
   }

   private void handleNullByteBufWrite(byte[] b, int off, int len) throws IOException {
      int needToWrite = len;
      if (len > BUFFER_SIZE) {
         while (needToWrite > BUFFER_SIZE) {
            alloc();
            buf.writeBytes(b, off + (len - needToWrite), BUFFER_SIZE);
            consume(buf, false);
            needToWrite -= BUFFER_SIZE;
         }
      }
      alloc();
      buf.writeBytes(b, off + (len - needToWrite), needToWrite);
   }

   @Override
   public synchronized void flush() throws IOException {
      flush(false);
   }

   private void flush(boolean complete) throws IOException {
      if (throwable != null) {
         throw new IOException(throwable);
      }
      if (buf != null && buf.isReadable()) {
         ByteBuf buf = this.buf;
         this.buf = null;
         consume(buf, complete);
      }
   }

   @Override
   public void close() throws IOException {
      flush(true);
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy