| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.tomcat.util.net; |
| |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.net.SocketTimeoutException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.CompletionHandler; |
| import java.nio.channels.InterruptedByTimeoutException; |
| import java.nio.channels.ReadPendingException; |
| import java.nio.channels.WritePendingException; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.RejectedExecutionException; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import jakarta.servlet.ServletConnection; |
| |
| import org.apache.juli.logging.Log; |
| import org.apache.juli.logging.LogFactory; |
| import org.apache.tomcat.util.ExceptionUtils; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| public abstract class SocketWrapperBase<E> { |
| |
| private static final Log log = LogFactory.getLog(SocketWrapperBase.class); |
| |
| protected static final StringManager sm = StringManager.getManager(SocketWrapperBase.class); |
| |
| /* |
| * At 100,000 connections a second there are enough IDs here for ~3,000,000 |
| * years before it overflows (and then we have another 3,000,000 years |
| * before it gets back to zero). |
| * |
| * Local testing shows that 5 threads can obtain 60,000,000+ IDs a second |
| * from a single AtomicLong. That is about about 17ns per request. It does |
| * not appear that the introduction of this counter will cause a bottleneck |
| * for connection processing. |
| */ |
| private static final AtomicLong connectionIdGenerator = new AtomicLong(0); |
| |
| private E socket; |
| private final AbstractEndpoint<E,?> endpoint; |
| private final ReentrantLock lock = new ReentrantLock(); |
| |
| protected final AtomicBoolean closed = new AtomicBoolean(false); |
| |
| // Volatile because I/O and setting the timeout values occurs on a different |
| // thread to the thread checking the timeout. |
| private volatile long readTimeout = -1; |
| private volatile long writeTimeout = -1; |
| |
| protected volatile IOException previousIOException = null; |
| |
| private volatile int keepAliveLeft = 100; |
| private String negotiatedProtocol = null; |
| |
| private final String connectionId; |
| |
| /* |
| * Following cached for speed / reduced GC |
| */ |
| protected String localAddr = null; |
| protected String localName = null; |
| protected int localPort = -1; |
| protected String remoteAddr = null; |
| protected String remoteHost = null; |
| protected int remotePort = -1; |
| protected volatile ServletConnection servletConnection = null; |
| |
| /** |
| * Used to record the first IOException that occurs during non-blocking |
| * read/writes that can't be usefully propagated up the stack since there is |
| * no user code or appropriate container code in the stack to handle it. |
| */ |
| private volatile IOException error = null; |
| |
| /** |
| * The buffers used for communicating with the socket. |
| */ |
| protected volatile SocketBufferHandler socketBufferHandler = null; |
| |
| /** |
| * The max size of the individual buffered write buffers |
| */ |
| protected int bufferedWriteSize = 64 * 1024; // 64k default write buffer |
| |
| /** |
| * Additional buffer used for non-blocking writes. Non-blocking writes need |
| * to return immediately even if the data cannot be written immediately but |
| * the socket buffer may not be big enough to hold all of the unwritten |
| * data. This structure provides an additional buffer to hold the data until |
| * it can be written. |
| * Not that while the Servlet API only allows one non-blocking write at a |
| * time, due to buffering and the possible need to write HTTP headers, this |
| * layer may see multiple writes. |
| */ |
| protected final WriteBuffer nonBlockingWriteBuffer = new WriteBuffer(bufferedWriteSize); |
| |
| /* |
| * Asynchronous operations. |
| */ |
| protected final Semaphore readPending; |
| protected volatile OperationState<?> readOperation = null; |
| protected final Semaphore writePending; |
| protected volatile OperationState<?> writeOperation = null; |
| |
| /** |
| * The org.apache.coyote.Processor instance currently associated with the |
| * wrapper. Only populated when required to maintain wrapper<->Processor |
| * mapping between calls to |
| * {@link AbstractEndpoint.Handler#process(SocketWrapperBase, SocketEvent)}. |
| */ |
| private final AtomicReference<Object> currentProcessor = new AtomicReference<>(); |
| |
| public SocketWrapperBase(E socket, AbstractEndpoint<E,?> endpoint) { |
| this.socket = socket; |
| this.endpoint = endpoint; |
| if (endpoint.getUseAsyncIO() || needSemaphores()) { |
| readPending = new Semaphore(1); |
| writePending = new Semaphore(1); |
| } else { |
| readPending = null; |
| writePending = null; |
| } |
| connectionId = Long.toHexString(connectionIdGenerator.getAndIncrement()); |
| } |
| |
| public E getSocket() { |
| return socket; |
| } |
| |
| protected void reset(E closedSocket) { |
| socket = closedSocket; |
| } |
| |
| protected AbstractEndpoint<E,?> getEndpoint() { |
| return endpoint; |
| } |
| |
| public ReentrantLock getLock() { |
| return lock; |
| } |
| |
| public Object getCurrentProcessor() { |
| return currentProcessor.get(); |
| } |
| |
| public void setCurrentProcessor(Object currentProcessor) { |
| this.currentProcessor.set(currentProcessor); |
| } |
| |
| public Object takeCurrentProcessor() { |
| return currentProcessor.getAndSet(null); |
| } |
| |
| /** |
| * Transfers processing to a container thread. |
| * |
| * @param runnable The actions to process on a container thread |
| * |
| * @throws RejectedExecutionException If the runnable cannot be executed |
| */ |
| public void execute(Runnable runnable) { |
| Executor executor = endpoint.getExecutor(); |
| if (!endpoint.isRunning() || executor == null) { |
| throw new RejectedExecutionException(); |
| } |
| executor.execute(runnable); |
| } |
| |
| public IOException getError() { return error; } |
| public void setError(IOException error) { |
| // Not perfectly thread-safe but good enough. Just needs to ensure that |
| // once this.error is non-null, it can never be null. |
| if (this.error != null) { |
| return; |
| } |
| this.error = error; |
| } |
| public void checkError() throws IOException { |
| if (error != null) { |
| throw error; |
| } |
| } |
| |
| public String getNegotiatedProtocol() { return negotiatedProtocol; } |
| public void setNegotiatedProtocol(String negotiatedProtocol) { |
| this.negotiatedProtocol = negotiatedProtocol; |
| } |
| |
| /** |
| * Set the timeout for reading. Values of zero or less will be changed to |
| * -1. |
| * |
| * @param readTimeout The timeout in milliseconds. A value of -1 indicates |
| * an infinite timeout. |
| */ |
| public void setReadTimeout(long readTimeout) { |
| if (readTimeout > 0) { |
| this.readTimeout = readTimeout; |
| } else { |
| this.readTimeout = -1; |
| } |
| } |
| |
| public long getReadTimeout() { |
| return this.readTimeout; |
| } |
| |
| /** |
| * Set the timeout for writing. Values of zero or less will be changed to |
| * -1. |
| * |
| * @param writeTimeout The timeout in milliseconds. A value of zero or less |
| * indicates an infinite timeout. |
| */ |
| public void setWriteTimeout(long writeTimeout) { |
| if (writeTimeout > 0) { |
| this.writeTimeout = writeTimeout; |
| } else { |
| this.writeTimeout = -1; |
| } |
| } |
| |
| public long getWriteTimeout() { |
| return this.writeTimeout; |
| } |
| |
| |
| public void setKeepAliveLeft(int keepAliveLeft) { this.keepAliveLeft = keepAliveLeft; } |
| public int decrementKeepAlive() { return (--keepAliveLeft); } |
| |
| public String getRemoteHost() { |
| if (remoteHost == null) { |
| populateRemoteHost(); |
| } |
| return remoteHost; |
| } |
| protected abstract void populateRemoteHost(); |
| |
| public String getRemoteAddr() { |
| if (remoteAddr == null) { |
| populateRemoteAddr(); |
| } |
| return remoteAddr; |
| } |
| protected abstract void populateRemoteAddr(); |
| |
| public int getRemotePort() { |
| if (remotePort == -1) { |
| populateRemotePort(); |
| } |
| return remotePort; |
| } |
| protected abstract void populateRemotePort(); |
| |
| public String getLocalName() { |
| if (localName == null) { |
| populateLocalName(); |
| } |
| return localName; |
| } |
| protected abstract void populateLocalName(); |
| |
| public String getLocalAddr() { |
| if (localAddr == null) { |
| populateLocalAddr(); |
| } |
| return localAddr; |
| } |
| protected abstract void populateLocalAddr(); |
| |
| public int getLocalPort() { |
| if (localPort == -1) { |
| populateLocalPort(); |
| } |
| return localPort; |
| } |
| protected abstract void populateLocalPort(); |
| |
| public SocketBufferHandler getSocketBufferHandler() { return socketBufferHandler; } |
| |
| public boolean hasDataToRead() { |
| // Return true because it is always safe to make a read attempt |
| return true; |
| } |
| |
| public boolean hasDataToWrite() { |
| return !socketBufferHandler.isWriteBufferEmpty() || !nonBlockingWriteBuffer.isEmpty(); |
| } |
| |
| /** |
| * Checks to see if there are any writes pending and if there are calls |
| * {@link #registerWriteInterest()} to trigger a callback once the pending |
| * writes have completed. |
| * <p> |
| * Note: Once this method has returned <code>false</code> it <b>MUST NOT</b> |
| * be called again until the pending write has completed and the |
| * callback has been fired. |
| * TODO: Modify {@link #registerWriteInterest()} so the above |
| * restriction is enforced there rather than relying on the caller. |
| * |
| * @return <code>true</code> if no writes are pending and data can be |
| * written otherwise <code>false</code> |
| */ |
| public boolean isReadyForWrite() { |
| boolean result = canWrite(); |
| if (!result) { |
| registerWriteInterest(); |
| } |
| return result; |
| } |
| |
| |
| public boolean canWrite() { |
| if (socketBufferHandler == null) { |
| throw new IllegalStateException(sm.getString("socket.closed")); |
| } |
| return socketBufferHandler.isWriteBufferWritable() && nonBlockingWriteBuffer.isEmpty(); |
| } |
| |
| |
| /** |
| * Overridden for debug purposes. No guarantees are made about the format of |
| * this message which may vary significantly between point releases. |
| * <p> |
| * {@inheritDoc} |
| */ |
| @Override |
| public String toString() { |
| return super.toString() + ":" + String.valueOf(socket); |
| } |
| |
| |
| public abstract int read(boolean block, byte[] b, int off, int len) throws IOException; |
| public abstract int read(boolean block, ByteBuffer to) throws IOException; |
| public abstract boolean isReadyForRead() throws IOException; |
| public abstract void setAppReadBufHandler(ApplicationBufferHandler handler); |
| |
| protected int populateReadBuffer(byte[] b, int off, int len) { |
| socketBufferHandler.configureReadBufferForRead(); |
| ByteBuffer readBuffer = socketBufferHandler.getReadBuffer(); |
| int remaining = readBuffer.remaining(); |
| |
| // Is there enough data in the read buffer to satisfy this request? |
| // Copy what data there is in the read buffer to the byte array |
| if (remaining > 0) { |
| remaining = Math.min(remaining, len); |
| readBuffer.get(b, off, remaining); |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Socket: [" + this + "], Read from buffer: [" + remaining + "]"); |
| } |
| } |
| return remaining; |
| } |
| |
| |
| protected int populateReadBuffer(ByteBuffer to) { |
| // Is there enough data in the read buffer to satisfy this request? |
| // Copy what data there is in the read buffer to the byte array |
| socketBufferHandler.configureReadBufferForRead(); |
| int nRead = transfer(socketBufferHandler.getReadBuffer(), to); |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Socket: [" + this + "], Read from buffer: [" + nRead + "]"); |
| } |
| return nRead; |
| } |
| |
| |
| /** |
| * Return input that has been read to the input buffer for re-reading by the |
| * correct component. There are times when a component may read more data |
| * than it needs before it passes control to another component. One example |
| * of this is during HTTP upgrade. If an (arguably misbehaving client) sends |
| * data associated with the upgraded protocol before the HTTP upgrade |
| * completes, the HTTP handler may read it. This method provides a way for |
| * that data to be returned so it can be processed by the correct component. |
| * |
| * @param returnedInput The input to return to the input buffer. |
| */ |
| public void unRead(ByteBuffer returnedInput) { |
| if (returnedInput != null) { |
| socketBufferHandler.unReadReadBuffer(returnedInput); |
| } |
| } |
| |
| |
| /** |
| * Close the socket wrapper. |
| */ |
| public void close() { |
| if (closed.compareAndSet(false, true)) { |
| try { |
| getEndpoint().getHandler().release(this); |
| } catch (Throwable e) { |
| ExceptionUtils.handleThrowable(e); |
| if (log.isDebugEnabled()) { |
| log.error(sm.getString("endpoint.debug.handlerRelease"), e); |
| } |
| } finally { |
| getEndpoint().countDownConnection(); |
| doClose(); |
| } |
| } |
| } |
| |
| /** |
| * Perform the actual close. The closed atomic boolean guarantees this will |
| * be called only once per wrapper. |
| */ |
| protected abstract void doClose(); |
| |
| /** |
| * @return true if the wrapper has been closed |
| */ |
| public boolean isClosed() { |
| return closed.get(); |
| } |
| |
| |
| /** |
| * Writes the provided data to the socket write buffer. If the socket write |
| * buffer fills during the write, the content of the socket write buffer is |
| * written to the network and this method starts to fill the socket write |
| * buffer again. Depending on the size of the data to write, there may be |
| * multiple writes to the network. |
| * <p> |
| * Non-blocking writes must return immediately and the byte array holding |
| * the data to be written must be immediately available for re-use. It may |
| * not be possible to write sufficient data to the network to allow this to |
| * happen. In this case data that cannot be written to the network and |
| * cannot be held by the socket buffer is stored in the non-blocking write |
| * buffer. |
| * <p> |
| * Note: There is an implementation assumption that, before switching from |
| * non-blocking writes to blocking writes, any data remaining in the |
| * non-blocking write buffer will have been written to the network. |
| * |
| * @param block <code>true</code> if a blocking write should be used, |
| * otherwise a non-blocking write will be used |
| * @param buf The byte array containing the data to be written |
| * @param off The offset within the byte array of the data to be written |
| * @param len The length of the data to be written |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| public final void write(boolean block, byte[] buf, int off, int len) throws IOException { |
| if (len == 0 || buf == null) { |
| return; |
| } |
| |
| /* |
| * While the implementations for blocking and non-blocking writes are |
| * very similar they have been split into separate methods: |
| * - To allow sub-classes to override them individually. NIO2, for |
| * example, overrides the non-blocking write but not the blocking |
| * write. |
| * - To enable a marginally more efficient implemented for blocking |
| * writes which do not require the additional checks related to the |
| * use of the non-blocking write buffer |
| */ |
| if (block) { |
| writeBlocking(buf, off, len); |
| } else { |
| writeNonBlocking(buf, off, len); |
| } |
| } |
| |
| |
| /** |
| * Writes the provided data to the socket write buffer. If the socket write |
| * buffer fills during the write, the content of the socket write buffer is |
| * written to the network and this method starts to fill the socket write |
| * buffer again. Depending on the size of the data to write, there may be |
| * multiple writes to the network. |
| * <p> |
| * Non-blocking writes must return immediately and the ByteBuffer holding |
| * the data to be written must be immediately available for re-use. It may |
| * not be possible to write sufficient data to the network to allow this to |
| * happen. In this case data that cannot be written to the network and |
| * cannot be held by the socket buffer is stored in the non-blocking write |
| * buffer. |
| * <p> |
| * Note: There is an implementation assumption that, before switching from |
| * non-blocking writes to blocking writes, any data remaining in the |
| * non-blocking write buffer will have been written to the network. |
| * |
| * @param block <code>true</code> if a blocking write should be used, |
| * otherwise a non-blocking write will be used |
| * @param from The ByteBuffer containing the data to be written |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| public final void write(boolean block, ByteBuffer from) throws IOException { |
| if (from == null || from.remaining() == 0) { |
| return; |
| } |
| |
| /* |
| * While the implementations for blocking and non-blocking writes are |
| * very similar they have been split into separate methods: |
| * - To allow sub-classes to override them individually. NIO2, for |
| * example, overrides the non-blocking write but not the blocking |
| * write. |
| * - To enable a marginally more efficient implemented for blocking |
| * writes which do not require the additional checks related to the |
| * use of the non-blocking write buffer |
| */ |
| if (block) { |
| writeBlocking(from); |
| } else { |
| writeNonBlocking(from); |
| } |
| } |
| |
| |
| /** |
| * Writes the provided data to the socket write buffer. If the socket write |
| * buffer fills during the write, the content of the socket write buffer is |
| * written to the network using a blocking write. Once that blocking write |
| * is complete, this method starts to fill the socket write buffer again. |
| * Depending on the size of the data to write, there may be multiple writes |
| * to the network. On completion of this method there will always be space |
| * remaining in the socket write buffer. |
| * |
| * @param buf The byte array containing the data to be written |
| * @param off The offset within the byte array of the data to be written |
| * @param len The length of the data to be written |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| protected void writeBlocking(byte[] buf, int off, int len) throws IOException { |
| if (len > 0) { |
| socketBufferHandler.configureWriteBufferForWrite(); |
| int thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); |
| len -= thisTime; |
| while (len > 0) { |
| off += thisTime; |
| doWrite(true); |
| socketBufferHandler.configureWriteBufferForWrite(); |
| thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); |
| len -= thisTime; |
| } |
| } |
| } |
| |
| |
| /** |
| * Writes the provided data to the socket write buffer. If the socket write |
| * buffer fills during the write, the content of the socket write buffer is |
| * written to the network using a blocking write. Once that blocking write |
| * is complete, this method starts to fill the socket write buffer again. |
| * Depending on the size of the data to write, there may be multiple writes |
| * to the network. On completion of this method there will always be space |
| * remaining in the socket write buffer. |
| * |
| * @param from The ByteBuffer containing the data to be written |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| protected void writeBlocking(ByteBuffer from) throws IOException { |
| if (from.hasRemaining()) { |
| socketBufferHandler.configureWriteBufferForWrite(); |
| transfer(from, socketBufferHandler.getWriteBuffer()); |
| while (from.hasRemaining()) { |
| doWrite(true); |
| socketBufferHandler.configureWriteBufferForWrite(); |
| transfer(from, socketBufferHandler.getWriteBuffer()); |
| } |
| } |
| } |
| |
| |
| /** |
| * Transfers the data to the socket write buffer (writing that data to the |
| * socket if the buffer fills up using a non-blocking write) until either |
| * all the data has been transferred and space remains in the socket write |
| * buffer or a non-blocking write leaves data in the socket write buffer. |
| * After an incomplete write, any data remaining to be transferred to the |
| * socket write buffer will be copied to the socket write buffer. If the |
| * remaining data is too big for the socket write buffer, the socket write |
| * buffer will be filled and the additional data written to the non-blocking |
| * write buffer. |
| * |
| * @param buf The byte array containing the data to be written |
| * @param off The offset within the byte array of the data to be written |
| * @param len The length of the data to be written |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| protected void writeNonBlocking(byte[] buf, int off, int len) throws IOException { |
| if (len > 0 && nonBlockingWriteBuffer.isEmpty() |
| && socketBufferHandler.isWriteBufferWritable()) { |
| socketBufferHandler.configureWriteBufferForWrite(); |
| int thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); |
| len -= thisTime; |
| while (len > 0) { |
| off = off + thisTime; |
| doWrite(false); |
| if (len > 0 && socketBufferHandler.isWriteBufferWritable()) { |
| socketBufferHandler.configureWriteBufferForWrite(); |
| thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); |
| } else { |
| // Didn't write any data in the last non-blocking write. |
| // Therefore the write buffer will still be full. Nothing |
| // else to do here. Exit the loop. |
| break; |
| } |
| len -= thisTime; |
| } |
| } |
| |
| if (len > 0) { |
| // Remaining data must be buffered |
| nonBlockingWriteBuffer.add(buf, off, len); |
| } |
| } |
| |
| |
| /** |
| * Transfers the data to the socket write buffer (writing that data to the |
| * socket if the buffer fills up using a non-blocking write) until either |
| * all the data has been transferred and space remains in the socket write |
| * buffer or a non-blocking write leaves data in the socket write buffer. |
| * After an incomplete write, any data remaining to be transferred to the |
| * socket write buffer will be copied to the socket write buffer. If the |
| * remaining data is too big for the socket write buffer, the socket write |
| * buffer will be filled and the additional data written to the non-blocking |
| * write buffer. |
| * |
| * @param from The ByteBuffer containing the data to be written |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| protected void writeNonBlocking(ByteBuffer from) |
| throws IOException { |
| |
| if (from.hasRemaining() && nonBlockingWriteBuffer.isEmpty() |
| && socketBufferHandler.isWriteBufferWritable()) { |
| writeNonBlockingInternal(from); |
| } |
| |
| if (from.hasRemaining()) { |
| // Remaining data must be buffered |
| nonBlockingWriteBuffer.add(from); |
| } |
| } |
| |
| |
| /** |
| * Separate method so it can be re-used by the socket write buffer to write |
| * data to the network |
| * |
| * @param from The ByteBuffer containing the data to be written |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| protected void writeNonBlockingInternal(ByteBuffer from) throws IOException { |
| socketBufferHandler.configureWriteBufferForWrite(); |
| transfer(from, socketBufferHandler.getWriteBuffer()); |
| while (from.hasRemaining()) { |
| doWrite(false); |
| if (socketBufferHandler.isWriteBufferWritable()) { |
| socketBufferHandler.configureWriteBufferForWrite(); |
| transfer(from, socketBufferHandler.getWriteBuffer()); |
| } else { |
| break; |
| } |
| } |
| } |
| |
| |
| /** |
| * Writes as much data as possible from any that remains in the buffers. |
| * |
| * @param block <code>true</code> if a blocking write should be used, |
| * otherwise a non-blocking write will be used |
| * |
| * @return <code>true</code> if data remains to be flushed after this method |
| * completes, otherwise <code>false</code>. In blocking mode |
| * therefore, the return value should always be <code>false</code> |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| public boolean flush(boolean block) throws IOException { |
| boolean result = false; |
| if (block) { |
| // A blocking flush will always empty the buffer. |
| flushBlocking(); |
| } else { |
| result = flushNonBlocking(); |
| } |
| |
| return result; |
| } |
| |
| |
| /** |
| * Writes all remaining data from the buffers and blocks until the write is |
| * complete. |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| protected void flushBlocking() throws IOException { |
| doWrite(true); |
| |
| if (!nonBlockingWriteBuffer.isEmpty()) { |
| nonBlockingWriteBuffer.write(this, true); |
| |
| if (!socketBufferHandler.isWriteBufferEmpty()) { |
| doWrite(true); |
| } |
| } |
| |
| } |
| |
| |
| /** |
| * Writes as much data as possible from any that remains in the buffers. |
| * |
| * @return <code>true</code> if data remains to be flushed after this method |
| * completes, otherwise <code>false</code>. |
| * |
| * @throws IOException If an IO error occurs during the write |
| */ |
| protected abstract boolean flushNonBlocking() throws IOException; |
| |
| |
| /** |
| * Write the contents of the socketWriteBuffer to the socket. For blocking |
| * writes either then entire contents of the buffer will be written or an |
| * IOException will be thrown. Partial blocking writes will not occur. |
| * |
| * @param block Should the write be blocking or not? |
| * |
| * @throws IOException If an I/O error such as a timeout occurs during the |
| * write |
| */ |
| protected void doWrite(boolean block) throws IOException { |
| socketBufferHandler.configureWriteBufferForRead(); |
| doWrite(block, socketBufferHandler.getWriteBuffer()); |
| } |
| |
| |
| /** |
| * Write the contents of the ByteBuffer to the socket. For blocking writes |
| * either then entire contents of the buffer will be written or an |
| * IOException will be thrown. Partial blocking writes will not occur. |
| * |
| * @param block Should the write be blocking or not? |
| * @param from the ByteBuffer containing the data to be written |
| * |
| * @throws IOException If an I/O error such as a timeout occurs during the |
| * write |
| */ |
| protected abstract void doWrite(boolean block, ByteBuffer from) throws IOException; |
| |
| |
| public void processSocket(SocketEvent socketStatus, boolean dispatch) { |
| endpoint.processSocket(this, socketStatus, dispatch); |
| } |
| |
| |
| public abstract void registerReadInterest(); |
| |
| public abstract void registerWriteInterest(); |
| |
| public abstract SendfileDataBase createSendfileData(String filename, long pos, long length); |
| |
| /** |
| * Starts the sendfile process. It is expected that if the sendfile process |
| * does not complete during this call and does not report an error, that the |
| * caller <b>will not</b> add the socket to the poller (or equivalent). That |
| * is the responsibility of this method. |
| * |
| * @param sendfileData Data representing the file to send |
| * |
| * @return The state of the sendfile process after the first write. |
| */ |
| public abstract SendfileState processSendfile(SendfileDataBase sendfileData); |
| |
| /** |
| * Require the client to perform CLIENT-CERT authentication if it hasn't |
| * already done so. |
| * |
| * @param sslSupport The SSL/TLS support instance currently being used by |
| * the connection that may need updating after the client |
| * authentication |
| * |
| * @throws IOException If authentication is required then there will be I/O |
| * with the client and this exception will be thrown if |
| * that goes wrong |
| */ |
| public abstract void doClientAuth(SSLSupport sslSupport) throws IOException; |
| |
| /** |
| * Obtain an SSLSupport instance for this socket. |
| * |
| * @return An SSLSupport instance for this socket. |
| */ |
| public abstract SSLSupport getSslSupport(); |
| |
| |
| // ------------------------------------------------------- NIO 2 style APIs |
| |
| |
| public enum BlockingMode { |
| /** |
| * The operation will not block. If there are pending operations, |
| * the operation will throw a pending exception. |
| */ |
| CLASSIC, |
| /** |
| * The operation will not block. If there are pending operations, |
| * the operation will return CompletionState.NOT_DONE. |
| */ |
| NON_BLOCK, |
| /** |
| * The operation will block until pending operations are completed, but |
| * will not block after performing it. |
| */ |
| SEMI_BLOCK, |
| /** |
| * The operation will block until completed. |
| */ |
| BLOCK |
| } |
| |
| public enum CompletionState { |
| /** |
| * Operation is still pending. |
| */ |
| PENDING, |
| /** |
| * Operation was pending and non blocking. |
| */ |
| NOT_DONE, |
| /** |
| * The operation completed inline. |
| */ |
| INLINE, |
| /** |
| * The operation completed inline but failed. |
| */ |
| ERROR, |
| /** |
| * The operation completed, but not inline. |
| */ |
| DONE |
| } |
| |
| public enum CompletionHandlerCall { |
| /** |
| * Operation should continue, the completion handler shouldn't be |
| * called. |
| */ |
| CONTINUE, |
| /** |
| * The operation completed but the completion handler shouldn't be |
| * called. |
| */ |
| NONE, |
| /** |
| * The operation is complete, the completion handler should be |
| * called. |
| */ |
| DONE |
| } |
| |
| public interface CompletionCheck { |
| /** |
| * Determine what call, if any, should be made to the completion |
| * handler. |
| * |
| * @param state of the operation (done or done in-line since the |
| * IO call is done) |
| * @param buffers ByteBuffer[] that has been passed to the |
| * original IO call |
| * @param offset that has been passed to the original IO call |
| * @param length that has been passed to the original IO call |
| * |
| * @return The call, if any, to make to the completion handler |
| */ |
| CompletionHandlerCall callHandler(CompletionState state, ByteBuffer[] buffers, |
| int offset, int length); |
| } |
| |
| /** |
| * This utility CompletionCheck will cause the write to fully write |
| * all remaining data. If the operation completes inline, the |
| * completion handler will not be called. |
| */ |
| public static final CompletionCheck COMPLETE_WRITE = new CompletionCheck() { |
| @Override |
| public CompletionHandlerCall callHandler(CompletionState state, ByteBuffer[] buffers, |
| int offset, int length) { |
| for (int i = 0; i < length; i++) { |
| if (buffers[offset + i].hasRemaining()) { |
| return CompletionHandlerCall.CONTINUE; |
| } |
| } |
| return (state == CompletionState.DONE) ? CompletionHandlerCall.DONE |
| : CompletionHandlerCall.NONE; |
| } |
| }; |
| |
| /** |
| * This utility CompletionCheck will cause the write to fully write |
| * all remaining data. The completion handler will then be called. |
| */ |
| public static final CompletionCheck COMPLETE_WRITE_WITH_COMPLETION = new CompletionCheck() { |
| @Override |
| public CompletionHandlerCall callHandler(CompletionState state, ByteBuffer[] buffers, |
| int offset, int length) { |
| for (int i = 0; i < length; i++) { |
| if (buffers[offset + i].hasRemaining()) { |
| return CompletionHandlerCall.CONTINUE; |
| } |
| } |
| return CompletionHandlerCall.DONE; |
| } |
| }; |
| |
| /** |
| * This utility CompletionCheck will cause the completion handler |
| * to be called once some data has been read. If the operation |
| * completes inline, the completion handler will not be called. |
| */ |
| public static final CompletionCheck READ_DATA = new CompletionCheck() { |
| @Override |
| public CompletionHandlerCall callHandler(CompletionState state, ByteBuffer[] buffers, |
| int offset, int length) { |
| return (state == CompletionState.DONE) ? CompletionHandlerCall.DONE |
| : CompletionHandlerCall.NONE; |
| } |
| }; |
| |
| /** |
| * This utility CompletionCheck will cause the completion handler |
| * to be called once the given buffers are full. The completion |
| * handler will then be called. |
| */ |
| public static final CompletionCheck COMPLETE_READ_WITH_COMPLETION = COMPLETE_WRITE_WITH_COMPLETION; |
| |
| /** |
| * This utility CompletionCheck will cause the completion handler |
| * to be called once the given buffers are full. If the operation |
| * completes inline, the completion handler will not be called. |
| */ |
| public static final CompletionCheck COMPLETE_READ = COMPLETE_WRITE; |
| |
| /** |
| * Internal state tracker for vectored operations. |
| */ |
| protected abstract class OperationState<A> implements Runnable { |
| protected final boolean read; |
| protected final ByteBuffer[] buffers; |
| protected final int offset; |
| protected final int length; |
| protected final A attachment; |
| protected final long timeout; |
| protected final TimeUnit unit; |
| protected final BlockingMode block; |
| protected final CompletionCheck check; |
| protected final CompletionHandler<Long, ? super A> handler; |
| protected final Semaphore semaphore; |
| protected final VectoredIOCompletionHandler<A> completion; |
| protected final AtomicBoolean callHandler; |
| protected OperationState(boolean read, ByteBuffer[] buffers, int offset, int length, |
| BlockingMode block, long timeout, TimeUnit unit, A attachment, |
| CompletionCheck check, CompletionHandler<Long, ? super A> handler, |
| Semaphore semaphore, VectoredIOCompletionHandler<A> completion) { |
| this.read = read; |
| this.buffers = buffers; |
| this.offset = offset; |
| this.length = length; |
| this.block = block; |
| this.timeout = timeout; |
| this.unit = unit; |
| this.attachment = attachment; |
| this.check = check; |
| this.handler = handler; |
| this.semaphore = semaphore; |
| this.completion = completion; |
| callHandler = (handler != null) ? new AtomicBoolean(true) : null; |
| } |
| protected volatile long nBytes = 0; |
| protected volatile CompletionState state = CompletionState.PENDING; |
| protected boolean completionDone = true; |
| |
| /** |
| * @return true if the operation is still inline, false if the operation |
| * is running on a thread that is not the original caller |
| */ |
| protected abstract boolean isInline(); |
| |
| protected boolean hasOutboundRemaining() { |
| // NIO2 and APR never have remaining outbound data when the |
| // completion handler is called. NIO needs to override this. |
| return false; |
| } |
| |
| |
| /** |
| * Process the operation using the connector executor. |
| * @return true if the operation was accepted, false if the executor |
| * rejected execution |
| */ |
| protected boolean process() { |
| try { |
| getEndpoint().getExecutor().execute(this); |
| return true; |
| } catch (RejectedExecutionException ree) { |
| log.warn(sm.getString("endpoint.executor.fail", SocketWrapperBase.this) , ree); |
| } catch (Throwable t) { |
| ExceptionUtils.handleThrowable(t); |
| // This means we got an OOM or similar creating a thread, or that |
| // the pool and its queue are full |
| log.error(sm.getString("endpoint.process.fail"), t); |
| } |
| return false; |
| } |
| |
| /** |
| * Start the operation, this will typically call run. |
| */ |
| protected void start() { |
| run(); |
| } |
| |
| /** |
| * End the operation. |
| */ |
| protected void end() { |
| } |
| |
| } |
| |
| /** |
| * Completion handler for vectored operations. This will check the completion of the operation, |
| * then either continue or call the user provided completion handler. |
| */ |
| protected class VectoredIOCompletionHandler<A> implements CompletionHandler<Long, OperationState<A>> { |
| @Override |
| public void completed(Long nBytes, OperationState<A> state) { |
| if (nBytes.longValue() < 0) { |
| failed(new EOFException(), state); |
| } else { |
| state.nBytes += nBytes.longValue(); |
| CompletionState currentState = state.isInline() ? CompletionState.INLINE : CompletionState.DONE; |
| boolean complete = true; |
| boolean completion = true; |
| if (state.check != null) { |
| CompletionHandlerCall call = state.check.callHandler(currentState, state.buffers, state.offset, state.length); |
| if (call == CompletionHandlerCall.CONTINUE || (!state.read && state.hasOutboundRemaining())) { |
| complete = false; |
| } else if (call == CompletionHandlerCall.NONE) { |
| completion = false; |
| } |
| } |
| if (complete) { |
| boolean notify = false; |
| if (state.read) { |
| readOperation = null; |
| } else { |
| writeOperation = null; |
| } |
| // Semaphore must be released after [read|write]Operation is cleared |
| // to ensure that the next thread to hold the semaphore hasn't |
| // written a new value to [read|write]Operation by the time it is |
| // cleared. |
| state.semaphore.release(); |
| if (state.block == BlockingMode.BLOCK && currentState != CompletionState.INLINE) { |
| notify = true; |
| } else { |
| state.state = currentState; |
| } |
| state.end(); |
| if (completion && state.handler != null && state.callHandler.compareAndSet(true, false)) { |
| state.handler.completed(Long.valueOf(state.nBytes), state.attachment); |
| } |
| synchronized (state) { |
| state.completionDone = true; |
| if (notify) { |
| state.state = currentState; |
| state.notify(); |
| } |
| } |
| } else { |
| synchronized (state) { |
| state.completionDone = true; |
| } |
| state.run(); |
| } |
| } |
| } |
| @Override |
| public void failed(Throwable exc, OperationState<A> state) { |
| IOException ioe = null; |
| if (exc instanceof InterruptedByTimeoutException) { |
| ioe = new SocketTimeoutException(); |
| exc = ioe; |
| } else if (exc instanceof IOException) { |
| ioe = (IOException) exc; |
| } |
| setError(ioe); |
| boolean notify = false; |
| if (state.read) { |
| readOperation = null; |
| } else { |
| writeOperation = null; |
| } |
| // Semaphore must be released after [read|write]Operation is cleared |
| // to ensure that the next thread to hold the semaphore hasn't |
| // written a new value to [read|write]Operation by the time it is |
| // cleared. |
| state.semaphore.release(); |
| if (state.block == BlockingMode.BLOCK) { |
| notify = true; |
| } else { |
| state.state = state.isInline() ? CompletionState.ERROR : CompletionState.DONE; |
| } |
| state.end(); |
| if (state.handler != null && state.callHandler.compareAndSet(true, false)) { |
| state.handler.failed(exc, state.attachment); |
| } |
| synchronized (state) { |
| state.completionDone = true; |
| if (notify) { |
| state.state = state.isInline() ? CompletionState.ERROR : CompletionState.DONE; |
| state.notify(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Allows using NIO2 style read/write. |
| * |
| * @return {@code true} if the connector has the capability enabled |
| */ |
| public boolean hasAsyncIO() { |
| // The semaphores are only created if async IO is enabled |
| return (readPending != null); |
| } |
| |
| /** |
| * Allows indicating if the connector needs semaphores. |
| * |
| * @return This default implementation always returns {@code false} |
| */ |
| public boolean needSemaphores() { |
| return false; |
| } |
| |
| /** |
| * Allows indicating if the connector supports per operation timeout. |
| * |
| * @return This default implementation always returns {@code false} |
| */ |
| public boolean hasPerOperationTimeout() { |
| return false; |
| } |
| |
| /** |
| * Allows checking if an asynchronous read operation is currently pending. |
| * @return <code>true</code> if the endpoint supports asynchronous IO and |
| * a read operation is being processed asynchronously |
| */ |
| public boolean isReadPending() { |
| return false; |
| } |
| |
| /** |
| * Allows checking if an asynchronous write operation is currently pending. |
| * @return <code>true</code> if the endpoint supports asynchronous IO and |
| * a write operation is being processed asynchronously |
| */ |
| public boolean isWritePending() { |
| return false; |
| } |
| |
| /** |
| * Scatter read. The completion handler will be called once some |
| * data has been read or an error occurred. The default NIO2 |
| * behavior is used: the completion handler will be called as soon |
| * as some data has been read, even if the read has completed inline. |
| * |
| * @param timeout timeout duration for the read |
| * @param unit units for the timeout duration |
| * @param attachment an object to attach to the I/O operation that will be |
| * used when calling the completion handler |
| * @param handler to call when the IO is complete |
| * @param dsts buffers |
| * @param <A> The attachment type |
| * @return the completion state (done, done inline, or still pending) |
| */ |
| public final <A> CompletionState read(long timeout, TimeUnit unit, A attachment, |
| CompletionHandler<Long, ? super A> handler, ByteBuffer... dsts) { |
| if (dsts == null) { |
| throw new IllegalArgumentException(); |
| } |
| return read(dsts, 0, dsts.length, BlockingMode.CLASSIC, timeout, unit, attachment, null, handler); |
| } |
| |
| /** |
| * Scatter read. The completion handler will be called once some |
| * data has been read or an error occurred. If a CompletionCheck |
| * object has been provided, the completion handler will only be |
| * called if the callHandler method returned true. If no |
| * CompletionCheck object has been provided, the default NIO2 |
| * behavior is used: the completion handler will be called as soon |
| * as some data has been read, even if the read has completed inline. |
| * |
| * @param block is the blocking mode that will be used for this operation |
| * @param timeout timeout duration for the read |
| * @param unit units for the timeout duration |
| * @param attachment an object to attach to the I/O operation that will be |
| * used when calling the completion handler |
| * @param check for the IO operation completion |
| * @param handler to call when the IO is complete |
| * @param dsts buffers |
| * @param <A> The attachment type |
| * @return the completion state (done, done inline, or still pending) |
| */ |
| public final <A> CompletionState read(BlockingMode block, long timeout, |
| TimeUnit unit, A attachment, CompletionCheck check, |
| CompletionHandler<Long, ? super A> handler, ByteBuffer... dsts) { |
| if (dsts == null) { |
| throw new IllegalArgumentException(); |
| } |
| return read(dsts, 0, dsts.length, block, timeout, unit, attachment, check, handler); |
| } |
| |
| /** |
| * Scatter read. The completion handler will be called once some |
| * data has been read or an error occurred. If a CompletionCheck |
| * object has been provided, the completion handler will only be |
| * called if the callHandler method returned true. If no |
| * CompletionCheck object has been provided, the default NIO2 |
| * behavior is used: the completion handler will be called as soon |
| * as some data has been read, even if the read has completed inline. |
| * |
| * @param dsts buffers |
| * @param offset in the buffer array |
| * @param length in the buffer array |
| * @param block is the blocking mode that will be used for this operation |
| * @param timeout timeout duration for the read |
| * @param unit units for the timeout duration |
| * @param attachment an object to attach to the I/O operation that will be |
| * used when calling the completion handler |
| * @param check for the IO operation completion |
| * @param handler to call when the IO is complete |
| * @param <A> The attachment type |
| * @return the completion state (done, done inline, or still pending) |
| */ |
| public final <A> CompletionState read(ByteBuffer[] dsts, int offset, int length, |
| BlockingMode block, long timeout, TimeUnit unit, A attachment, |
| CompletionCheck check, CompletionHandler<Long, ? super A> handler) { |
| return vectoredOperation(true, dsts, offset, length, block, timeout, unit, attachment, check, handler); |
| } |
| |
| /** |
| * Gather write. The completion handler will be called once some |
| * data has been written or an error occurred. The default NIO2 |
| * behavior is used: the completion handler will be called, even |
| * if the write is incomplete and data remains in the buffers, or |
| * if the write completed inline. |
| * |
| * @param timeout timeout duration for the write |
| * @param unit units for the timeout duration |
| * @param attachment an object to attach to the I/O operation that will be |
| * used when calling the completion handler |
| * @param handler to call when the IO is complete |
| * @param srcs buffers |
| * @param <A> The attachment type |
| * @return the completion state (done, done inline, or still pending) |
| */ |
| public final <A> CompletionState write(long timeout, TimeUnit unit, A attachment, |
| CompletionHandler<Long, ? super A> handler, ByteBuffer... srcs) { |
| if (srcs == null) { |
| throw new IllegalArgumentException(); |
| } |
| return write(srcs, 0, srcs.length, BlockingMode.CLASSIC, timeout, unit, attachment, null, handler); |
| } |
| |
| /** |
| * Gather write. The completion handler will be called once some |
| * data has been written or an error occurred. If a CompletionCheck |
| * object has been provided, the completion handler will only be |
| * called if the callHandler method returned true. If no |
| * CompletionCheck object has been provided, the default NIO2 |
| * behavior is used: the completion handler will be called, even |
| * if the write is incomplete and data remains in the buffers, or |
| * if the write completed inline. |
| * |
| * @param block is the blocking mode that will be used for this operation |
| * @param timeout timeout duration for the write |
| * @param unit units for the timeout duration |
| * @param attachment an object to attach to the I/O operation that will be |
| * used when calling the completion handler |
| * @param check for the IO operation completion |
| * @param handler to call when the IO is complete |
| * @param srcs buffers |
| * @param <A> The attachment type |
| * @return the completion state (done, done inline, or still pending) |
| */ |
| public final <A> CompletionState write(BlockingMode block, long timeout, |
| TimeUnit unit, A attachment, CompletionCheck check, |
| CompletionHandler<Long, ? super A> handler, ByteBuffer... srcs) { |
| if (srcs == null) { |
| throw new IllegalArgumentException(); |
| } |
| return write(srcs, 0, srcs.length, block, timeout, unit, attachment, check, handler); |
| } |
| |
| /** |
| * Gather write. The completion handler will be called once some |
| * data has been written or an error occurred. If a CompletionCheck |
| * object has been provided, the completion handler will only be |
| * called if the callHandler method returned true. If no |
| * CompletionCheck object has been provided, the default NIO2 |
| * behavior is used: the completion handler will be called, even |
| * if the write is incomplete and data remains in the buffers, or |
| * if the write completed inline. |
| * |
| * @param srcs buffers |
| * @param offset in the buffer array |
| * @param length in the buffer array |
| * @param block is the blocking mode that will be used for this operation |
| * @param timeout timeout duration for the write |
| * @param unit units for the timeout duration |
| * @param attachment an object to attach to the I/O operation that will be |
| * used when calling the completion handler |
| * @param check for the IO operation completion |
| * @param handler to call when the IO is complete |
| * @param <A> The attachment type |
| * @return the completion state (done, done inline, or still pending) |
| */ |
| public final <A> CompletionState write(ByteBuffer[] srcs, int offset, int length, |
| BlockingMode block, long timeout, TimeUnit unit, A attachment, |
| CompletionCheck check, CompletionHandler<Long, ? super A> handler) { |
| return vectoredOperation(false, srcs, offset, length, block, timeout, unit, attachment, check, handler); |
| } |
| |
| |
| /** |
| * Vectored operation. The completion handler will be called once |
| * the operation is complete or an error occurred. If a CompletionCheck |
| * object has been provided, the completion handler will only be |
| * called if the callHandler method returned true. If no |
| * CompletionCheck object has been provided, the default NIO2 |
| * behavior is used: the completion handler will be called, even |
| * if the operation is incomplete, or if the operation completed inline. |
| * |
| * @param read true if the operation is a read, false if it is a write |
| * @param buffers buffers |
| * @param offset in the buffer array |
| * @param length in the buffer array |
| * @param block is the blocking mode that will be used for this operation |
| * @param timeout timeout duration for the write |
| * @param unit units for the timeout duration |
| * @param attachment an object to attach to the I/O operation that will be |
| * used when calling the completion handler |
| * @param check for the IO operation completion |
| * @param handler to call when the IO is complete |
| * @param <A> The attachment type |
| * @return the completion state (done, done inline, or still pending) |
| */ |
| protected final <A> CompletionState vectoredOperation(boolean read, |
| ByteBuffer[] buffers, int offset, int length, |
| BlockingMode block, long timeout, TimeUnit unit, A attachment, |
| CompletionCheck check, CompletionHandler<Long, ? super A> handler) { |
| IOException ioe = getError(); |
| if (ioe != null) { |
| handler.failed(ioe, attachment); |
| return CompletionState.ERROR; |
| } |
| if (timeout == -1) { |
| timeout = AbstractEndpoint.toTimeout(read ? getReadTimeout() : getWriteTimeout()); |
| unit = TimeUnit.MILLISECONDS; |
| } else if (!hasPerOperationTimeout() && (unit.toMillis(timeout) != (read ? getReadTimeout() : getWriteTimeout()))) { |
| if (read) { |
| setReadTimeout(unit.toMillis(timeout)); |
| } else { |
| setWriteTimeout(unit.toMillis(timeout)); |
| } |
| } |
| if (block == BlockingMode.BLOCK || block == BlockingMode.SEMI_BLOCK) { |
| try { |
| if (read ? !readPending.tryAcquire(timeout, unit) : !writePending.tryAcquire(timeout, unit)) { |
| handler.failed(new SocketTimeoutException(), attachment); |
| return CompletionState.ERROR; |
| } |
| } catch (InterruptedException e) { |
| handler.failed(e, attachment); |
| return CompletionState.ERROR; |
| } |
| } else { |
| if (read ? !readPending.tryAcquire() : !writePending.tryAcquire()) { |
| if (block == BlockingMode.NON_BLOCK) { |
| return CompletionState.NOT_DONE; |
| } else { |
| handler.failed(read ? new ReadPendingException() : new WritePendingException(), attachment); |
| return CompletionState.ERROR; |
| } |
| } |
| } |
| VectoredIOCompletionHandler<A> completion = new VectoredIOCompletionHandler<>(); |
| OperationState<A> state = newOperationState(read, buffers, offset, length, block, timeout, unit, |
| attachment, check, handler, read ? readPending : writePending, completion); |
| if (read) { |
| readOperation = state; |
| } else { |
| writeOperation = state; |
| } |
| state.start(); |
| if (block == BlockingMode.BLOCK) { |
| synchronized (state) { |
| if (state.state == CompletionState.PENDING) { |
| try { |
| state.wait(unit.toMillis(timeout)); |
| if (state.state == CompletionState.PENDING) { |
| if (handler != null && state.callHandler.compareAndSet(true, false)) { |
| handler.failed(new SocketTimeoutException(getTimeoutMsg(read)), attachment); |
| } |
| return CompletionState.ERROR; |
| } |
| } catch (InterruptedException e) { |
| if (handler != null && state.callHandler.compareAndSet(true, false)) { |
| handler.failed(new SocketTimeoutException(getTimeoutMsg(read)), attachment); |
| } |
| return CompletionState.ERROR; |
| } |
| } |
| } |
| } |
| return state.state; |
| } |
| |
| |
| private String getTimeoutMsg(boolean read) { |
| if (read) { |
| return sm.getString("socketWrapper.readTimeout"); |
| } else { |
| return sm.getString("socketWrapper.writeTimeout"); |
| } |
| } |
| |
| |
| protected abstract <A> OperationState<A> newOperationState(boolean read, |
| ByteBuffer[] buffers, int offset, int length, |
| BlockingMode block, long timeout, TimeUnit unit, A attachment, |
| CompletionCheck check, CompletionHandler<Long, ? super A> handler, |
| Semaphore semaphore, VectoredIOCompletionHandler<A> completion); |
| |
| |
| // --------------------------------------------------------- Utility methods |
| |
| protected static int transfer(byte[] from, int offset, int length, ByteBuffer to) { |
| int max = Math.min(length, to.remaining()); |
| if (max > 0) { |
| to.put(from, offset, max); |
| } |
| return max; |
| } |
| |
| protected static int transfer(ByteBuffer from, ByteBuffer to) { |
| int max = Math.min(from.remaining(), to.remaining()); |
| if (max > 0) { |
| int fromLimit = from.limit(); |
| from.limit(from.position() + max); |
| to.put(from); |
| from.limit(fromLimit); |
| } |
| return max; |
| } |
| |
| protected static boolean buffersArrayHasRemaining(ByteBuffer[] buffers, int offset, int length) { |
| for (int pos = offset; pos < offset + length; pos++) { |
| if (buffers[pos].hasRemaining()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| // -------------------------------------------------------------- ID methods |
| |
| public ServletConnection getServletConnection(String protocol, String protocolConnectionId) { |
| if (servletConnection == null) { |
| servletConnection = new ServletConnectionImpl( |
| connectionId, protocol, protocolConnectionId, endpoint.isSSLEnabled()); |
| } |
| return servletConnection; |
| } |
| } |