Cleanup non-retainable `Retainable`s (#9159)
`Retainable`s that return false from `canRetain()` now are noops if `retain()` is called, which allows for a simpler calling convention. `AsyncContent` has also been reworked to allocate less and be clearer in its use of `canRetain()`.
This commit is contained in:
parent
46355c6110
commit
8b3bcc3a50
|
@ -112,8 +112,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Queueing chunk {}", chunk);
|
LOG.debug("Queueing chunk {}", chunk);
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
chunkCallbacks.add(new ChunkCallback(chunk, demander, response::abort));
|
chunkCallbacks.add(new ChunkCallback(chunk, demander, response::abort));
|
||||||
l.signalAll();
|
l.signalAll();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -574,7 +574,7 @@ public abstract class HttpReceiver
|
||||||
return _chunk;
|
return _chunk;
|
||||||
|
|
||||||
// Retain the input chunk because its ByteBuffer will be referenced by the Inflater.
|
// Retain the input chunk because its ByteBuffer will be referenced by the Inflater.
|
||||||
if (retain && _chunk.canRetain())
|
if (retain)
|
||||||
_chunk.retain();
|
_chunk.retain();
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("decoding: {}", _chunk);
|
LOG.debug("decoding: {}", _chunk);
|
||||||
|
|
|
@ -412,8 +412,7 @@ public class ResponseNotifier
|
||||||
if (chunk.hasRemaining())
|
if (chunk.hasRemaining())
|
||||||
chunk = Content.Chunk.asChunk(chunk.getByteBuffer().slice(), chunk.isLast(), chunk);
|
chunk = Content.Chunk.asChunk(chunk.getByteBuffer().slice(), chunk.isLast(), chunk);
|
||||||
// Retain the slice because it is stored for later reads.
|
// Retain the slice because it is stored for later reads.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
}
|
}
|
||||||
else if (!currentChunk.isLast())
|
else if (!currentChunk.isLast())
|
||||||
|
|
|
@ -99,8 +99,7 @@ public class HttpReceiverOverFCGI extends HttpReceiver
|
||||||
if (this.chunk != null)
|
if (this.chunk != null)
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
// Retain the chunk because it is stored for later reads.
|
// Retain the chunk because it is stored for later reads.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
responseContentAvailable();
|
responseContentAvailable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,8 +192,7 @@ public class HttpStreamOverFCGI implements HttpStream
|
||||||
public void onContent(Content.Chunk chunk)
|
public void onContent(Content.Chunk chunk)
|
||||||
{
|
{
|
||||||
// Retain the chunk because it is stored for later reads.
|
// Retain the chunk because it is stored for later reads.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
_chunk = chunk;
|
_chunk = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -284,8 +284,7 @@ public class MultiPartByteRanges extends CompletableFuture<MultiPartByteRanges.P
|
||||||
public void onPartContent(Content.Chunk chunk)
|
public void onPartContent(Content.Chunk chunk)
|
||||||
{
|
{
|
||||||
// Retain the chunk because it is stored for later use.
|
// Retain the chunk because it is stored for later use.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
partChunks.add(chunk);
|
partChunks.add(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -444,8 +444,7 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Retain the chunk because it is stored for later use.
|
// Retain the chunk because it is stored for later use.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
partChunks.add(chunk);
|
partChunks.add(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,22 +43,4 @@ public class Trailers implements Content.Chunk
|
||||||
{
|
{
|
||||||
return trailers;
|
return trailers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canRetain()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void retain()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean release()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -690,8 +690,7 @@ public class MultiPartTest
|
||||||
public void onPartContent(Content.Chunk chunk)
|
public void onPartContent(Content.Chunk chunk)
|
||||||
{
|
{
|
||||||
// Retain the chunk because it is stored for later use.
|
// Retain the chunk because it is stored for later use.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
partContent.add(chunk);
|
partContent.add(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -432,24 +432,6 @@ public interface Stream
|
||||||
{
|
{
|
||||||
super(new DataFrame(streamId, BufferUtil.EMPTY_BUFFER, true));
|
super(new DataFrame(streamId, BufferUtil.EMPTY_BUFFER, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canRetain()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void retain()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean release()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -411,24 +411,6 @@ public interface Stream
|
||||||
{
|
{
|
||||||
super(new DataFrame(BufferUtil.EMPTY_BUFFER, true));
|
super(new DataFrame(BufferUtil.EMPTY_BUFFER, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canRetain()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void retain()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean release()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -335,8 +335,7 @@ public abstract class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, A
|
||||||
public void onData(Data data)
|
public void onData(Data data)
|
||||||
{
|
{
|
||||||
// Retain the data because it is stored for later reads.
|
// Retain the data because it is stored for later reads.
|
||||||
if (data.canRetain())
|
data.retain();
|
||||||
data.retain();
|
|
||||||
if (!dataRef.compareAndSet(null, data))
|
if (!dataRef.compareAndSet(null, data))
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
||||||
|
|
|
@ -436,11 +436,50 @@ public class Content
|
||||||
/**
|
/**
|
||||||
* <p>An empty, non-last, chunk.</p>
|
* <p>An empty, non-last, chunk.</p>
|
||||||
*/
|
*/
|
||||||
Chunk EMPTY = ByteBufferChunk.EMPTY;
|
Chunk EMPTY = new Chunk()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getByteBuffer()
|
||||||
|
{
|
||||||
|
return BufferUtil.EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLast()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "EMPTY";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>An empty, last, chunk.</p>
|
* <p>An empty, last, chunk.</p>
|
||||||
*/
|
*/
|
||||||
Content.Chunk EOF = ByteBufferChunk.EOF;
|
Content.Chunk EOF = new Chunk()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getByteBuffer()
|
||||||
|
{
|
||||||
|
return BufferUtil.EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLast()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "EOF";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Creates a Chunk with the given ByteBuffer.</p>
|
* <p>Creates a Chunk with the given ByteBuffer.</p>
|
||||||
|
@ -657,24 +696,6 @@ public class Content
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canRetain()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void retain()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean release()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,30 +27,36 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
public interface Retainable
|
public interface Retainable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* <p>Returns whether this resource can be retained, that is whether {@link #retain()}
|
* <p>Returns whether this resource is referenced counted by calls to {@link #retain()}
|
||||||
* can be called safely.</p>
|
* and {@link #release()}.</p>
|
||||||
* <p>Implementations may decide that special resources are not retainable (for example,
|
* <p>Implementations may decide that special resources are not not referenced counted (for example,
|
||||||
* {@code static} constants) so calling {@link #retain()} is not safe because it may throw.</p>
|
* {@code static} constants) so calling {@link #retain()} is a no-operation, and
|
||||||
* <p>Calling {@link #release()} on those special resources is typically allowed, and
|
* calling {@link #release()} on those special resources is a no-operation that always returns true.</p>
|
||||||
* it is a no-operation.</p>
|
|
||||||
*
|
*
|
||||||
* @return whether it is safe to call {@link #retain()}
|
* @return true if calls to {@link #retain()} are reference counted.
|
||||||
*/
|
*/
|
||||||
boolean canRetain();
|
default boolean canRetain()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Retains this resource, incrementing the reference count.</p>
|
* <p>Retains this resource, potentially incrementing a reference count if there are resources that will be released.</p>
|
||||||
*/
|
*/
|
||||||
void retain();
|
default void retain()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Releases this resource, decrementing the reference count.</p>
|
* <p>Releases this resource, potentially decrementing a reference count (if any).</p>
|
||||||
* <p>This method returns {@code true} when the reference count goes to zero,
|
|
||||||
* {@code false} otherwise.</p>
|
|
||||||
*
|
*
|
||||||
* @return whether the invocation of this method decremented the reference count to zero
|
* @return {@code true} when the reference count goes to zero or if there was no reference count,
|
||||||
|
* {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
boolean release();
|
default boolean release()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper of {@link Retainable} instances.
|
* A wrapper of {@link Retainable} instances.
|
||||||
|
|
|
@ -23,6 +23,9 @@ import java.util.Objects;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
|
import org.eclipse.jetty.io.Retainable;
|
||||||
|
import org.eclipse.jetty.io.internal.ByteBufferChunk;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.thread.AutoLock;
|
import org.eclipse.jetty.util.thread.AutoLock;
|
||||||
import org.eclipse.jetty.util.thread.SerializedInvoker;
|
import org.eclipse.jetty.util.thread.SerializedInvoker;
|
||||||
|
@ -37,10 +40,18 @@ import org.eclipse.jetty.util.thread.SerializedInvoker;
|
||||||
public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
{
|
{
|
||||||
private static final int UNDETERMINED_LENGTH = -2;
|
private static final int UNDETERMINED_LENGTH = -2;
|
||||||
|
private static final AsyncChunk ASYNC_EOF = new AsyncChunk(true, BufferUtil.EMPTY_BUFFER, Callback.NOOP)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "ASYNC_EOF";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final AutoLock.WithCondition lock = new AutoLock.WithCondition();
|
private final AutoLock.WithCondition lock = new AutoLock.WithCondition();
|
||||||
private final SerializedInvoker invoker = new SerializedInvoker();
|
private final SerializedInvoker invoker = new SerializedInvoker();
|
||||||
private final Queue<ChunkCallback> chunks = new ArrayDeque<>();
|
private final Queue<AsyncChunk> chunks = new ArrayDeque<>();
|
||||||
private Content.Chunk.Error errorChunk;
|
private Content.Chunk.Error errorChunk;
|
||||||
private boolean readClosed;
|
private boolean readClosed;
|
||||||
private boolean writeClosed;
|
private boolean writeClosed;
|
||||||
|
@ -52,20 +63,15 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
* <p>The write completes:</p>
|
* <p>The write completes:</p>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>immediately with a failure when this instance is closed or already in error</li>
|
* <li>immediately with a failure when this instance is closed or already in error</li>
|
||||||
* <li>successfully when the {@link Content.Chunk} returned by {@link #read()} is released</li>
|
* <li>successfully when a non empty {@link Content.Chunk} returned by {@link #read()} is released</li>
|
||||||
* <li>successfully just before the {@link Content.Chunk} is returned by {@link #read()},
|
* <li>successfully just before the {@link Content.Chunk} is returned by {@link #read()},
|
||||||
* if the chunk {@link Content.Chunk#canRetain() cannot be retained}</li>
|
* for any empty chunk {@link Content.Chunk}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
|
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
|
||||||
{
|
{
|
||||||
Content.Chunk chunk;
|
offer(new AsyncChunk(last, byteBuffer, callback));
|
||||||
if (byteBuffer.hasRemaining())
|
|
||||||
chunk = Content.Chunk.from(byteBuffer, last, callback::succeeded);
|
|
||||||
else
|
|
||||||
chunk = last ? Content.Chunk.EOF : Content.Chunk.EMPTY;
|
|
||||||
offer(chunk, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,14 +79,8 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
* or succeeded if and only if the chunk is terminal, as non-terminal
|
* or succeeded if and only if the chunk is terminal, as non-terminal
|
||||||
* chunks have to bind the succeeding of the callback to their release.
|
* chunks have to bind the succeeding of the callback to their release.
|
||||||
*/
|
*/
|
||||||
private void offer(Content.Chunk chunk, Callback callback)
|
private void offer(AsyncChunk chunk)
|
||||||
{
|
{
|
||||||
if (chunk instanceof Content.Chunk.Error)
|
|
||||||
{
|
|
||||||
callback.failed(new IllegalArgumentException("Cannot not write Chunk.Error instances, call fail(Throwable) instead"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Throwable failure = null;
|
Throwable failure = null;
|
||||||
boolean wasEmpty = false;
|
boolean wasEmpty = false;
|
||||||
try (AutoLock ignored = lock.lock())
|
try (AutoLock ignored = lock.lock())
|
||||||
|
@ -98,17 +98,21 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
wasEmpty = chunks.isEmpty();
|
wasEmpty = chunks.isEmpty();
|
||||||
// No need to retain the chunk, because it's created internally
|
// No need to retain the chunk, because it's created internally
|
||||||
// from a ByteBuffer and it will be released by the caller of read().
|
// from a ByteBuffer and it will be released by the caller of read().
|
||||||
chunks.offer(new ChunkCallback(chunk, callback));
|
chunks.offer(chunk);
|
||||||
if (chunk.isLast())
|
if (chunk.isLast())
|
||||||
{
|
{
|
||||||
writeClosed = true;
|
writeClosed = true;
|
||||||
if (length == UNDETERMINED_LENGTH)
|
if (length == UNDETERMINED_LENGTH)
|
||||||
length = chunks.stream().mapToLong(cc -> cc.chunk().remaining()).sum();
|
{
|
||||||
|
length = 0;
|
||||||
|
for (AsyncChunk c : chunks)
|
||||||
|
length += c.remaining();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failure != null)
|
if (failure != null)
|
||||||
callback.failed(failure);
|
chunk.failed(failure);
|
||||||
if (wasEmpty)
|
if (wasEmpty)
|
||||||
invoker.run(this::invokeDemandCallback);
|
invoker.run(this::invokeDemandCallback);
|
||||||
}
|
}
|
||||||
|
@ -128,7 +132,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
// Special case for a last empty chunk that may not be read.
|
// Special case for a last empty chunk that may not be read.
|
||||||
if (writeClosed && chunks.size() == 1)
|
if (writeClosed && chunks.size() == 1)
|
||||||
{
|
{
|
||||||
Content.Chunk chunk = chunks.peek().chunk();
|
AsyncChunk chunk = chunks.peek();
|
||||||
if (chunk.isLast() && !chunk.hasRemaining())
|
if (chunk.isLast() && !chunk.hasRemaining())
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -144,7 +148,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
offer(Content.Chunk.EOF, Callback.NOOP);
|
offer(ASYNC_EOF);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isClosed()
|
public boolean isClosed()
|
||||||
|
@ -167,7 +171,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
@Override
|
@Override
|
||||||
public Content.Chunk read()
|
public Content.Chunk read()
|
||||||
{
|
{
|
||||||
ChunkCallback current;
|
AsyncChunk current;
|
||||||
try (AutoLock.WithCondition condition = lock.lock())
|
try (AutoLock.WithCondition condition = lock.lock())
|
||||||
{
|
{
|
||||||
if (length == UNDETERMINED_LENGTH)
|
if (length == UNDETERMINED_LENGTH)
|
||||||
|
@ -181,13 +185,18 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
return errorChunk;
|
return errorChunk;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
readClosed = current.chunk().isLast();
|
readClosed = current.isLast();
|
||||||
if (chunks.isEmpty())
|
if (chunks.isEmpty())
|
||||||
condition.signal();
|
condition.signal();
|
||||||
}
|
}
|
||||||
if (!current.chunk().canRetain())
|
|
||||||
current.callback().succeeded();
|
// If the chunk is reference counted, the callback is succeeded when it is released.
|
||||||
return current.chunk();
|
if (current.canRetain())
|
||||||
|
return current;
|
||||||
|
|
||||||
|
// If the chunk is not reference counted, we can succeed it now and return a chunk with a noop release.
|
||||||
|
current.succeeded();
|
||||||
|
return current.isLast() ? Content.Chunk.EOF : Content.Chunk.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -232,7 +241,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
@Override
|
@Override
|
||||||
public void fail(Throwable failure)
|
public void fail(Throwable failure)
|
||||||
{
|
{
|
||||||
List<ChunkCallback> drained;
|
List<AsyncChunk> drained;
|
||||||
try (AutoLock.WithCondition condition = lock.lock())
|
try (AutoLock.WithCondition condition = lock.lock())
|
||||||
{
|
{
|
||||||
if (readClosed)
|
if (readClosed)
|
||||||
|
@ -244,7 +253,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
chunks.clear();
|
chunks.clear();
|
||||||
condition.signal();
|
condition.signal();
|
||||||
}
|
}
|
||||||
drained.forEach(cc -> cc.callback().failed(failure));
|
drained.forEach(ac -> ac.failed(failure));
|
||||||
invoker.run(this::invokeDemandCallback);
|
invoker.run(this::invokeDemandCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +265,52 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ChunkCallback(Content.Chunk chunk, Callback callback)
|
private static class AsyncChunk extends ByteBufferChunk implements Callback
|
||||||
{
|
{
|
||||||
|
private final Callback callback;
|
||||||
|
private final Retainable.ReferenceCounter referenceCounter;
|
||||||
|
|
||||||
|
public AsyncChunk(boolean last, ByteBuffer byteBuffer, Callback callback)
|
||||||
|
{
|
||||||
|
super(byteBuffer.hasRemaining() ? byteBuffer : BufferUtil.EMPTY_BUFFER, last);
|
||||||
|
this.callback = callback;
|
||||||
|
referenceCounter = getByteBuffer() == BufferUtil.EMPTY_BUFFER ? null : new ReferenceCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canRetain()
|
||||||
|
{
|
||||||
|
return referenceCounter != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void retain()
|
||||||
|
{
|
||||||
|
if (canRetain())
|
||||||
|
referenceCounter.retain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release()
|
||||||
|
{
|
||||||
|
if (!canRetain())
|
||||||
|
return true;
|
||||||
|
boolean released = referenceCounter.release();
|
||||||
|
if (released)
|
||||||
|
succeeded();
|
||||||
|
return released;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
callback.failed(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,7 @@ public class ContentSinkSubscriber implements Flow.Subscriber<Content.Chunk>
|
||||||
public void onNext(Content.Chunk chunk)
|
public void onNext(Content.Chunk chunk)
|
||||||
{
|
{
|
||||||
// Retain the chunk because the write may not complete immediately.
|
// Retain the chunk because the write may not complete immediately.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
sink.write(chunk.isLast(), chunk.getByteBuffer(), Callback.from(() -> succeeded(chunk), x -> failed(chunk, x)));
|
sink.write(chunk.isLast(), chunk.getByteBuffer(), Callback.from(() -> succeeded(chunk), x -> failed(chunk, x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,23 +24,6 @@ import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
|
||||||
public abstract class ByteBufferChunk implements Content.Chunk
|
public abstract class ByteBufferChunk implements Content.Chunk
|
||||||
{
|
{
|
||||||
public static final ByteBufferChunk EMPTY = new ByteBufferChunk(BufferUtil.EMPTY_BUFFER, false)
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "%s[EMPTY]".formatted(ByteBufferChunk.class.getSimpleName());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public static final ByteBufferChunk EOF = new ByteBufferChunk(BufferUtil.EMPTY_BUFFER, true)
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "%s[EOF]".formatted(ByteBufferChunk.class.getSimpleName());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ByteBuffer byteBuffer;
|
private final ByteBuffer byteBuffer;
|
||||||
private final boolean last;
|
private final boolean last;
|
||||||
|
|
||||||
|
@ -62,24 +45,6 @@ public abstract class ByteBufferChunk implements Content.Chunk
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canRetain()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void retain()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean release()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -525,8 +525,7 @@ public class ContentSourceTest
|
||||||
private void add(Content.Chunk chunk)
|
private void add(Content.Chunk chunk)
|
||||||
{
|
{
|
||||||
// Retain the chunk because it is stored for later use.
|
// Retain the chunk because it is stored for later use.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
_chunks.add(chunk);
|
_chunks.add(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -688,8 +688,7 @@ public abstract class ProxyHandler extends Handler.Abstract
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("{} S2P received content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
LOG.debug("{} S2P received content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
||||||
|
|
||||||
if (serverToProxyChunk.canRetain())
|
serverToProxyChunk.retain();
|
||||||
serverToProxyChunk.retain();
|
|
||||||
Callback callback = new Callback()
|
Callback callback = new Callback()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -165,7 +165,7 @@ public class GzipRequest extends Request.Wrapper
|
||||||
return Content.Chunk.EOF;
|
return Content.Chunk.EOF;
|
||||||
|
|
||||||
// Retain the input chunk because its ByteBuffer will be referenced by the Inflater.
|
// Retain the input chunk because its ByteBuffer will be referenced by the Inflater.
|
||||||
if (retain && _chunk.canRetain())
|
if (retain)
|
||||||
_chunk.retain();
|
_chunk.retain();
|
||||||
ByteBuffer decodedBuffer = _decoder.decode(_chunk);
|
ByteBuffer decodedBuffer = _decoder.decode(_chunk);
|
||||||
|
|
||||||
|
|
|
@ -46,24 +46,6 @@ public class MockHttpStream implements HttpStream
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canRetain()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void retain()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean release()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
private final long _nanoTime = NanoTime.now();
|
private final long _nanoTime = NanoTime.now();
|
||||||
private final AtomicReference<Content.Chunk> _content = new AtomicReference<>();
|
private final AtomicReference<Content.Chunk> _content = new AtomicReference<>();
|
||||||
|
@ -106,8 +88,7 @@ public class MockHttpStream implements HttpStream
|
||||||
|
|
||||||
private Runnable addContent(Content.Chunk chunk)
|
private Runnable addContent(Content.Chunk chunk)
|
||||||
{
|
{
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
chunk = _content.getAndSet(chunk);
|
chunk = _content.getAndSet(chunk);
|
||||||
if (chunk == DEMAND)
|
if (chunk == DEMAND)
|
||||||
return _channel.onContentAvailable();
|
return _channel.onContentAvailable();
|
||||||
|
|
|
@ -154,8 +154,7 @@ public class HttpClientDemandTest extends AbstractTest
|
||||||
public void onContent(Response response, Content.Chunk chunk, Runnable demander)
|
public void onContent(Response response, Content.Chunk chunk, Runnable demander)
|
||||||
{
|
{
|
||||||
// Store the chunk and don't demand.
|
// Store the chunk and don't demand.
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
contentQueue.offer(chunk);
|
contentQueue.offer(chunk);
|
||||||
demanderQueue.offer(demander);
|
demanderQueue.offer(demander);
|
||||||
}
|
}
|
||||||
|
@ -244,8 +243,7 @@ public class HttpClientDemandTest extends AbstractTest
|
||||||
client.newRequest(newURI(transport))
|
client.newRequest(newURI(transport))
|
||||||
.onResponseContentAsync((response, chunk, demander) ->
|
.onResponseContentAsync((response, chunk, demander) ->
|
||||||
{
|
{
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
chunkRef.set(chunk);
|
chunkRef.set(chunk);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -439,8 +439,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
||||||
@Override
|
@Override
|
||||||
public void onContent(Response serverResponse, Content.Chunk chunk, Runnable demander)
|
public void onContent(Response serverResponse, Content.Chunk chunk, Runnable demander)
|
||||||
{
|
{
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
Callback callback = Callback.from(chunk::release, Callback.from(demander, serverResponse::abort));
|
Callback callback = Callback.from(chunk::release, Callback.from(demander, serverResponse::abort));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -216,8 +216,7 @@ public class ProxyServlet extends AbstractProxyServlet
|
||||||
content.get(buffer);
|
content.get(buffer);
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
Callback callback = Callback.from(chunk::release, Callback.from(demander, proxyResponse::abort));
|
Callback callback = Callback.from(chunk::release, Callback.from(demander, proxyResponse::abort));
|
||||||
onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
|
onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -439,8 +439,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
||||||
@Override
|
@Override
|
||||||
public void onContent(Response serverResponse, Content.Chunk chunk, Runnable demander)
|
public void onContent(Response serverResponse, Content.Chunk chunk, Runnable demander)
|
||||||
{
|
{
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
Callback callback = Callback.from(chunk::release, Callback.from(demander, serverResponse::abort));
|
Callback callback = Callback.from(chunk::release, Callback.from(demander, serverResponse::abort));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -216,8 +216,7 @@ public class ProxyServlet extends AbstractProxyServlet
|
||||||
content.get(buffer);
|
content.get(buffer);
|
||||||
offset = 0;
|
offset = 0;
|
||||||
}
|
}
|
||||||
if (chunk.canRetain())
|
chunk.retain();
|
||||||
chunk.retain();
|
|
||||||
Callback callback = Callback.from(chunk::release, Callback.from(demander, proxyResponse::abort));
|
Callback callback = Callback.from(chunk::release, Callback.from(demander, proxyResponse::abort));
|
||||||
onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
|
onResponseContent(request, response, proxyResponse, buffer, offset, length, callback);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue