
org.apache.servicecomb.foundation.vertx.stream.OutputStreamToWriteStream Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.servicecomb.foundation.vertx.stream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.servicecomb.foundation.common.io.AsyncCloseable;
import io.vertx.core.AsyncResult;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.streams.WriteStream;
/**
* for pump from a readStream
*/
public class OutputStreamToWriteStream implements WriteStream, AsyncCloseable {
private static final int DEFAULT_MAX_BUFFERS = 4;
private static final int SMALLEST_MAX_BUFFERS = 2;
private final OutputStream outputStream;
private final Context context;
private final boolean autoCloseOutputStream;
private Handler exceptionHandler;
// resume readStream
private Handler drainHandler;
// when invoke close, but outputStream not write all data, must put close logic to closedDeferred
private Runnable closedDeferred;
private boolean closed;
// buffers.size() need to loop all node, and maybe result is not correct in concurrent condition
// we just need to flow control by pump, so use another size
private final Queue buffers = new ConcurrentLinkedQueue<>();
private int currentBufferCount;
// just indicate if buffers is full, not control add logic
// must >= SMALLEST_MAX_BUFFERS
// if < SMALLEST_MAX_BUFFERS, then maxBuffers will be SMALLEST_MAX_BUFFERS
private int maxBuffers = DEFAULT_MAX_BUFFERS;
// if currentBufferCount <= drainMark, will invoke drainHandler to resume readStream
private int drainMark = maxBuffers / 2;
public OutputStreamToWriteStream(Context context, OutputStream outputStream,
boolean autoCloseOutputStream) {
this.context = context;
this.outputStream = outputStream;
this.autoCloseOutputStream = autoCloseOutputStream;
}
@Override
public WriteStream exceptionHandler(Handler handler) {
this.exceptionHandler = handler;
return this;
}
private void handleException(Throwable t) {
if (exceptionHandler != null) {
exceptionHandler.handle(t);
}
}
@Override
public synchronized Future write(Buffer data) {
Promise result = Promise.promise();
write(data, ar -> {
if (ar.failed()) {
handleException(ar.cause());
}
result.complete();
});
return result.future();
}
@Override
public void write(Buffer data, Handler> handler) {
currentBufferCount++;
buffers.add(data);
context.executeBlocking(this::writeInWorker,
true,
handler);
}
protected void writeInWorker(Promise future) {
while (true) {
Buffer buffer = buffers.poll();
if (buffer == null) {
future.complete();
return;
}
try {
outputStream.write(buffer.getBytes());
synchronized (OutputStreamToWriteStream.this) {
currentBufferCount--;
Runnable action = (currentBufferCount == 0 && closedDeferred != null) ? closedDeferred : this::checkDrained;
action.run();
}
} catch (IOException e) {
currentBufferCount--;
future.fail(e);
return;
}
}
}
@Override
public void end(Handler> handler) {
close();
}
@Override
public WriteStream setWriteQueueMaxSize(int maxSize) {
this.maxBuffers = Math.max(maxSize, SMALLEST_MAX_BUFFERS);
this.drainMark = maxBuffers / 2;
return this;
}
@Override
public synchronized boolean writeQueueFull() {
return currentBufferCount >= maxBuffers;
}
@Override
public synchronized WriteStream drainHandler(Handler handler) {
this.drainHandler = handler;
return this;
}
private synchronized void checkDrained() {
if (drainHandler != null && currentBufferCount <= drainMark) {
Handler handler = drainHandler;
drainHandler = null;
handler.handle(null);
}
}
@Override
public CompletableFuture close() {
return closeInternal();
}
private void check() {
checkClosed();
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException(this.getClass().getName() + " is closed");
}
}
private synchronized CompletableFuture closeInternal() {
check();
closed = true;
CompletableFuture future = new CompletableFuture<>();
if (currentBufferCount == 0) {
doClose(future);
} else {
closedDeferred = () -> doClose(future);
}
return future;
}
private void doClose(CompletableFuture future) {
if (autoCloseOutputStream) {
try {
outputStream.close();
} catch (IOException e) {
future.completeExceptionally(new IllegalStateException("failed to close outputStream.", e));
return;
}
}
future.complete(null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy