org.eclipse.jetty.io.MemoryEndPointPipe Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.io;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HexFormat;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Memory-based implementation of {@link EndPoint.Pipe}.
*/
public class MemoryEndPointPipe implements EndPoint.Pipe
{
private static final Logger LOG = LoggerFactory.getLogger(MemoryEndPointPipe.class);
private final LocalEndPoint localEndPoint;
private final RemoteEndPoint remoteEndPoint;
private final Consumer taskConsumer;
public MemoryEndPointPipe(Scheduler scheduler, Consumer consumer, SocketAddress socketAddress)
{
localEndPoint = new LocalEndPoint(scheduler, socketAddress);
remoteEndPoint = new RemoteEndPoint(scheduler, new MemorySocketAddress());
localEndPoint.setPeerEndPoint(remoteEndPoint);
remoteEndPoint.setPeerEndPoint(localEndPoint);
taskConsumer = consumer;
}
@Override
public EndPoint getLocalEndPoint()
{
return localEndPoint;
}
@Override
public EndPoint getRemoteEndPoint()
{
return remoteEndPoint;
}
private class MemoryEndPoint extends AbstractEndPoint
{
private static final ByteBuffer EOF = ByteBuffer.allocate(0);
private final AutoLock lock = new AutoLock();
private final Deque byteBuffers = new ArrayDeque<>();
private final SocketAddress localAddress;
private MemoryEndPoint peerEndPoint;
private Invocable.Task fillableTask;
private Invocable.Task completeWriteTask;
private long maxCapacity;
private long capacity;
private MemoryEndPoint(Scheduler scheduler, SocketAddress localAddress)
{
super(scheduler);
this.localAddress = localAddress;
}
void setPeerEndPoint(MemoryEndPoint peerEndPoint)
{
this.peerEndPoint = peerEndPoint;
this.fillableTask = new FillableTask(peerEndPoint.getFillInterest());
this.completeWriteTask = new CompleteWriteTask(peerEndPoint.getWriteFlusher());
}
public long getMaxCapacity()
{
return maxCapacity;
}
public void setMaxCapacity(long maxCapacity)
{
this.maxCapacity = maxCapacity;
}
@Override
public Object getTransport()
{
return null;
}
@Override
public SocketAddress getLocalSocketAddress()
{
return localAddress;
}
@Override
public SocketAddress getRemoteSocketAddress()
{
return peerEndPoint.getLocalSocketAddress();
}
@Override
protected void onIncompleteFlush()
{
}
@Override
protected void needsFillInterest()
{
}
@Override
public int fill(ByteBuffer buffer) throws IOException
{
if (!isOpen())
throw new IOException("closed");
if (isInputShutdown())
return -1;
int filled;
ByteBuffer data;
try (AutoLock ignored = peerEndPoint.lock.lock())
{
Queue byteBuffers = peerEndPoint.byteBuffers;
data = byteBuffers.peek();
if (data == null)
{
filled = 0;
}
else if (data == EOF)
{
filled = -1;
}
else
{
int length = data.remaining();
int space = BufferUtil.space(buffer);
if (length <= space)
byteBuffers.poll();
filled = Math.min(length, space);
peerEndPoint.capacity -= filled;
}
}
if (LOG.isDebugEnabled())
LOG.debug("filled {} from {}", filled, this);
if (data == null)
return 0;
if (data == EOF)
{
shutdownInput();
return -1;
}
int copied = BufferUtil.append(buffer, data);
assert copied == filled;
if (filled > 0)
{
notIdle();
onFilled();
}
return filled;
}
private void onFilled()
{
taskConsumer.accept(completeWriteTask);
}
@Override
public boolean flush(ByteBuffer... buffers) throws IOException
{
if (!isOpen())
throw new IOException("closed");
if (isOutputShutdown())
throw new IOException("shutdown");
long flushed = 0;
boolean result = true;
try (AutoLock ignored = lock.lock())
{
for (ByteBuffer buffer : buffers)
{
int remaining = buffer.remaining();
if (remaining == 0)
continue;
long newCapacity = capacity + remaining;
long maxCapacity = getMaxCapacity();
if (maxCapacity > 0 && newCapacity > maxCapacity)
{
result = false;
break;
}
byteBuffers.offer(BufferUtil.copy(buffer));
buffer.position(buffer.limit());
capacity = newCapacity;
flushed += remaining;
}
}
if (LOG.isDebugEnabled())
LOG.debug("flushed {} to {}", flushed, this);
if (flushed > 0)
{
notIdle();
onFlushed();
}
return result;
}
@Override
protected void doShutdownOutput()
{
super.doShutdownOutput();
try (AutoLock ignored = lock.lock())
{
byteBuffers.offer(EOF);
}
onFlushed();
}
@Override
protected void doClose()
{
super.doClose();
try (AutoLock ignored = lock.lock())
{
ByteBuffer last = byteBuffers.peekLast();
if (last != EOF)
byteBuffers.offer(EOF);
}
onFlushed();
}
private void onFlushed()
{
taskConsumer.accept(fillableTask);
}
}
private class LocalEndPoint extends MemoryEndPoint
{
private LocalEndPoint(Scheduler scheduler, SocketAddress socketAddress)
{
super(scheduler, socketAddress);
}
}
private class RemoteEndPoint extends MemoryEndPoint
{
private RemoteEndPoint(Scheduler scheduler, SocketAddress socketAddress)
{
super(scheduler, socketAddress);
}
}
private record FillableTask(FillInterest fillInterest) implements Invocable.Task
{
@Override
public void run()
{
fillInterest.fillable();
}
@Override
public InvocationType getInvocationType()
{
return fillInterest.getCallbackInvocationType();
}
}
private record CompleteWriteTask(WriteFlusher writeFlusher) implements Invocable.Task
{
@Override
public void run()
{
writeFlusher.completeWrite();
}
@Override
public InvocationType getInvocationType()
{
return writeFlusher.getCallbackInvocationType();
}
}
private static class MemorySocketAddress extends SocketAddress
{
private static final AtomicLong ID = new AtomicLong();
private final long id = ID.incrementAndGet();
private final String address = "[memory:/%s]".formatted(HexFormat.of().formatHex(ByteBuffer.allocate(8).putLong(id).array()));
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj instanceof MemorySocketAddress that)
return id == that.id;
return false;
}
@Override
public int hashCode()
{
return Objects.hash(id);
}
@Override
public String toString()
{
return address;
}
}
}