InputStreamSubscriber/Tests conform to style
See gh-31677
This commit is contained in:
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed 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
|
||||
*
|
||||
* https://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.springframework.http.client;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -29,48 +45,65 @@ import org.springframework.util.Assert;
|
||||
* {@link org.springframework.core.io.buffer.InputStreamSubscriber}.
|
||||
*
|
||||
* @author Oleh Dokuka
|
||||
* @since 6.1
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.2
|
||||
* @param <T> the publisher byte buffer type
|
||||
*/
|
||||
final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscriber<T> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(InputStreamSubscriber.class);
|
||||
|
||||
static final Object READY = new Object();
|
||||
static final byte[] DONE = new byte[0];
|
||||
static final byte[] CLOSED = new byte[0];
|
||||
private static final Object READY = new Object();
|
||||
|
||||
final int prefetch;
|
||||
final int limit;
|
||||
final ReentrantLock lock;
|
||||
final Queue<T> queue;
|
||||
final Function<T, byte[]> mapper;
|
||||
final Consumer<T> onDiscardHandler;
|
||||
private static final byte[] DONE = new byte[0];
|
||||
|
||||
final AtomicReference<Object> parkedThread = new AtomicReference<>();
|
||||
final AtomicInteger workAmount = new AtomicInteger();
|
||||
private static final byte[] CLOSED = new byte[0];
|
||||
|
||||
|
||||
private final Function<T, byte[]> mapper;
|
||||
|
||||
private final Consumer<T> onDiscardHandler;
|
||||
|
||||
private final int prefetch;
|
||||
|
||||
private final int limit;
|
||||
|
||||
private final ReentrantLock lock;
|
||||
|
||||
private final Queue<T> queue;
|
||||
|
||||
private final AtomicReference<Object> parkedThread = new AtomicReference<>();
|
||||
|
||||
private final AtomicInteger workAmount = new AtomicInteger();
|
||||
|
||||
volatile boolean closed;
|
||||
int consumed;
|
||||
|
||||
private int consumed;
|
||||
|
||||
@Nullable
|
||||
byte[] available;
|
||||
int position;
|
||||
private byte[] available;
|
||||
|
||||
private int position;
|
||||
|
||||
@Nullable
|
||||
Flow.Subscription s;
|
||||
boolean done;
|
||||
private Flow.Subscription subscription;
|
||||
|
||||
private boolean done;
|
||||
|
||||
@Nullable
|
||||
Throwable error;
|
||||
private Throwable error;
|
||||
|
||||
|
||||
private InputStreamSubscriber(Function<T, byte[]> mapper, Consumer<T> onDiscardHandler, int prefetch) {
|
||||
this.prefetch = prefetch;
|
||||
this.limit = prefetch == Integer.MAX_VALUE ? Integer.MAX_VALUE : prefetch - (prefetch >> 2);
|
||||
this.mapper = mapper;
|
||||
this.onDiscardHandler = onDiscardHandler;
|
||||
this.prefetch = prefetch;
|
||||
this.limit = (prefetch == Integer.MAX_VALUE ? Integer.MAX_VALUE : prefetch - (prefetch >> 2));
|
||||
this.queue = new ArrayBlockingQueue<>(prefetch);
|
||||
this.lock = new ReentrantLock(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subscribes to given {@link Flow.Publisher} and returns subscription
|
||||
* as {@link InputStream} that allows reading all propagated {@link DataBuffer} messages via its imperative API.
|
||||
@@ -85,8 +118,7 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
* any of the {@link InputStream#read} methods
|
||||
* <p>
|
||||
* Note: {@link Flow.Subscription#request(long)} happens eagerly for the first time upon subscription
|
||||
* and then repeats every time {@code bufferSize - (bufferSize >> 2)} consumed
|
||||
*
|
||||
* and then repeats every time {@code bufferSize - (bufferSize >> 2)} consumed.
|
||||
* @param publisher the source of {@link DataBuffer} which should be represented as an {@link InputStream}
|
||||
* @param mapper function to transform <T> element to {@code byte[]}. Note, <T> should be released during the mapping if needed.
|
||||
* @param onDiscardHandler <T> element consumer if returned {@link InputStream} is closed prematurely.
|
||||
@@ -107,33 +139,33 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Flow.Subscription subscription) {
|
||||
if (this.s != null) {
|
||||
if (this.subscription != null) {
|
||||
subscription.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
this.s = subscription;
|
||||
subscription.request(prefetch == Integer.MAX_VALUE ? Long.MAX_VALUE : prefetch);
|
||||
this.subscription = subscription;
|
||||
subscription.request(this.prefetch == Integer.MAX_VALUE ? Long.MAX_VALUE : this.prefetch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T t) {
|
||||
Assert.notNull(t, "T value must not be null");
|
||||
public void onNext(T buffer) {
|
||||
Assert.notNull(buffer, "Buffer must not be null");
|
||||
|
||||
if (this.done) {
|
||||
discard(t);
|
||||
discard(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!queue.offer(t)) {
|
||||
discard(t);
|
||||
error = new RuntimeException("Buffer overflow");
|
||||
done = true;
|
||||
if (!this.queue.offer(buffer)) {
|
||||
discard(buffer);
|
||||
this.error = new RuntimeException("Buffer overflow");
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
int previousWorkState = addWork();
|
||||
if (previousWorkState == Integer.MIN_VALUE) {
|
||||
T value = queue.poll();
|
||||
T value = this.queue.poll();
|
||||
if (value != null) {
|
||||
discard(value);
|
||||
}
|
||||
@@ -179,51 +211,62 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
int nextProduced = produced == Integer.MAX_VALUE ? 1 : produced + 1;
|
||||
int nextProduced = (produced == Integer.MAX_VALUE ? 1 : produced + 1);
|
||||
|
||||
|
||||
if (workAmount.weakCompareAndSetRelease(produced, nextProduced)) {
|
||||
if (this.workAmount.weakCompareAndSetRelease(produced, nextProduced)) {
|
||||
return produced;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resume() {
|
||||
if (this.parkedThread != READY) {
|
||||
Object old = this.parkedThread.getAndSet(READY);
|
||||
if (old != READY) {
|
||||
LockSupport.unpark((Thread)old);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* InputStream implementation */
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (!lock.tryLock()) {
|
||||
if (!this.lock.tryLock()) {
|
||||
if (this.closed) {
|
||||
return -1;
|
||||
}
|
||||
throw new ConcurrentModificationException("concurrent access is disallowed");
|
||||
throw new ConcurrentModificationException("Concurrent access is not allowed");
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] bytes = getBytesOrAwait();
|
||||
byte[] next = getNextOrAwait();
|
||||
|
||||
if (bytes == DONE) {
|
||||
if (next == DONE) {
|
||||
this.closed = true;
|
||||
cleanAndFinalize();
|
||||
if (this.error == null) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
throw Exceptions.propagate(error);
|
||||
throw Exceptions.propagate(this.error);
|
||||
}
|
||||
} else if (bytes == CLOSED) {
|
||||
}
|
||||
else if (next == CLOSED) {
|
||||
cleanAndFinalize();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes[this.position++] & 0xFF;
|
||||
return next[this.position++] & 0xFF;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
catch (Throwable ex) {
|
||||
this.closed = true;
|
||||
requiredSubscriber().cancel();
|
||||
cleanAndFinalize();
|
||||
throw Exceptions.propagate(t);
|
||||
throw Exceptions.propagate(ex);
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +277,7 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!lock.tryLock()) {
|
||||
if (!this.lock.tryLock()) {
|
||||
if (this.closed) {
|
||||
return -1;
|
||||
}
|
||||
@@ -243,9 +286,9 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
|
||||
try {
|
||||
for (int j = 0; j < len;) {
|
||||
byte[] bytes = getBytesOrAwait();
|
||||
byte[] next = getNextOrAwait();
|
||||
|
||||
if (bytes == DONE) {
|
||||
if (next == DONE) {
|
||||
cleanAndFinalize();
|
||||
if (this.error == null) {
|
||||
this.closed = true;
|
||||
@@ -254,37 +297,38 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
else {
|
||||
if (j == 0) {
|
||||
this.closed = true;
|
||||
throw Exceptions.propagate(error);
|
||||
throw Exceptions.propagate(this.error);
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
} else if (bytes == CLOSED) {
|
||||
}
|
||||
else if (next == CLOSED) {
|
||||
requiredSubscriber().cancel();
|
||||
cleanAndFinalize();
|
||||
return -1;
|
||||
}
|
||||
int i = this.position;
|
||||
for (; i < bytes.length && j < len; i++, j++) {
|
||||
b[off + j] = bytes[i];
|
||||
for (; i < next.length && j < len; i++, j++) {
|
||||
b[off + j] = next[i];
|
||||
}
|
||||
this.position = i;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
catch (Throwable ex) {
|
||||
this.closed = true;
|
||||
requiredSubscriber().cancel();
|
||||
cleanAndFinalize();
|
||||
throw Exceptions.propagate(t);
|
||||
throw Exceptions.propagate(ex);
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
this.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] getBytesOrAwait() {
|
||||
byte[] getNextOrAwait() {
|
||||
if (this.available == null || this.available.length - this.position == 0) {
|
||||
this.available = null;
|
||||
|
||||
@@ -294,12 +338,12 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
return CLOSED;
|
||||
}
|
||||
|
||||
boolean d = this.done;
|
||||
T t = this.queue.poll();
|
||||
if (t != null) {
|
||||
boolean done = this.done;
|
||||
T buffer = this.queue.poll();
|
||||
if (buffer != null) {
|
||||
int consumed = ++this.consumed;
|
||||
this.position = 0;
|
||||
this.available = Objects.requireNonNull(this.mapper.apply(t));
|
||||
this.available = Objects.requireNonNull(this.mapper.apply(buffer));
|
||||
if (consumed == this.limit) {
|
||||
this.consumed = 0;
|
||||
requiredSubscriber().request(this.limit);
|
||||
@@ -307,11 +351,11 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
break;
|
||||
}
|
||||
|
||||
if (d) {
|
||||
if (done) {
|
||||
return DONE;
|
||||
}
|
||||
|
||||
actualWorkAmount = workAmount.addAndGet(-actualWorkAmount);
|
||||
actualWorkAmount = this.workAmount.addAndGet(-actualWorkAmount);
|
||||
if (actualWorkAmount == 0) {
|
||||
await();
|
||||
}
|
||||
@@ -327,8 +371,7 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
for (;;) {
|
||||
int workAmount = this.workAmount.getPlain();
|
||||
T value;
|
||||
|
||||
while((value = queue.poll()) != null) {
|
||||
while ((value = this.queue.poll()) != null) {
|
||||
discard(value);
|
||||
}
|
||||
|
||||
@@ -338,16 +381,6 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
}
|
||||
}
|
||||
|
||||
void discard(T value) {
|
||||
try {
|
||||
this.onDiscardHandler.accept(value);
|
||||
} catch (Throwable t) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to release " + value.getClass().getSimpleName() + ": " + value, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.closed) {
|
||||
@@ -373,8 +406,19 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
}
|
||||
|
||||
private Flow.Subscription requiredSubscriber() {
|
||||
Assert.state(this.s != null, "Subscriber must be subscribed to use InputStream");
|
||||
return this.s;
|
||||
Assert.state(this.subscription != null, "Subscriber must be subscribed to use InputStream");
|
||||
return this.subscription;
|
||||
}
|
||||
|
||||
void discard(T buffer) {
|
||||
try {
|
||||
this.onDiscardHandler.accept(buffer);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to release " + buffer.getClass().getSimpleName() + ": " + buffer, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void await() {
|
||||
@@ -390,7 +434,7 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
throw new IllegalStateException("Only one (Virtual)Thread can await!");
|
||||
}
|
||||
|
||||
if (parkedThread.compareAndSet( null, toUnpark)) {
|
||||
if (this.parkedThread.compareAndSet( null, toUnpark)) {
|
||||
LockSupport.park();
|
||||
// we don't just break here because park() can wake up spuriously
|
||||
// if we got a proper resume, get() == READY and the loop will quit above
|
||||
@@ -400,13 +444,4 @@ final class InputStreamSubscriber<T> extends InputStream implements Flow.Subscri
|
||||
this.parkedThread.lazySet(null);
|
||||
}
|
||||
|
||||
private void resume() {
|
||||
if (this.parkedThread != READY) {
|
||||
Object old = parkedThread.getAndSet(READY);
|
||||
if (old != READY) {
|
||||
LockSupport.unpark((Thread)old);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user