DataBufferUtils does not release DataBuffer on error cases
This commit makes sure that in DataBufferUtils.write, any received data buffers are returned as part of the returned flux, even when an error occurs or is received. Issue: SPR-16782
This commit is contained in:
@@ -31,6 +31,7 @@ import java.nio.file.StandardOpenOption;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntPredicate;
|
||||
|
||||
@@ -336,6 +337,7 @@ public abstract class DataBufferUtils {
|
||||
sink.next(dataBuffer);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
sink.next(dataBuffer);
|
||||
sink.error(ex);
|
||||
}
|
||||
|
||||
@@ -355,6 +357,26 @@ public abstract class DataBufferUtils {
|
||||
* @param channel the channel to write to
|
||||
* @return a flux containing the same buffers as in {@code source}, that starts the writing
|
||||
* process when subscribed to, and that publishes any writing errors and the completion signal
|
||||
* @since 5.1
|
||||
*/
|
||||
public static Flux<DataBuffer> write(
|
||||
Publisher<DataBuffer> source, AsynchronousFileChannel channel) {
|
||||
return write(source, channel, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write the given stream of {@link DataBuffer DataBuffers} to the given {@code AsynchronousFileChannel}.
|
||||
* Does <strong>not</strong> close the channel when the flux is terminated, and does
|
||||
* <strong>not</strong> {@linkplain #release(DataBuffer) release} the data buffers in the
|
||||
* source. If releasing is required, then subscribe to the returned {@code Flux} with a
|
||||
* {@link #releaseConsumer()}.
|
||||
* <p>Note that the writing process does not start until the returned {@code Flux} is subscribed to.
|
||||
* @param source the stream of data buffers to be written
|
||||
* @param channel the channel to write to
|
||||
* @param position the file position at which the write is to begin; must be non-negative
|
||||
* @return a flux containing the same buffers as in {@code source}, that starts the writing
|
||||
* process when subscribed to, and that publishes any writing errors and the completion signal
|
||||
*/
|
||||
public static Flux<DataBuffer> write(
|
||||
Publisher<DataBuffer> source, AsynchronousFileChannel channel, long position) {
|
||||
@@ -610,10 +632,11 @@ public abstract class DataBufferUtils {
|
||||
|
||||
private final AtomicBoolean completed = new AtomicBoolean();
|
||||
|
||||
private final AtomicReference<Throwable> error = new AtomicReference<>();
|
||||
|
||||
private final AtomicLong position;
|
||||
|
||||
@Nullable
|
||||
private DataBuffer dataBuffer;
|
||||
private final AtomicReference<DataBuffer> dataBuffer = new AtomicReference<>();
|
||||
|
||||
public AsynchronousFileChannelWriteCompletionHandler(
|
||||
FluxSink<DataBuffer> sink, AsynchronousFileChannel channel, long position) {
|
||||
@@ -630,21 +653,27 @@ public abstract class DataBufferUtils {
|
||||
|
||||
@Override
|
||||
protected void hookOnNext(DataBuffer value) {
|
||||
this.dataBuffer = value;
|
||||
if (!this.dataBuffer.compareAndSet(null, value)) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
ByteBuffer byteBuffer = value.asByteBuffer();
|
||||
this.channel.write(byteBuffer, this.position.get(), byteBuffer, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hookOnError(Throwable throwable) {
|
||||
this.sink.error(throwable);
|
||||
this.error.set(throwable);
|
||||
|
||||
if (this.dataBuffer.get() == null) {
|
||||
this.sink.error(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hookOnComplete() {
|
||||
this.completed.set(true);
|
||||
|
||||
if (this.dataBuffer == null) {
|
||||
if (this.dataBuffer.get() == null) {
|
||||
this.sink.complete();
|
||||
}
|
||||
}
|
||||
@@ -656,11 +685,13 @@ public abstract class DataBufferUtils {
|
||||
this.channel.write(byteBuffer, pos, byteBuffer, this);
|
||||
return;
|
||||
}
|
||||
if (this.dataBuffer != null) {
|
||||
this.sink.next(this.dataBuffer);
|
||||
this.dataBuffer = null;
|
||||
sinkDataBuffer();
|
||||
|
||||
Throwable throwable = this.error.get();
|
||||
if (throwable != null) {
|
||||
this.sink.error(throwable);
|
||||
}
|
||||
if (this.completed.get()) {
|
||||
else if (this.completed.get()) {
|
||||
this.sink.complete();
|
||||
}
|
||||
else {
|
||||
@@ -670,8 +701,16 @@ public abstract class DataBufferUtils {
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, ByteBuffer byteBuffer) {
|
||||
sinkDataBuffer();
|
||||
this.sink.error(exc);
|
||||
}
|
||||
|
||||
private void sinkDataBuffer() {
|
||||
DataBuffer dataBuffer = this.dataBuffer.get();
|
||||
Assert.state(dataBuffer != null, "DataBuffer should not be null");
|
||||
this.sink.next(dataBuffer);
|
||||
this.dataBuffer.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user