All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.server.protocol.framed.AbstractFramedStreamSinkChannel Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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
 *
 *     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 io.undertow.server.protocol.framed;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

import io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.connector.PooledByteBuffer;
import io.undertow.util.ImmediatePooledByteBuffer;
import org.xnio.Buffers;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.IoUtils;
import org.xnio.Option;
import org.xnio.Options;
import org.xnio.XnioExecutor;
import org.xnio.XnioIoThread;
import org.xnio.XnioWorker;
import org.xnio.channels.Channels;
import org.xnio.channels.StreamSinkChannel;
import org.xnio.channels.StreamSourceChannel;

import static org.xnio.Bits.allAreClear;
import static org.xnio.Bits.anyAreSet;

/**
 * Framed Stream Sink Channel.
 * 

* Thread safety notes: *

* The general contract is that this channel is only to be used by a single thread at a time. The only exception to this is * during flush. A flush will only happen when {@link #readyForFlush} is set, and while this bit is set the buffer * must not be modified. * * @author Stuart Douglas */ public abstract class AbstractFramedStreamSinkChannel, R extends AbstractFramedStreamSourceChannel, S extends AbstractFramedStreamSinkChannel> implements StreamSinkChannel { /** * The maximum timeout to wait on awaitWritable in milliseconds when not specified. */ private static final int AWAIT_WRITABLE_TIMEOUT; /** * Extra timeout to make sure the flush has actually timed out */ private static final int FUZZ_FACTOR = 50; static { final int defaultAwaitWritableTimeout = 600000; int await_writable_timeout = AccessController.doPrivileged((PrivilegedAction) () -> Integer.getInteger("io.undertow.await_writable_timeout", defaultAwaitWritableTimeout)); AWAIT_WRITABLE_TIMEOUT = await_writable_timeout > 0? await_writable_timeout : defaultAwaitWritableTimeout; } private static final PooledByteBuffer EMPTY_BYTE_BUFFER = new ImmediatePooledByteBuffer(ByteBuffer.allocateDirect(0)); private final C channel; private final ChannelListener.SimpleSetter writeSetter = new ChannelListener.SimpleSetter<>(); private final ChannelListener.SimpleSetter closeSetter = new ChannelListener.SimpleSetter<>(); private final Object lock = new Object(); /** * handle to control the time we are waiting for flushing. */ private volatile XnioExecutor.Key handle = null; /** * Expiration time for flushing the frame. */ private volatile long flushExpirationTime = -1; /** * The timeout runnable to use for the handle. */ private final TimeoutRunnable timeoutRunnable; /** * the state variable, this must only be access by the thread that 'owns' the channel */ private volatile int state = 0; /** * If this channel is ready for flush, updated by multiple threads. In general it will be set by the thread * that 'owns' the channel, and cleared by the IO thread */ private volatile boolean readyForFlush; /** * If all the data has been written out and the channel has been fully flushed */ private volatile boolean fullyFlushed; /** * If the last frame has been queued. * * Note that this may not actually be the final frame in some circumstances, e.g. if the final frame * is two large to fit in the flow control window. In this case the flag may be cleared after flush is complete. */ private volatile boolean finalFrameQueued; /** * If this channel is broken, updated by multiple threads */ private volatile boolean broken; private volatile int waiterCount = 0; private volatile SendFrameHeader header; private volatile PooledByteBuffer writeBuffer; private volatile PooledByteBuffer body; private static final int STATE_CLOSED = 1; private static final int STATE_WRITES_SHUTDOWN = 1 << 1; private static final int STATE_FIRST_DATA_WRITTEN = 1 << 2; private static final int STATE_PRE_WRITE_CALLED = 1 << 3; private volatile boolean bufferFull; private volatile boolean writesResumed; @SuppressWarnings("unused") private volatile int inListenerLoop; /* keep track of successful writes to properly prevent a loop UNDERTOW-1624 */ private volatile boolean writeSucceeded; private static final AtomicIntegerFieldUpdater inListenerLoopUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractFramedStreamSinkChannel.class, "inListenerLoop"); protected AbstractFramedStreamSinkChannel(C channel) { this.channel = channel; this.timeoutRunnable = new TimeoutRunnable(this); } public long transferFrom(final FileChannel src, final long position, final long count) throws IOException { return src.transferTo(position, count, this); } public long transferFrom(final StreamSourceChannel source, final long count, final ByteBuffer throughBuffer) throws IOException { return IoUtils.transfer(source, count, throughBuffer, this); } @Override public void suspendWrites() { writesResumed = false; } /** * Returns the header for the current frame. * * This consists of the frame data, and also an integer specifying how much data is remaining in the buffer. * If this is non-zero then this method must adjust the buffers limit accordingly. * * It is expected that this will be used when limits on the size of a data frame prevent the whole buffer from * being sent at once. * * * @return The header for the current frame, or null */ final SendFrameHeader getFrameHeader() throws IOException { if (header == null) { header = createFrameHeader(); if (header == null) { header = new SendFrameHeader(0, null); } } return header; } protected SendFrameHeader createFrameHeader() throws IOException{ return null; } final void preWrite() { synchronized (lock) { if (allAreClear(state, STATE_PRE_WRITE_CALLED)) { state |= STATE_PRE_WRITE_CALLED; body = preWriteTransform(body); } } } protected PooledByteBuffer preWriteTransform(PooledByteBuffer body) { return body; } @Override public boolean isWriteResumed() { return writesResumed; } @Override public void wakeupWrites() { resumeWritesInternal(true); } @Override public void resumeWrites() { resumeWritesInternal(false); } protected void resumeWritesInternal(boolean wakeup) { boolean alreadyResumed = writesResumed; if(!wakeup && alreadyResumed) { return; } writesResumed = true; if(readyForFlush && !wakeup) { //we already have data queued to be flushed return; } if (inListenerLoopUpdater.compareAndSet(this, 0, 1)) { getChannel().runInIoThread(new Runnable() { // loopCount keeps track of runnable being invoked in a // loop without any successful write operation int loopCount = 0; @Override public void run() { try { ChannelListener listener = getWriteListener(); if (listener == null || !isWriteResumed()) { return; } if (writeSucceeded) { // reset write succeeded and loopCount writeSucceeded = false; loopCount = 0; } else if (loopCount++ == 100) { //should never happen UndertowLogger.ROOT_LOGGER.listenerNotProgressing(); IoUtils.safeClose(AbstractFramedStreamSinkChannel.this); return; } ChannelListeners.invokeChannelListener((S) AbstractFramedStreamSinkChannel.this, listener); } finally { inListenerLoopUpdater.set(AbstractFramedStreamSinkChannel.this, 0); } //if writes are shutdown or we become active then we stop looping //we stop when writes are shutdown because we can't flush until we are active //although we may be flushed as part of a batch if (writesResumed && allAreClear(state, STATE_CLOSED) && !broken && !readyForFlush && !fullyFlushed) { if (inListenerLoopUpdater.compareAndSet(AbstractFramedStreamSinkChannel.this, 0, 1)) { getIoThread().execute(this); } } } }); } } @Override public void shutdownWrites() throws IOException { // Queue prior to shutting down writes, since we might send the write buffer queueFinalFrame(); synchronized (lock) { if (anyAreSet(state, STATE_WRITES_SHUTDOWN) || broken) { return; } state |= STATE_WRITES_SHUTDOWN; } } private void queueFinalFrame() throws IOException { synchronized (lock) { if (!readyForFlush && !fullyFlushed && allAreClear(state, STATE_CLOSED) && !broken && !finalFrameQueued) { if (null == body && null != writeBuffer) { sendWriteBuffer(); } else if (null == body) { body = EMPTY_BYTE_BUFFER; } readyForFlush = true; state |= STATE_FIRST_DATA_WRITTEN; state |= STATE_WRITES_SHUTDOWN; // Mark writes as shutdown as well, since we want that set prior to queueing finalFrameQueued = true; } else return; } channel.queueFrame((S) this); } protected boolean isFinalFrameQueued() { return finalFrameQueued; } @Override public void awaitWritable() throws IOException { awaitWritable(getAwaitWritableTimeout(), TimeUnit.MILLISECONDS); } @Override public void awaitWritable(long l, TimeUnit timeUnit) throws IOException { if(Thread.currentThread() == getIoThread()) { throw UndertowMessages.MESSAGES.awaitCalledFromIoThread(); } synchronized (lock) { if (anyAreSet(state, STATE_CLOSED) || broken) { return; } if (readyForFlush) { try { waiterCount++; if(readyForFlush && !anyAreSet(state, STATE_CLOSED) && !broken) { lock.wait(timeUnit.toMillis(l)); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } finally { waiterCount--; } } } } @Override public XnioExecutor getWriteThread() { return channel.getIoThread(); } @Override public ChannelListener.Setter getWriteSetter() { return writeSetter; } @Override public ChannelListener.Setter getCloseSetter() { return closeSetter; } @Override public XnioWorker getWorker() { return channel.getWorker(); } @Override public XnioIoThread getIoThread() { return channel.getIoThread(); } @Override public boolean flush() throws IOException { if(anyAreSet(state, STATE_CLOSED)) { return true; } if (broken) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (readyForFlush) { return false; } synchronized (lock) { if (fullyFlushed) { state |= STATE_CLOSED; return true; } } if (anyAreSet(state, STATE_WRITES_SHUTDOWN) && !finalFrameQueued) { queueFinalFrame(); return false; } if(anyAreSet(state, STATE_WRITES_SHUTDOWN)) { return false; } if(isFlushRequiredOnEmptyBuffer() || (writeBuffer != null && writeBuffer.getBuffer().position() > 0)) { handleBufferFull(); return !readyForFlush; } return true; } protected boolean isFlushRequiredOnEmptyBuffer() { return false; } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { if(!safeToSend()) { return 0; } if(writeBuffer == null) { writeBuffer = getChannel().getBufferPool().allocate(); } ByteBuffer buffer = writeBuffer.getBuffer(); int copied = Buffers.copy(buffer, srcs, offset, length); if(!buffer.hasRemaining()) { handleBufferFull(); } writeSucceeded = writeSucceeded || copied > 0; return copied; } @Override public long write(ByteBuffer[] srcs) throws IOException { return write(srcs, 0, srcs.length); } @Override public int write(ByteBuffer src) throws IOException { if(!safeToSend()) { return 0; } if(writeBuffer == null) { writeBuffer = getChannel().getBufferPool().allocate(); } ByteBuffer buffer = writeBuffer.getBuffer(); int copied = Buffers.copy(buffer, src); if(!buffer.hasRemaining()) { handleBufferFull(); } writeSucceeded = writeSucceeded || copied > 0; return copied; } /** * Send a buffer to this channel. * * @param pooled Pooled ByteBuffer to send. The buffer should have data available. This channel will free the buffer * after sending data * @return true if the buffer was accepted; false if the channel needs to first be flushed * @throws IOException if this channel is closed */ public boolean send(PooledByteBuffer pooled) throws IOException { if(isWritesShutdown()) { throw UndertowMessages.MESSAGES.channelIsClosed(); } boolean result = sendInternal(pooled); if(result) { flush(); } return result; } protected boolean sendInternal(PooledByteBuffer pooled) throws IOException { if (safeToSend()) { this.body = pooled; writeSucceeded = true; return true; } return false; } protected boolean safeToSend() throws IOException { int state = this.state; if (anyAreSet(state, STATE_CLOSED) || broken) { throw UndertowMessages.MESSAGES.channelIsClosed(); } if (readyForFlush) { return false; //we can't do anything, we are waiting for a flush } if( null != this.body) { throw UndertowMessages.MESSAGES.bodyIsSetAndNotReadyForFlush(); } return true; } /** * Return the timeout used by awaitWritable and flush tasks. First the * channel write timeout is read and if not set the default * AWAIT_WRITABLE_TIMEOUT. * * @return the awaitWritable timeout, in milliseconds */ protected long getAwaitWritableTimeout() { Integer timeout = null; try { timeout = getChannel().getOption(Options.WRITE_TIMEOUT); } catch (IOException e) { // should never happen, ignoring } if (timeout != null && timeout > 0) { return timeout; } else { return AWAIT_WRITABLE_TIMEOUT; } } @Override public long writeFinal(ByteBuffer[] srcs, int offset, int length) throws IOException { return Channels.writeFinalBasic(this, srcs, offset, length); } @Override public long writeFinal(ByteBuffer[] srcs) throws IOException { return writeFinal(srcs, 0, srcs.length); } @Override public int writeFinal(ByteBuffer src) throws IOException { return Channels.writeFinalBasic(this, src); } private void handleBufferFull() throws IOException { synchronized (lock) { bufferFull = true; if (readyForFlush) return; sendWriteBuffer(); readyForFlush = true; state |= STATE_FIRST_DATA_WRITTEN; } channel.queueFrame((S) this); } private void sendWriteBuffer() throws IOException { if(writeBuffer == null) { writeBuffer = EMPTY_BYTE_BUFFER; } writeBuffer.getBuffer().flip(); if(!sendInternal(writeBuffer)) { throw UndertowMessages.MESSAGES.failedToSendAfterBeingSafe(); } writeBuffer = null; } /** * @return true If this is the last frame that will be sent on this connection */ protected abstract boolean isLastFrame(); /** * @return true if the channel is ready to be flushed. When a channel is ready to be flushed nothing should modify the buffer, * as it may be written out by another thread. */ public boolean isReadyForFlush() { return readyForFlush; } /** * Returns true writes have been shutdown */ public boolean isWritesShutdown() { return anyAreSet(state, STATE_WRITES_SHUTDOWN); } @Override public boolean isOpen() { return allAreClear(state, STATE_CLOSED); } @Override public void close() throws IOException { if(fullyFlushed || anyAreSet(state, STATE_CLOSED)) { return; } try { synchronized (lock) { // Double check to avoid executing the the rest of this method multiple times if(fullyFlushed || anyAreSet(state, STATE_CLOSED)) { return; } state |= STATE_CLOSED; if (writeBuffer != null) { writeBuffer.close(); writeBuffer = null; } if (body != null) { body.close(); body = null; } if (header != null && header.getByteBuffer() != null) { header.getByteBuffer().close(); header = null; } removeHandle(); } channelForciblyClosed(); //we need to wake up/invoke the write listener if (isWriteResumed()) { ChannelListeners.invokeChannelListener(getIoThread(), this, (ChannelListener) getWriteListener()); } wakeupWrites(); } finally { wakeupWaiters(); } } /** * Called when a channel has been forcibly closed, and data (frames) have already been written. * * The action this should take is protocol dependent, e.g. for SPDY a RST_STREAM should be sent, * for websockets the channel should be closed. * * By default this will just close the underlying channel * * @throws IOException */ protected void channelForciblyClosed() throws IOException { if(isFirstDataWritten()) { getChannel().markWritesBroken(null); } removeHandle(); wakeupWaiters(); } @Override public boolean supportsOption(Option option) { return false; } @Override public T getOption(Option tOption) throws IOException { return null; } @Override public T setOption(Option tOption, T t) throws IllegalArgumentException, IOException { return null; } public ByteBuffer getBuffer() { if(anyAreSet(state, STATE_CLOSED)) { throw new IllegalStateException(); } if(body == null) { // TODO should we IllegalState here? we expect a buffer to already exist body = EMPTY_BYTE_BUFFER; } return body.getBuffer(); } /** * Method that is invoked when a frame has been fully flushed. This method is only invoked by the IO thread */ final void flushComplete() throws IOException { synchronized (lock) { try { boolean resetReadyForFlush = true; bufferFull = false; int remaining = header.getRemainingInBuffer(); boolean finalFrame = finalFrameQueued; boolean channelClosed = finalFrame && remaining == 0 && !header.isAnotherFrameRequired(); if (remaining > 0) { // We still have a body, but since we just flushed, we transfer it to the write buffer. // This works as long as you call write() again or if finalFrame is true //TODO: this code may not work if the channel has frame level compression and flow control //we don't have an implementation that needs this yet so it is ok for now body.getBuffer().limit(body.getBuffer().limit() + remaining); body.getBuffer().compact(); writeBuffer = body; body = null; state &= ~STATE_PRE_WRITE_CALLED; if (finalFrame) { // we clear the final frame flag, as it could not actually be written out this.finalFrameQueued = false; // setting readyForFlush will prevent the final frame to be requeued by write listener, so mark // it as false; and do not reset it to false later on // (queueFinalFrame() will set readyForFlush to true and will do so iff readyForFlush is false) resetReadyForFlush = readyForFlush = false; flushExpirationTime = -1; queueFinalFrame(); } } else if (header.isAnotherFrameRequired()) { this.finalFrameQueued = false; if (body != null) { body.close(); body = null; state &= ~STATE_PRE_WRITE_CALLED; } } else if (body != null) { body.close(); body = null; state &= ~STATE_PRE_WRITE_CALLED; } if (channelClosed) { fullyFlushed = true; removeHandle(); if (body != null) { body.close(); body = null; state &= ~STATE_PRE_WRITE_CALLED; } } if (header.getByteBuffer() != null) { header.getByteBuffer().close(); } header = null; if (resetReadyForFlush) { readyForFlush = false; flushExpirationTime = -1; } if (isWriteResumed() && !channelClosed) { wakeupWrites(); } else if (isWriteResumed()) { //we need to execute the write listener one last time //we need to dispatch it back to the IO thread, so we don't invoke it recursivly ChannelListeners.invokeChannelListener(getIoThread(), (S) this, getWriteListener()); } final ChannelListener closeListener = this.closeSetter.get(); if (channelClosed && closeListener != null) { ChannelListeners.invokeChannelListener(getIoThread(), (S) AbstractFramedStreamSinkChannel.this, closeListener); } handleFlushComplete(channelClosed); } finally { wakeupWaiters(); } } } protected void handleFlushComplete(boolean finalFrame) { } protected boolean isFirstDataWritten() { return anyAreSet(state, STATE_FIRST_DATA_WRITTEN); } public void markBroken() { this.broken = true; try { wakeupWrites(); wakeupWaiters(); if (isWriteResumed()) { ChannelListener writeListener = this.writeSetter.get(); if (writeListener != null) { ChannelListeners.invokeChannelListener(getIoThread(), (S) this, writeListener); } } ChannelListener closeListener = this.closeSetter.get(); if (closeListener != null) { ChannelListeners.invokeChannelListener(getIoThread(), (S) this, closeListener); } } finally { removeHandle(); if(header != null) { if( header.getByteBuffer() != null) { header.getByteBuffer().close(); header = null; } } if(body != null) { body.close(); body = null; } if(writeBuffer != null) { writeBuffer.close(); writeBuffer = null; } } } ChannelListener getWriteListener() { return writeSetter.get(); } private void wakeupWaiters() { if(waiterCount > 0) { synchronized (lock) { // It is possible that waiter count would be updated before gaining the lock, lets check one more // time whether the condition wasn't changed in the meantime. if (waiterCount > 0) { lock.notifyAll(); } } } } public C getChannel() { return channel; } public boolean isBroken() { return broken; } public boolean isBufferFull() { return bufferFull; } private void removeHandle() { if (handle != null) { synchronized (lock) { if (handle != null) { handle.remove(); handle = null; } } } } private void addHandle(long timeout) { synchronized (lock) { if (handle == null) { handle = getChannel().getIoThread().executeAfter(timeoutRunnable, timeout + FUZZ_FACTOR, TimeUnit.MILLISECONDS); } } } /** * Adds the timeout task waiting for flushing. The task is assigned and * not removed until executed or closing the frame. This method is called * by {@link io.undertow.server.protocol.framed.AbstractFramedChannel} when * the frame is held by the frame priority to avoid hangs. */ void addReadyForFlushTask() { synchronized (lock) { final long timeout = this.getAwaitWritableTimeout(); flushExpirationTime = System.currentTimeMillis() + timeout; // set the handle to avoid wait forever for flushing addHandle(timeout); } } /** * Runnable used to execute the timeout and avoid hangs if the other * end does not read the data and the flush is not performed. The task once * added is never removed until executed or the frame is finally closed. * Therefore the task can: *

    *
  1. Do nothing if the frame was flushed or frame is closed/broken.
  2. *
  3. Re-schedule the task if the expiration time was extended.
  4. *
  5. Forcibly close the frame if timeout expired without flushing.
  6. *
*/ private static class TimeoutRunnable implements Runnable { private final AbstractFramedStreamSinkChannel channel; TimeoutRunnable(AbstractFramedStreamSinkChannel channel) { this.channel = channel; } @Override public void run() { long currentTime = System.currentTimeMillis(); synchronized (channel.lock) { channel.handle = null; final long flushExpirationTime = channel.flushExpirationTime; if (flushExpirationTime < 0 || !channel.isReadyForFlush() || !channel.isOpen() || channel.isBroken()) { // the channel does not need to check for timeout return; } else if (currentTime < flushExpirationTime) { // timeout has been re-scheduled channel.addHandle(flushExpirationTime - currentTime); return; } } // Reaching this point the flush has been waiting more than the timeout => terminate it UndertowLogger.REQUEST_IO_LOGGER.noFrameflushInTimeout(channel.getAwaitWritableTimeout()); try { channel.channelForciblyClosed(); } catch (IOException e) { UndertowLogger.REQUEST_IO_LOGGER.debugf(e, "Exception closing the framed sink channel because of timeout"); channel.markBroken(); } } } }