org.apache.tomcat.util.net.AprEndpoint Maven / Gradle / Ivy
The newest version!
/*
* 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.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import javax.net.ssl.KeyManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jni.Address;
import org.apache.tomcat.jni.Error;
import org.apache.tomcat.jni.File;
import org.apache.tomcat.jni.Library;
import org.apache.tomcat.jni.OS;
import org.apache.tomcat.jni.Poll;
import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext;
import org.apache.tomcat.jni.SSLContext.SNICallBack;
import org.apache.tomcat.jni.SSLSocket;
import org.apache.tomcat.jni.Sockaddr;
import org.apache.tomcat.jni.Socket;
import org.apache.tomcat.jni.Status;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.ByteBufferUtils;
import org.apache.tomcat.util.collections.SynchronizedStack;
import org.apache.tomcat.util.compat.JrePlatform;
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
import org.apache.tomcat.util.net.Acceptor.AcceptorState;
import org.apache.tomcat.util.net.openssl.OpenSSLContext;
import org.apache.tomcat.util.net.openssl.OpenSSLUtil;
/**
* APR tailored thread pool, providing the following services:
*
* - Socket acceptor thread
* - Socket poller thread
* - Sendfile thread
* - Worker threads pool
*
*
* @author Mladen Turk
* @author Remy Maucherat
*/
public class AprEndpoint extends AbstractEndpoint implements SNICallBack {
// -------------------------------------------------------------- Constants
private static final Log log = LogFactory.getLog(AprEndpoint.class);
// ----------------------------------------------------------------- Fields
/**
* Root APR memory pool.
*/
protected long rootPool = 0;
/**
* Server socket "pointer".
*/
protected volatile long serverSock = 0;
/**
* APR memory pool for the server socket.
*/
protected long serverSockPool = 0;
/**
* SSL context.
*/
protected long sslContext = 0;
private int previousAcceptedPort = -1;
private String previousAcceptedAddress = null;
private long previousAcceptedSocketNanoTime = 0;
// ------------------------------------------------------------ Constructor
public AprEndpoint() {
// Asynchronous IO has significantly lower performance with APR:
// - no IO vectoring
// - mandatory use of direct buffers forces output buffering
// - needs extra output flushes due to buffering
setUseAsyncIO(false);
}
// ------------------------------------------------------------- Properties
/**
* Defer accept.
*/
protected boolean deferAccept = true;
public void setDeferAccept(boolean deferAccept) { this.deferAccept = deferAccept; }
@Override
public boolean getDeferAccept() { return deferAccept; }
private boolean ipv6v6only = false;
public void setIpv6v6only(boolean ipv6v6only) { this.ipv6v6only = ipv6v6only; }
public boolean getIpv6v6only() { return ipv6v6only; }
/**
* Size of the sendfile (= concurrent files which can be served).
*/
protected int sendfileSize = 1 * 1024;
public void setSendfileSize(int sendfileSize) { this.sendfileSize = sendfileSize; }
public int getSendfileSize() { return sendfileSize; }
/**
* Poll interval, in microseconds. The smaller the value, the more CPU the poller
* will use, but the more responsive to activity it will be.
*/
protected int pollTime = 2000;
public int getPollTime() { return pollTime; }
public void setPollTime(int pollTime) { if (pollTime > 0) { this.pollTime = pollTime; } }
/*
* When the endpoint is created and configured, the APR library will not
* have been initialised. This flag is used to determine if the default
* value of useSendFile should be changed if the APR library indicates it
* supports send file once it has been initialised. If useSendFile is set
* by configuration, that configuration will always take priority.
*/
private boolean useSendFileSet = false;
@Override
public void setUseSendfile(boolean useSendfile) {
useSendFileSet = true;
super.setUseSendfile(useSendfile);
}
/*
* For internal use to avoid setting the useSendFileSet flag
*/
private void setUseSendfileInternal(boolean useSendfile) {
super.setUseSendfile(useSendfile);
}
/**
* The socket poller.
*/
protected Poller poller = null;
public Poller getPoller() {
return poller;
}
/**
* The static file sender.
*/
protected Sendfile sendfile = null;
public Sendfile getSendfile() {
return sendfile;
}
@Override
public InetSocketAddress getLocalAddress() throws IOException {
long s = serverSock;
if (s == 0) {
return null;
} else {
long sa;
try {
sa = Address.get(Socket.APR_LOCAL, s);
} catch (IOException ioe) {
// re-throw
throw ioe;
} catch (Exception e) {
// wrap
throw new IOException(e);
}
Sockaddr addr = Address.getInfo(sa);
if (addr.hostname == null) {
// any local address
if (addr.family == Socket.APR_INET6) {
return new InetSocketAddress("::", addr.port);
} else {
return new InetSocketAddress("0.0.0.0", addr.port);
}
}
return new InetSocketAddress(addr.hostname, addr.port);
}
}
/**
* This endpoint does not support -1
for unlimited connections,
* nor does it support setting this attribute while the endpoint is running.
*
* {@inheritDoc}
*/
@Override
public void setMaxConnections(int maxConnections) {
if (maxConnections == -1) {
log.warn(sm.getString("endpoint.apr.maxConnections.unlimited",
Integer.valueOf(getMaxConnections())));
return;
}
if (running) {
log.warn(sm.getString("endpoint.apr.maxConnections.running",
Integer.valueOf(getMaxConnections())));
return;
}
super.setMaxConnections(maxConnections);
}
/**
* Path for the Unix Domain Socket, used to create the socket address.
*/
private String unixDomainSocketPath = null;
public String getUnixDomainSocketPath() { return this.unixDomainSocketPath; }
public void setUnixDomainSocketPath(String unixDomainSocketPath) {
this.unixDomainSocketPath = unixDomainSocketPath;
}
/**
* Permissions which will be set on the Unix Domain Socket if it is created.
*/
private String unixDomainSocketPathPermissions = null;
public String getUnixDomainSocketPathPermissions() { return this.unixDomainSocketPathPermissions; }
public void setUnixDomainSocketPathPermissions(String unixDomainSocketPathPermissions) {
this.unixDomainSocketPathPermissions = unixDomainSocketPathPermissions;
}
// --------------------------------------------------------- Public Methods
/**
* Obtain the number of kept alive sockets.
*
* @return The number of open sockets currently managed by the Poller
*/
public int getKeepAliveCount() {
if (poller == null) {
return 0;
}
return poller.getConnectionCount();
}
/**
* Obtain the number of sendfile sockets.
*
* @return The number of sockets currently managed by the Sendfile poller.
*/
public int getSendfileCount() {
if (sendfile == null) {
return 0;
}
return sendfile.getSendfileCount();
}
@Override
public String getId() {
if (getUnixDomainSocketPath() != null) {
return getUnixDomainSocketPath();
} else {
return null;
}
}
// ----------------------------------------------- Public Lifecycle Methods
/**
* Initialize the endpoint.
*/
@Override
public void bind() throws Exception {
int family;
String hostname = null;
// Create the root APR memory pool
try {
rootPool = Pool.create(0);
} catch (UnsatisfiedLinkError e) {
throw new Exception(sm.getString("endpoint.init.notavail"));
}
// Create the pool for the server socket
serverSockPool = Pool.create(rootPool);
// Create the APR address that will be bound
if (getUnixDomainSocketPath() != null) {
if (Library.APR_HAVE_UNIX) {
hostname = getUnixDomainSocketPath();
family = Socket.APR_UNIX;
}
else {
throw new Exception(sm.getString("endpoint.init.unixnotavail"));
}
}
else {
if (getAddress() != null) {
hostname = getAddress().getHostAddress();
}
family = Socket.APR_UNSPEC;
}
long sockAddress = Address.info(hostname, family, getPortWithOffset(), 0, rootPool);
// Create the APR server socket
if (family == Socket.APR_UNIX) {
serverSock = Socket.create(family, Socket.SOCK_STREAM, 0, rootPool);
}
else {
int saFamily = Address.getInfo(sockAddress).family;
serverSock = Socket.create(saFamily,
Socket.SOCK_STREAM,
Socket.APR_PROTO_TCP, rootPool);
if (OS.IS_UNIX) {
Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
}
if (Library.APR_HAVE_IPV6 && saFamily == Socket.APR_INET6) {
if (getIpv6v6only()) {
Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 1);
} else {
Socket.optSet(serverSock, Socket.APR_IPV6_V6ONLY, 0);
}
}
// Deal with the firewalls that tend to drop the inactive sockets
Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
}
// Bind the server socket
int ret = Socket.bind(serverSock, sockAddress);
if (ret != 0) {
throw new Exception(sm.getString("endpoint.init.bind", "" + ret, Error.strerror(ret)));
}
// Start listening on the server socket
ret = Socket.listen(serverSock, getAcceptCount());
if (ret != 0) {
throw new Exception(sm.getString("endpoint.init.listen", "" + ret, Error.strerror(ret)));
}
if (family == Socket.APR_UNIX) {
if (getUnixDomainSocketPathPermissions() != null) {
FileAttribute> attrs =
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(
getUnixDomainSocketPathPermissions()));
Path path = Paths.get(getUnixDomainSocketPath());
Files.setAttribute(path, attrs.name(), attrs.value());
}
} else {
if (OS.IS_WIN32 || OS.IS_WIN64) {
// On Windows set the reuseaddr flag after the bind/listen
Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
}
}
// Enable Sendfile by default if it has not been configured but usage on
// systems which don't support it cause major problems
if (!useSendFileSet) {
setUseSendfileInternal(Library.APR_HAS_SENDFILE);
} else if (getUseSendfile() && !Library.APR_HAS_SENDFILE) {
setUseSendfileInternal(false);
}
// Delay accepting of new connections until data is available
// Only Linux kernels 2.4 + have that implemented
// on other platforms this call is noop and will return APR_ENOTIMPL.
if (deferAccept) {
if (Socket.optSet(serverSock, Socket.APR_TCP_DEFER_ACCEPT, 1) == Status.APR_ENOTIMPL) {
deferAccept = false;
}
}
// Initialize SSL if needed
if (isSSLEnabled()) {
for (SSLHostConfig sslHostConfig : sslHostConfigs.values()) {
createSSLContext(sslHostConfig);
}
SSLHostConfig defaultSSLHostConfig = sslHostConfigs.get(getDefaultSSLHostConfigName());
if (defaultSSLHostConfig == null) {
throw new IllegalArgumentException(sm.getString("endpoint.noSslHostConfig",
getDefaultSSLHostConfigName(), getName()));
}
Long defaultSSLContext = defaultSSLHostConfig.getOpenSslContext();
sslContext = defaultSSLContext.longValue();
SSLContext.registerDefault(defaultSSLContext, this);
// For now, sendfile is not supported with SSL
if (getUseSendfile()) {
setUseSendfileInternal(false);
if (useSendFileSet) {
log.warn(sm.getString("endpoint.apr.noSendfileWithSSL"));
}
}
}
}
@Override
protected void createSSLContext(SSLHostConfig sslHostConfig) throws Exception {
OpenSSLContext sslContext = null;
Set certificates = sslHostConfig.getCertificates(true);
for (SSLHostConfigCertificate certificate : certificates) {
if (sslContext == null) {
SSLUtil sslUtil = new OpenSSLUtil(certificate);
sslHostConfig.setEnabledProtocols(sslUtil.getEnabledProtocols());
sslHostConfig.setEnabledCiphers(sslUtil.getEnabledCiphers());
try {
sslContext = (OpenSSLContext) sslUtil.createSSLContext(negotiableProtocols);
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
} else {
SSLUtil sslUtil = new OpenSSLUtil(certificate);
KeyManager[] kms = sslUtil.getKeyManagers();
certificate.setCertificateKeyManager(OpenSSLUtil.chooseKeyManager(kms));
sslContext.addCertificate(certificate);
}
certificate.setSslContext(sslContext);
}
if (certificates.size() > 2) {
// TODO: Can this limitation be removed?
throw new Exception(sm.getString("endpoint.apr.tooManyCertFiles"));
}
}
@Override
public long getSslContext(String sniHostName) {
SSLHostConfig sslHostConfig = getSSLHostConfig(sniHostName);
Long ctx = sslHostConfig.getOpenSslContext();
if (ctx != null) {
return ctx.longValue();
}
// Default
return 0;
}
@Override
public boolean isAlpnSupported() {
// The APR/native connector always supports ALPN if TLS is in use
// because OpenSSL supports ALPN. Therefore, this is equivalent to
// testing of SSL is enabled.
return isSSLEnabled();
}
/**
* Start the APR endpoint, creating acceptor, poller and sendfile threads.
*/
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
// Create worker collection
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// Start poller thread
poller = new Poller();
poller.init();
poller.start();
// Start sendfile thread
if (getUseSendfile()) {
sendfile = new Sendfile();
sendfile.init();
sendfile.start();
}
startAcceptorThread();
}
}
/**
* Stop the endpoint. This will cause all processing threads to stop.
*/
@Override
public void stopInternal() {
if (!paused) {
pause();
}
if (running) {
running = false;
// Stop new connections being accepted.
acceptor.stop(10);
// Stop the Poller calling select
poller.stop();
if (getUseSendfile()) {
sendfile.stop();
}
// Wait for the acceptor to shutdown
if (acceptor.getState() != AcceptorState.ENDED && !getBindOnInit()) {
log.warn(sm.getString("endpoint.warn.unlockAcceptorFailed", acceptor.getThreadName()));
// If the Acceptor is still running force
// the hard socket close.
if (serverSock != 0) {
Socket.shutdown(serverSock, Socket.APR_SHUTDOWN_READ);
serverSock = 0;
}
}
// Wait for Poller to stop
int waitMillis = 0;
try {
while (poller.pollerThread.isAlive() && waitMillis < 10000) {
waitMillis++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
// Ignore
}
if (getUseSendfile()) {
try {
// Wait for the sendfile thread to exit, otherwise parallel
// destruction of sockets which are still in the poller can cause
// problems.
waitMillis = 0;
try {
while (sendfile.sendfileThread.isAlive() && waitMillis < 10000) {
waitMillis++;
Thread.sleep(1);
}
} catch (InterruptedException e) {
// Ignore
}
if (sendfile.sendfileThread.isAlive()) {
log.warn(sm.getString("endpoint.sendfileThreadStop"));
}
} catch (Exception e) {
// Ignore
}
}
// Close the SocketWrapper for each open connection - this should
// trigger a IOException when the app (or container) tries to write.
// Use the blocking status write lock as a proxy for a lock on
// writing to the socket. Don't want to close it while another
// thread is writing as that could trigger a JVM crash.
for (SocketWrapperBase socketWrapper : connections.values()) {
WriteLock wl = ((AprSocketWrapper) socketWrapper).getBlockingStatusWriteLock();
wl.lock();
try {
socketWrapper.close();
} finally {
wl.unlock();
}
}
for (Long socket : connections.keySet()) {
// Close the APR Socket. Need to do this before destroying the
// poller since that will also destroy the root pool for these
// sockets.
Socket.shutdown(socket.longValue(), Socket.APR_SHUTDOWN_READWRITE);
}
if (getUseSendfile()) {
try {
sendfile.destroy();
} catch (Exception e) {
// Ignore
}
sendfile = null;
}
try {
poller.destroy();
} catch (Exception e) {
// Ignore
}
poller = null;
connections.clear();
if (processorCache != null) {
processorCache.clear();
processorCache = null;
}
}
shutdownExecutor();
}
/**
* Deallocate APR memory pools, and close server socket.
*/
@Override
public void unbind() throws Exception {
if (running) {
stop();
}
// Destroy pool if it was initialised
if (serverSockPool != 0) {
Pool.destroy(serverSockPool);
serverSockPool = 0;
}
doCloseServerSocket();
destroySsl();
// Close all APR memory pools and resources if initialised
if (rootPool != 0) {
Pool.destroy(rootPool);
rootPool = 0;
}
getHandler().recycle();
}
@Override
protected void doCloseServerSocket() {
// Close server socket if it was initialised
if (serverSock != 0) {
Socket.close(serverSock);
serverSock = 0;
}
}
// ------------------------------------------------------ Protected Methods
/**
* Process the specified connection.
* @param socketWrapper The socket wrapper
* @return true
if the socket was correctly configured
* and processing may continue, false
if the socket needs to be
* close immediately
*/
protected boolean setSocketOptions(SocketWrapperBase socketWrapper) {
long socket = socketWrapper.getSocket().longValue();
// Process the connection
int step = 1;
try {
// 1: Set socket options: timeout, linger, etc
if (socketProperties.getSoLingerOn() && socketProperties.getSoLingerTime() >= 0) {
Socket.optSet(socket, Socket.APR_SO_LINGER, socketProperties.getSoLingerTime());
}
if (socketProperties.getTcpNoDelay()) {
Socket.optSet(socket, Socket.APR_TCP_NODELAY, (socketProperties.getTcpNoDelay() ? 1 : 0));
}
Socket.timeoutSet(socket, socketProperties.getSoTimeout() * 1000);
// 2: SSL handshake
step = 2;
if (sslContext != 0) {
int rv = SSLSocket.attach(sslContext, socket);
if (rv != Status.APR_SUCCESS) {
log.warn(sm.getString("endpoint.err.attach", Integer.valueOf(rv)));
return false;
}
// Need to make sure the socket doesn't get closed while the
// handshake is in progress as that could trigger a JVM crash.
// Like stopInternal(), use the blocking status write lock as a
// proxy for a lock on writing to the socket.
WriteLock wl = ((AprSocketWrapper) socketWrapper).getBlockingStatusWriteLock();
wl.lock();
try {
if (SSLSocket.handshake(socket) != 0) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.err.handshake") + ": " + SSL.getLastError());
}
return false;
}
} finally {
wl.unlock();
}
if (negotiableProtocols.size() > 0) {
byte[] negotiated = new byte[256];
int len = SSLSocket.getALPN(socket, negotiated);
String negotiatedProtocol =
new String(negotiated, 0, len, StandardCharsets.UTF_8);
if (negotiatedProtocol.length() > 0) {
socketWrapper.setNegotiatedProtocol(negotiatedProtocol);
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.alpn.negotiated", negotiatedProtocol));
}
}
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (log.isDebugEnabled()) {
if (step == 2) {
log.debug(sm.getString("endpoint.err.handshake"), t);
} else {
log.debug(sm.getString("endpoint.err.unexpected"), t);
}
}
// Tell to close the socket
return false;
}
return true;
}
/**
* Allocate a new poller of the specified size.
* @param size The size
* @param pool The pool from which the poller will be allocated
* @param timeout The timeout
* @return the poller pointer
*/
protected long allocatePoller(int size, long pool, int timeout) {
try {
return Poll.create(size, pool, 0, timeout * 1000);
} catch (Error e) {
if (Status.APR_STATUS_IS_EINVAL(e.getError())) {
log.info(sm.getString("endpoint.poll.limitedpollsize", "" + size));
throw new RuntimeException(e);
} else {
log.error(sm.getString("endpoint.poll.initfail"), e);
throw new RuntimeException(e);
}
}
}
/**
* Process given socket. This is called when the socket has been
* accepted.
* @param socket The socket
* @return true
if the socket was correctly configured
* and processing may continue, false
if the socket needs to be
* close immediately
*/
@Override
protected boolean setSocketOptions(Long socket) {
try {
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.debug.socket", socket));
}
// Do the duplicate accept check here rather than in serverSocketaccept()
// so we can cache the results in the SocketWrapper
AprSocketWrapper wrapper = new AprSocketWrapper(socket, this);
// Bug does not affect Windows. Skip the check on that platform.
if (!JrePlatform.IS_WINDOWS) {
long currentNanoTime = System.nanoTime();
if (wrapper.getRemotePort() == previousAcceptedPort) {
if (wrapper.getRemoteAddr().equals(previousAcceptedAddress)) {
if (currentNanoTime - previousAcceptedSocketNanoTime < 1000) {
throw new IOException(sm.getString("endpoint.err.duplicateAccept"));
}
}
}
previousAcceptedPort = wrapper.getRemotePort();
previousAcceptedAddress = wrapper.getRemoteAddr();
previousAcceptedSocketNanoTime = currentNanoTime;
}
connections.put(socket, wrapper);
wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
wrapper.setReadTimeout(getConnectionTimeout());
wrapper.setWriteTimeout(getConnectionTimeout());
getExecutor().execute(new SocketWithOptionsProcessor(wrapper));
return true;
} catch (RejectedExecutionException x) {
log.warn(sm.getString("endpoint.rejectedExecution", socket), x);
} 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;
}
@Override
protected Long serverSocketAccept() throws Exception {
// See setSocketOptions(Long) for duplicate accept check
long socket = Socket.accept(serverSock);
if (socket == 0) {
throw new IOException(sm.getString("endpoint.err.accept", getName()));
}
if (log.isDebugEnabled()) {
long sa = Address.get(Socket.APR_REMOTE, socket);
Sockaddr addr = Address.getInfo(sa);
log.debug(sm.getString("endpoint.apr.remoteport",
Long.valueOf(socket),
Long.valueOf(addr.port)));
}
return Long.valueOf(socket);
}
/**
* Process the given socket. Typically keep alive or upgraded protocol.
*
* @param socket The socket to process
* @param event The event to process
*
* @return true
if the processing completed normally otherwise
* false
which indicates an error occurred and that the
* socket should be closed
*/
protected boolean processSocket(long socket, SocketEvent event) {
SocketWrapperBase socketWrapper = connections.get(Long.valueOf(socket));
if (socketWrapper == null) {
// Socket probably closed from another thread. Triggering another
// close in case won't cause an issue.
return false;
}
if (event == SocketEvent.OPEN_READ && socketWrapper.readOperation != null) {
return socketWrapper.readOperation.process();
} else if (event == SocketEvent.OPEN_WRITE && socketWrapper.writeOperation != null) {
return socketWrapper.writeOperation.process();
} else {
return processSocket(socketWrapper, event, true);
}
}
@Override
protected SocketProcessorBase createSocketProcessor(
SocketWrapperBase socketWrapper, SocketEvent event) {
return new SocketProcessor(socketWrapper, event);
}
private void closeSocketInternal(long socket) {
closeSocket(Long.valueOf(socket));
}
@Override
protected void destroySocket(Long socket) {
countDownConnection();
destroySocketInternal(socket.longValue());
}
private void destroySocketInternal(long socket) {
if (log.isDebugEnabled()) {
String msg = sm.getString("endpoint.debug.destroySocket", Long.valueOf(socket));
if (log.isTraceEnabled()) {
log.trace(msg, new Exception());
} else {
log.debug(msg);
}
}
// Be VERY careful if you call this method directly. If it is called
// twice for the same socket the JVM will core. Currently this is only
// called from Poller.closePollset() to ensure kept alive connections
// are closed when calling stop() followed by start().
if (socket != 0) {
Socket.destroy(socket);
}
}
@Override
protected Log getLog() {
return log;
}
// -------------------------------------------------- SocketInfo Inner Class
public static class SocketInfo {
public long socket;
public long timeout;
public int flags;
public boolean read() {
return (flags & Poll.APR_POLLIN) == Poll.APR_POLLIN;
}
public boolean write() {
return (flags & Poll.APR_POLLOUT) == Poll.APR_POLLOUT;
}
public static int merge(int flag1, int flag2) {
return ((flag1 & Poll.APR_POLLIN) | (flag2 & Poll.APR_POLLIN))
| ((flag1 & Poll.APR_POLLOUT) | (flag2 & Poll.APR_POLLOUT));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Socket: [");
sb.append(socket);
sb.append("], timeout: [");
sb.append(timeout);
sb.append("], flags: [");
sb.append(flags);
return sb.toString();
}
}
// ---------------------------------------------- SocketTimeouts Inner Class
public static class SocketTimeouts {
protected int size;
protected long[] sockets;
protected long[] timeouts;
protected int pos = 0;
public SocketTimeouts(int size) {
this.size = 0;
sockets = new long[size];
timeouts = new long[size];
}
public void add(long socket, long timeout) {
sockets[size] = socket;
timeouts[size] = timeout;
size++;
}
/**
* Removes the specified socket from the poller.
*
* @param socket The socket to remove
*
* @return The configured timeout for the socket or zero if the socket
* was not in the list of socket timeouts
*/
public long remove(long socket) {
long result = 0;
for (int i = 0; i < size; i++) {
if (sockets[i] == socket) {
result = timeouts[i];
sockets[i] = sockets[size - 1];
timeouts[i] = timeouts[size - 1];
size--;
break;
}
}
return result;
}
public long check(long date) {
while (pos < size) {
if (date >= timeouts[pos]) {
long result = sockets[pos];
sockets[pos] = sockets[size - 1];
timeouts[pos] = timeouts[size - 1];
size--;
return result;
}
pos++;
}
pos = 0;
return 0;
}
}
// -------------------------------------------------- SocketList Inner Class
public static class SocketList {
protected volatile int size;
protected int pos;
protected long[] sockets;
protected long[] timeouts;
protected int[] flags;
protected SocketInfo info = new SocketInfo();
public SocketList(int size) {
this.size = 0;
pos = 0;
sockets = new long[size];
timeouts = new long[size];
flags = new int[size];
}
public int size() {
return this.size;
}
public SocketInfo get() {
if (pos == size) {
return null;
} else {
info.socket = sockets[pos];
info.timeout = timeouts[pos];
info.flags = flags[pos];
pos++;
return info;
}
}
public void clear() {
size = 0;
pos = 0;
}
public boolean add(long socket, long timeout, int flag) {
if (size == sockets.length) {
return false;
} else {
for (int i = 0; i < size; i++) {
if (sockets[i] == socket) {
flags[i] = SocketInfo.merge(flags[i], flag);
return true;
}
}
sockets[size] = socket;
timeouts[size] = timeout;
flags[size] = flag;
size++;
return true;
}
}
public boolean remove(long socket) {
for (int i = 0; i < size; i++) {
if (sockets[i] == socket) {
sockets[i] = sockets[size - 1];
timeouts[i] = timeouts[size - 1];
flags[size] = flags[size -1];
size--;
return true;
}
}
return false;
}
public void duplicate(SocketList copy) {
copy.size = size;
copy.pos = pos;
System.arraycopy(sockets, 0, copy.sockets, 0, size);
System.arraycopy(timeouts, 0, copy.timeouts, 0, size);
System.arraycopy(flags, 0, copy.flags, 0, size);
}
}
// ------------------------------------------------------ Poller Inner Class
public class Poller implements Runnable {
/**
* Pointer to the poller.
*/
private long aprPoller;
/**
* Actual poller size.
*/
private int pollerSize = 0;
/**
* Root pool.
*/
private long pool = 0;
/**
* Socket descriptors.
*/
private long[] desc;
/**
* List of sockets to be added to the poller.
*/
private SocketList addList = null; // Modifications guarded by this
/**
* List of sockets to be closed.
*/
private SocketList closeList = null; // Modifications guarded by this
/**
* Structure used for storing timeouts.
*/
private SocketTimeouts timeouts = null;
/**
* Last run of maintain. Maintain will run approximately once every one
* second (may be slightly longer between runs).
*/
private long lastMaintain = System.currentTimeMillis();
/**
* The number of connections currently inside this Poller. The correct
* operation of the Poller depends on this figure being correct. If it
* is not, it is possible that the Poller will enter a wait loop where
* it waits for the next connection to be added to the Poller before it
* calls poll when it should still be polling existing connections.
* Although not necessary at the time of writing this comment, it has
* been implemented as an AtomicInteger to ensure that it remains
* thread-safe.
*/
private AtomicInteger connectionCount = new AtomicInteger(0);
public int getConnectionCount() { return connectionCount.get(); }
private volatile Thread pollerThread;
private volatile boolean pollerRunning = true;
/**
* Create the poller.
*/
protected synchronized void init() {
pool = Pool.create(serverSockPool);
pollerSize = getMaxConnections();
timeouts = new SocketTimeouts(pollerSize);
// At the moment, setting the timeout is useless, but it could get
// used again as the normal poller could be faster using maintain.
// It might not be worth bothering though.
aprPoller = allocatePoller(pollerSize, pool, -1);
/*
* x2 - One descriptor for the socket, one for the event(s).
* x2 - Some APR implementations return multiple events for the
* same socket as different entries. Each socket is registered
* for a maximum of two events (read and write) at any one
* time.
*
* Therefore size is poller size *4.
*/
desc = new long[pollerSize * 4];
connectionCount.set(0);
addList = new SocketList(pollerSize);
closeList = new SocketList(pollerSize);
}
protected void start() {
pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
/*
* This method is synchronized so that it is not possible for a socket
* to be added to the Poller's addList once this method has completed.
*/
protected synchronized void stop() {
pollerRunning = false;
// In case the poller thread is in the idle wait
this.notify();
}
/**
* Destroy the poller.
*/
protected synchronized void destroy() {
// Wait for the poller thread to exit, otherwise parallel
// destruction of sockets which are still in the poller can cause
// problems.
int loops = 50;
while (loops > 0 && pollerThread.isAlive()) {
try {
this.wait(pollTime / 1000);
} catch (InterruptedException e) {
// Ignore
}
loops--;
}
if (pollerThread.isAlive()) {
log.warn(sm.getString("endpoint.pollerThreadStop"));
}
// Close all sockets in the close queue
SocketInfo info = closeList.get();
while (info != null) {
// Make sure we aren't trying add the socket as well as close it
addList.remove(info.socket);
// Make sure the socket isn't in the poller before we close it
removeFromPoller(info.socket);
// Poller isn't running at this point so use destroySocket()
// directly
closeSocketInternal(info.socket);
destroySocketInternal(info.socket);
info = closeList.get();
}
closeList.clear();
// Close all sockets in the add queue
info = addList.get();
while (info != null) {
// Make sure the socket isn't in the poller before we close it
removeFromPoller(info.socket);
// Poller isn't running at this point so use destroySocket()
// directly
closeSocketInternal(info.socket);
destroySocketInternal(info.socket);
info = addList.get();
}
addList.clear();
// Close all sockets still in the poller
int rv = Poll.pollset(aprPoller, desc);
if (rv > 0) {
for (int n = 0; n < rv; n++) {
closeSocketInternal(desc[n*2+1]);
destroySocketInternal(desc[n*2+1]);
}
}
Pool.destroy(pool);
connectionCount.set(0);
}
/**
* Add specified socket and associated pool to the poller. The socket
* will be added to a temporary array, and polled first after a maximum
* amount of time equal to pollTime (in most cases, latency will be much
* lower, however). Note: If both read and write are false, the socket
* will only be checked for timeout; if the socket was already present
* in the poller, a callback event will be generated and the socket will
* be removed from the poller.
*
* @param socket to add to the poller
* @param timeout to use for this connection in milliseconds
* @param flags Events to poll for (Poll.APR_POLLIN and/or
* Poll.APR_POLLOUT)
*/
private void add(long socket, long timeout, int flags) {
if (log.isDebugEnabled()) {
String msg = sm.getString("endpoint.debug.pollerAdd",
Long.valueOf(socket), Long.valueOf(timeout),
Integer.valueOf(flags));
if (log.isTraceEnabled()) {
log.trace(msg, new Exception());
} else {
log.debug(msg);
}
}
if (timeout <= 0) {
// Always put a timeout in
timeout = Integer.MAX_VALUE;
}
synchronized (this) {
// Add socket to the list. Newly added sockets will wait
// at most for pollTime before being polled.
if (addList.add(socket, timeout, flags)) {
// In case the poller thread is in the idle wait
this.notify();
}
}
}
/**
* Add specified socket to one of the pollers. Must only be called from
* {@link Poller#run()}.
*/
private boolean addToPoller(long socket, int events) {
int rv = Poll.add(aprPoller, socket, events);
if (rv == Status.APR_SUCCESS) {
connectionCount.incrementAndGet();
return true;
}
return false;
}
/*
* This is only called from the SocketWrapper to ensure that it is only
* called once per socket. Calling it more than once typically results
* in the JVM crash.
*/
private synchronized void close(long socket) {
closeList.add(socket, 0, 0);
// In case the poller thread is in the idle wait
this.notify();
}
/**
* Remove specified socket from the pollers. Must only be called from
* {@link Poller#run()}.
*/
private void removeFromPoller(long socket) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.debug.pollerRemove",
Long.valueOf(socket)));
}
int rv = Poll.remove(aprPoller, socket);
if (rv != Status.APR_NOTFOUND) {
connectionCount.decrementAndGet();
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.debug.pollerRemoved",
Long.valueOf(socket)));
}
}
timeouts.remove(socket);
}
/**
* Timeout checks. Must only be called from {@link Poller#run()}.
*/
private synchronized void maintain() {
long date = System.currentTimeMillis();
// Maintain runs at most once every 1s, although it will likely get
// called more
if ((date - lastMaintain) < 1000L) {
return;
} else {
lastMaintain = date;
}
long socket = timeouts.check(date);
while (socket != 0) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.debug.socketTimeout",
Long.valueOf(socket)));
}
SocketWrapperBase socketWrapper = connections.get(Long.valueOf(socket));
if (socketWrapper != null) {
socketWrapper.setError(new SocketTimeoutException());
if (socketWrapper.readOperation != null || socketWrapper.writeOperation != null) {
if (socketWrapper.readOperation != null) {
socketWrapper.readOperation.process();
} else {
socketWrapper.writeOperation.process();
}
} else {
processSocket(socketWrapper, SocketEvent.ERROR, true);
}
}
socket = timeouts.check(date);
}
}
/**
* Displays the list of sockets in the pollers.
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("Poller");
long[] res = new long[pollerSize * 2];
int count = Poll.pollset(aprPoller, res);
buf.append(" [ ");
for (int j = 0; j < count; j++) {
buf.append(desc[2*j+1]).append(' ');
}
buf.append(']');
return buf.toString();
}
/**
* The background thread that adds sockets to the Poller, checks the
* poller for triggered events and hands the associated socket off to an
* appropriate processor as events occur.
*/
@Override
public void run() {
SocketList localAddList = new SocketList(getMaxConnections());
SocketList localCloseList = new SocketList(getMaxConnections());
// Loop until we receive a shutdown command
while (pollerRunning) {
// Check timeouts if the poller is empty.
while (pollerRunning && connectionCount.get() < 1 &&
addList.size() < 1 && closeList.size() < 1) {
try {
if (getConnectionTimeout() > 0 && pollerRunning) {
maintain();
}
synchronized (this) {
// Make sure that no sockets have been placed in the
// addList or closeList since the check above.
// Without this check there could be a 10s pause
// with no processing since the notify() call in
// add()/close() would have no effect since it
// happened before this sync block was entered
if (pollerRunning && addList.size() < 1 && closeList.size() < 1) {
this.wait(10000);
}
}
} catch (InterruptedException e) {
// Ignore
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().warn(sm.getString("endpoint.timeout.err"));
}
}
// Don't add or poll if the poller has been stopped
if (!pollerRunning) {
break;
}
try {
// Duplicate the add and remove lists so that the syncs are
// minimised
synchronized (this) {
if (closeList.size() > 0) {
// Duplicate to another list, so that the syncing is
// minimal
closeList.duplicate(localCloseList);
closeList.clear();
} else {
localCloseList.clear();
}
}
synchronized (this) {
if (addList.size() > 0) {
// Duplicate to another list, so that the syncing is
// minimal
addList.duplicate(localAddList);
addList.clear();
} else {
localAddList.clear();
}
}
// Remove sockets
if (localCloseList.size() > 0) {
SocketInfo info = localCloseList.get();
while (info != null) {
localAddList.remove(info.socket);
removeFromPoller(info.socket);
closeSocketInternal(info.socket);
destroySocketInternal(info.socket);
info = localCloseList.get();
}
}
// Add sockets which are waiting to the poller
if (localAddList.size() > 0) {
SocketInfo info = localAddList.get();
while (info != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"endpoint.debug.pollerAddDo",
Long.valueOf(info.socket)));
}
timeouts.remove(info.socket);
AprSocketWrapper wrapper =
(AprSocketWrapper) connections.get(Long.valueOf(info.socket));
if (wrapper != null) {
if (info.read() || info.write()) {
wrapper.pollerFlags = wrapper.pollerFlags |
(info.read() ? Poll.APR_POLLIN : 0) |
(info.write() ? Poll.APR_POLLOUT : 0);
// A socket can only be added to the poller
// once. Adding it twice will return an error
// which will close the socket. Therefore make
// sure the socket we are about to add isn't in
// the poller.
removeFromPoller(info.socket);
if (!addToPoller(info.socket, wrapper.pollerFlags)) {
wrapper.close();
} else {
timeouts.add(info.socket,
System.currentTimeMillis() +
info.timeout);
}
} else {
// Should never happen.
wrapper.close();
getLog().warn(sm.getString(
"endpoint.apr.pollAddInvalid", info));
}
}
info = localAddList.get();
}
}
// Flag to ask to reallocate the pool
boolean reset = false;
int rv = Poll.poll(aprPoller, pollTime, desc, true);
if (rv > 0) {
rv = mergeDescriptors(desc, rv);
connectionCount.addAndGet(-rv);
for (int n = 0; n < rv; n++) {
if (getLog().isDebugEnabled()) {
log.debug(sm.getString(
"endpoint.debug.pollerProcess",
Long.valueOf(desc[n*2+1]),
Long.valueOf(desc[n*2])));
}
long timeout = timeouts.remove(desc[n*2+1]);
AprSocketWrapper wrapper = (AprSocketWrapper)
connections.get(Long.valueOf(desc[n*2+1]));
if (wrapper == null) {
// Socket was closed in another thread while still in
// the Poller but wasn't removed from the Poller before
// new data arrived.
continue;
}
wrapper.pollerFlags = wrapper.pollerFlags & ~((int) desc[n*2]);
// Check for failed sockets and hand this socket off to a worker
if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
|| ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)
|| ((desc[n*2] & Poll.APR_POLLNVAL) == Poll.APR_POLLNVAL)) {
// Need to trigger error handling. Poller may return error
// codes plus the flags it was waiting for or it may just
// return an error code. We could handle the error here but
// if we do, there will be no exception associated with the
// error in application code. By signalling read/write is
// possible, a read/write will be attempted, fail and that
// will trigger an exception the application will see.
// Check the return flags first, followed by what the socket
// was registered for
if ((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) {
// Error probably occurred during a non-blocking read
if (!processSocket(desc[n*2+1], SocketEvent.OPEN_READ)) {
// Close socket and clear pool
wrapper.close();
}
} else if ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) {
// Error probably occurred during a non-blocking write
if (!processSocket(desc[n*2+1], SocketEvent.OPEN_WRITE)) {
// Close socket and clear pool
wrapper.close();
}
} else if ((wrapper.pollerFlags & Poll.APR_POLLIN) == Poll.APR_POLLIN) {
// Can't tell what was happening when the error occurred but the
// socket is registered for non-blocking read so use that
if (!processSocket(desc[n*2+1], SocketEvent.OPEN_READ)) {
// Close socket and clear pool
wrapper.close();
}
} else if ((wrapper.pollerFlags & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) {
// Can't tell what was happening when the error occurred but the
// socket is registered for non-blocking write so use that
if (!processSocket(desc[n*2+1], SocketEvent.OPEN_WRITE)) {
// Close socket and clear pool
wrapper.close();
}
} else {
// Close socket and clear pool
wrapper.close();
}
} else if (((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN)
|| ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT)) {
boolean error = false;
if (((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) &&
!processSocket(desc[n*2+1], SocketEvent.OPEN_READ)) {
error = true;
// Close socket and clear pool
wrapper.close();
}
if (!error &&
((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) &&
!processSocket(desc[n*2+1], SocketEvent.OPEN_WRITE)) {
// Close socket and clear pool
error = true;
wrapper.close();
}
if (!error && wrapper.pollerFlags != 0) {
// If socket was registered for multiple events but
// only some of the occurred, re-register for the
// remaining events.
// timeout is the value of System.currentTimeMillis() that
// was set as the point that the socket will timeout. When
// adding to the poller, the timeout from now in
// milliseconds is required.
// So first, subtract the current timestamp
if (timeout > 0) {
timeout = timeout - System.currentTimeMillis();
}
// If the socket should have already expired by now,
// re-add it with a very short timeout
if (timeout <= 0) {
timeout = 1;
}
// Should be impossible but just in case since timeout will
// be cast to an int.
if (timeout > Integer.MAX_VALUE) {
timeout = Integer.MAX_VALUE;
}
add(desc[n*2+1], (int) timeout, wrapper.pollerFlags);
}
} else {
// Unknown event
getLog().warn(sm.getString(
"endpoint.apr.pollUnknownEvent",
Long.valueOf(desc[n*2])));
// Close socket and clear pool
wrapper.close();
}
}
} else if (rv < 0) {
int errn = -rv;
// Any non timeup or interrupted error is critical
if ((errn != Status.TIMEUP) && (errn != Status.EINTR)) {
if (errn > Status.APR_OS_START_USERERR) {
errn -= Status.APR_OS_START_USERERR;
}
getLog().error(sm.getString(
"endpoint.apr.pollError",
Integer.valueOf(errn),
Error.strerror(errn)));
// Destroy and reallocate the poller
reset = true;
}
}
if (reset && pollerRunning) {
// Reallocate the current poller
int count = Poll.pollset(aprPoller, desc);
long newPoller = allocatePoller(pollerSize, pool, -1);
// Don't restore connections for now, since I have not tested it
connectionCount.addAndGet(-count);
Poll.destroy(aprPoller);
aprPoller = newPoller;
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().warn(sm.getString("endpoint.poll.error"), t);
}
try {
// Process socket timeouts
if (getConnectionTimeout() > 0 && pollerRunning) {
// This works and uses only one timeout mechanism for everything, but the
// non event poller might be a bit faster by using the old maintain.
maintain();
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().warn(sm.getString("endpoint.timeout.err"), t);
}
}
synchronized (this) {
this.notifyAll();
}
}
private int mergeDescriptors(long[] desc, int startCount) {
/*
* https://bz.apache.org/bugzilla/show_bug.cgi?id=57653#c6 suggests
* this merging is only necessary on OSX and BSD.
*
* https://bz.apache.org/bugzilla/show_bug.cgi?id=56313 suggests the
* same, or a similar, issue is happening on Windows.
* Notes: Only the first startCount * 2 elements of the array
* are populated.
* The array is event, socket, event, socket etc.
*/
Map merged = new HashMap<>(startCount);
for (int n = 0; n < startCount; n++) {
Long newValue = merged.merge(Long.valueOf(desc[2*n+1]), Long.valueOf(desc[2*n]),
(v1, v2) -> Long.valueOf(v1.longValue() | v2.longValue()));
if (log.isDebugEnabled()) {
if (newValue.longValue() != desc[2*n]) {
log.debug(sm.getString("endpoint.apr.pollMergeEvents",
Long.valueOf(desc[2*n+1]), Long.valueOf(desc[2*n]), newValue));
}
}
}
int i = 0;
for (Map.Entry entry : merged.entrySet()) {
desc[i++] = entry.getValue().longValue();
desc[i++] = entry.getKey().longValue();
}
return merged.size();
}
}
// ----------------------------------------------- SendfileData Inner Class
/**
* SendfileData class.
*/
public static class SendfileData extends SendfileDataBase {
// File
protected long fd;
protected long fdpool;
// Socket and socket pool
protected long socket;
public SendfileData(String filename, long pos, long length) {
super(filename, pos, length);
}
}
// --------------------------------------------------- Sendfile Inner Class
public class Sendfile implements Runnable {
protected long sendfilePollset = 0;
protected long pool = 0;
protected long[] desc;
protected HashMap sendfileData;
protected int sendfileCount;
public int getSendfileCount() { return sendfileCount; }
protected ArrayList addS;
private volatile Thread sendfileThread;
private volatile boolean sendfileRunning = true;
/**
* Create the sendfile poller.
*/
protected void init() {
pool = Pool.create(serverSockPool);
int size = sendfileSize;
if (size <= 0) {
size = 16 * 1024;
}
sendfilePollset = allocatePoller(size, pool, getConnectionTimeout());
desc = new long[size * 2];
sendfileData = new HashMap<>(size);
addS = new ArrayList<>();
}
protected void start() {
sendfileThread = new Thread(sendfile, getName() + "-Sendfile");
sendfileThread.setPriority(threadPriority);
sendfileThread.setDaemon(true);
sendfileThread.start();
}
protected synchronized void stop() {
sendfileRunning = false;
// In case the sendfile thread is in the idle wait
this.notify();
}
/**
* Destroy the poller.
*/
protected void destroy() {
// Close any socket remaining in the add queue
for (int i = (addS.size() - 1); i >= 0; i--) {
SendfileData data = addS.get(i);
closeSocketInternal(data.socket);
}
// Close all sockets still in the poller
int rv = Poll.pollset(sendfilePollset, desc);
if (rv > 0) {
for (int n = 0; n < rv; n++) {
closeSocketInternal(desc[n*2+1]);
}
}
Pool.destroy(pool);
sendfileData.clear();
}
/**
* Add the sendfile data to the sendfile poller. Note that in most cases,
* the initial non blocking calls to sendfile will return right away, and
* will be handled asynchronously inside the kernel. As a result,
* the poller will never be used.
*
* @param data containing the reference to the data which should be sent
* @return true if all the data has been sent right away, and false
* otherwise
*/
public SendfileState add(SendfileData data) {
SocketWrapperBase socketWrapper = connections.get(Long.valueOf(data.socket));
// Use the blocking status write lock as a proxy for a lock on
// writing to the socket. Don't want it to close in another thread
// while this thread is writing as that could trigger a JVM crash.
WriteLock wl = ((AprSocketWrapper) socketWrapper).getBlockingStatusWriteLock();
wl.lock();
// Initialize fd from data given
try {
data.fdpool = Socket.pool(data.socket);
data.fd = File.open
(data.fileName, File.APR_FOPEN_READ
| File.APR_FOPEN_SENDFILE_ENABLED | File.APR_FOPEN_BINARY,
0, data.fdpool);
// Set the socket to nonblocking mode
Socket.timeoutSet(data.socket, 0);
while (sendfileRunning) {
long nw = Socket.sendfilen(data.socket, data.fd,
data.pos, data.length, 0);
if (nw < 0) {
if (!(-nw == Status.EAGAIN)) {
Pool.destroy(data.fdpool);
data.socket = 0;
return SendfileState.ERROR;
} else {
// Break the loop and add the socket to poller.
break;
}
} else {
data.pos += nw;
data.length -= nw;
if (data.length == 0) {
// Entire file has been sent
Pool.destroy(data.fdpool);
// Set back socket to blocking mode
Socket.timeoutSet(data.socket, getConnectionTimeout() * 1000);
return SendfileState.DONE;
}
}
}
} catch (Exception e) {
log.warn(sm.getString("endpoint.sendfile.error"), e);
return SendfileState.ERROR;
} finally {
wl.unlock();
}
// Add socket to the list. Newly added sockets will wait
// at most for pollTime before being polled
synchronized (this) {
addS.add(data);
this.notify();
}
return SendfileState.PENDING;
}
/**
* Remove socket from the poller.
*
* @param data the sendfile data which should be removed
*/
protected void remove(SendfileData data) {
int rv = Poll.remove(sendfilePollset, data.socket);
if (rv == Status.APR_SUCCESS) {
sendfileCount--;
}
sendfileData.remove(Long.valueOf(data.socket));
}
/**
* The background thread that listens for incoming TCP/IP connections
* and hands them off to an appropriate processor.
*/
@Override
public void run() {
long maintainTime = 0;
// Loop until we receive a shutdown command
while (sendfileRunning) {
// Loop if poller is empty
while (sendfileRunning && sendfileCount < 1 && addS.size() < 1) {
// Reset maintain time.
maintainTime = 0;
try {
synchronized (this) {
if (sendfileRunning && sendfileCount < 1 && addS.size() < 1) {
this.wait();
}
}
} catch (InterruptedException e) {
// Ignore
}
}
// Don't add or poll if the poller has been stopped
if (!sendfileRunning) {
break;
}
try {
// Add socket to the poller
if (addS.size() > 0) {
synchronized (this) {
for (int i = (addS.size() - 1); i >= 0; i--) {
SendfileData data = addS.get(i);
int rv = Poll.add(sendfilePollset, data.socket, Poll.APR_POLLOUT);
if (rv == Status.APR_SUCCESS) {
sendfileData.put(Long.valueOf(data.socket), data);
sendfileCount++;
} else {
getLog().warn(sm.getString(
"endpoint.sendfile.addfail",
Integer.valueOf(rv),
Error.strerror(rv)));
// Can't do anything: close the socket right away
closeSocketInternal(data.socket);
}
}
addS.clear();
}
}
maintainTime += pollTime;
// Pool for the specified interval
int rv = Poll.poll(sendfilePollset, pollTime, desc, false);
if (rv > 0) {
for (int n = 0; n < rv; n++) {
// Get the sendfile state
SendfileData state =
sendfileData.get(Long.valueOf(desc[n*2+1]));
// Problem events
if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
|| ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)) {
// Close socket and clear pool
remove(state);
// Destroy file descriptor pool, which should close the file
// Close the socket, as the response would be incomplete
closeSocketInternal(state.socket);
continue;
}
// Write some data using sendfile
long nw = Socket.sendfilen(state.socket, state.fd,
state.pos,
state.length, 0);
if (nw < 0) {
// Close socket and clear pool
remove(state);
// Close the socket, as the response would be incomplete
// This will close the file too.
closeSocketInternal(state.socket);
continue;
}
state.pos += nw;
state.length -= nw;
if (state.length == 0) {
remove(state);
switch (state.keepAliveState) {
case NONE: {
// Close the socket since this is
// the end of the not keep-alive request.
closeSocketInternal(state.socket);
break;
}
case PIPELINED: {
// Destroy file descriptor pool, which should close the file
Pool.destroy(state.fdpool);
Socket.timeoutSet(state.socket, getConnectionTimeout() * 1000);
// Process the pipelined request data
if (!processSocket(state.socket, SocketEvent.OPEN_READ)) {
closeSocketInternal(state.socket);
}
break;
}
case OPEN: {
// Destroy file descriptor pool, which should close the file
Pool.destroy(state.fdpool);
Socket.timeoutSet(state.socket, getConnectionTimeout() * 1000);
// Put the socket back in the poller for
// processing of further requests
getPoller().add(state.socket, getKeepAliveTimeout(),
Poll.APR_POLLIN);
break;
}
}
}
}
} else if (rv < 0) {
int errn = -rv;
/* Any non timeup or interrupted error is critical */
if ((errn != Status.TIMEUP) && (errn != Status.EINTR)) {
if (errn > Status.APR_OS_START_USERERR) {
errn -= Status.APR_OS_START_USERERR;
}
getLog().error(sm.getString(
"endpoint.apr.pollError",
Integer.valueOf(errn),
Error.strerror(errn)));
// Handle poll critical failure
synchronized (this) {
destroy();
init();
}
continue;
}
}
// Call maintain for the sendfile poller
if (getConnectionTimeout() > 0 &&
maintainTime > 1000000L && sendfileRunning) {
rv = Poll.maintain(sendfilePollset, desc, false);
maintainTime = 0;
if (rv > 0) {
for (int n = 0; n < rv; n++) {
// Get the sendfile state
SendfileData state = sendfileData.get(Long.valueOf(desc[n]));
// Close socket and clear pool
remove(state);
// Destroy file descriptor pool, which should close the file
// Close the socket, as the response would be incomplete
closeSocketInternal(state.socket);
}
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().error(sm.getString("endpoint.poll.error"), t);
}
}
synchronized (this) {
this.notifyAll();
}
}
}
// --------------------------------- SocketWithOptionsProcessor Inner Class
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool. This will also set the socket options
* and do the handshake.
*
* This is called after an accept().
*/
protected class SocketWithOptionsProcessor implements Runnable {
protected SocketWrapperBase socket = null;
public SocketWithOptionsProcessor(SocketWrapperBase socket) {
this.socket = socket;
}
@Override
public void run() {
synchronized (socket) {
if (!deferAccept) {
if (setSocketOptions(socket)) {
getPoller().add(socket.getSocket().longValue(),
getConnectionTimeout(), Poll.APR_POLLIN);
} else {
// Close socket and pool
getHandler().process(socket, SocketEvent.CONNECT_FAIL);
socket.close();
socket = null;
}
} else {
// Process the request from this socket
if (!setSocketOptions(socket)) {
// Close socket and pool
getHandler().process(socket, SocketEvent.CONNECT_FAIL);
socket.close();
socket = null;
return;
}
// Process the request from this socket
SocketState state = getHandler().process(socket, SocketEvent.OPEN_READ);
if (state == SocketState.CLOSED) {
// Close socket and pool
socket.close();
socket = null;
}
}
}
}
}
// -------------------------------------------- SocketProcessor Inner Class
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketProcessor extends SocketProcessorBase {
public SocketProcessor(SocketWrapperBase socketWrapper, SocketEvent event) {
super(socketWrapper, event);
}
@Override
protected void doRun() {
try {
// Process the request from this socket
SocketState state = getHandler().process(socketWrapper, event);
if (state == SocketState.CLOSED) {
// Close socket and pool
socketWrapper.close();
}
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && processorCache != null) {
processorCache.push(this);
}
}
}
}
public static class AprSocketWrapper extends SocketWrapperBase {
private static final int SSL_OUTPUT_BUFFER_SIZE = 8192;
private final ByteBuffer sslOutputBuffer;
// This field should only be used by Poller#run()
private int pollerFlags = 0;
/*
* Used if block/non-blocking is set at the socket level. The client is
* responsible for the thread-safe use of this field via the locks provided.
*/
private volatile boolean blockingStatus = true;
private final Lock blockingStatusReadLock;
private final WriteLock blockingStatusWriteLock;
public AprSocketWrapper(Long socket, AprEndpoint endpoint) {
super(socket, endpoint);
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
this.blockingStatusReadLock = lock.readLock();
this.blockingStatusWriteLock = lock.writeLock();
// TODO Make the socketWriteBuffer size configurable and align the
// SSL and app buffer size settings with NIO & NIO2.
if (endpoint.isSSLEnabled()) {
sslOutputBuffer = ByteBuffer.allocateDirect(SSL_OUTPUT_BUFFER_SIZE);
sslOutputBuffer.position(SSL_OUTPUT_BUFFER_SIZE);
} else {
sslOutputBuffer = null;
}
socketBufferHandler = new SocketBufferHandler(6 * 1500, 6 * 1500, true);
}
public boolean getBlockingStatus() { return blockingStatus; }
public void setBlockingStatus(boolean blockingStatus) {
this.blockingStatus = blockingStatus;
}
public Lock getBlockingStatusReadLock() { return blockingStatusReadLock; }
public WriteLock getBlockingStatusWriteLock() {
return blockingStatusWriteLock;
}
@Override
public int read(boolean block, byte[] b, int off, int len) throws IOException {
int nRead = populateReadBuffer(b, off, len);
if (nRead > 0) {
return nRead;
/*
* Since more bytes may have arrived since the buffer was last
* filled, it is an option at this point to perform a
* non-blocking read. However correctly handling the case if
* that read returns end of stream adds complexity. Therefore,
* at the moment, the preference is for simplicity.
*/
}
// Fill the read buffer as best we can.
nRead = fillReadBuffer(block);
// Fill as much of the remaining byte array as possible with the
// data that was just read
if (nRead > 0) {
socketBufferHandler.configureReadBufferForRead();
nRead = Math.min(nRead, len);
socketBufferHandler.getReadBuffer().get(b, off, nRead);
}
return nRead;
}
@Override
public int read(boolean block, ByteBuffer to) throws IOException {
int nRead = populateReadBuffer(to);
if (nRead > 0) {
return nRead;
/*
* Since more bytes may have arrived since the buffer was last
* filled, it is an option at this point to perform a
* non-blocking read. However correctly handling the case if
* that read returns end of stream adds complexity. Therefore,
* at the moment, the preference is for simplicity.
*/
}
// The socket read buffer capacity is socket.appReadBufSize
int limit = socketBufferHandler.getReadBuffer().capacity();
if (to.isDirect() && to.remaining() >= limit) {
to.limit(to.position() + limit);
nRead = fillReadBuffer(block, to);
if (log.isDebugEnabled()) {
log.debug("Socket: [" + this + "], Read direct from socket: [" + nRead + "]");
}
} else {
// Fill the read buffer as best we can.
nRead = fillReadBuffer(block);
if (log.isDebugEnabled()) {
log.debug("Socket: [" + this + "], Read into buffer: [" + nRead + "]");
}
// Fill as much of the remaining byte array as possible with the
// data that was just read
if (nRead > 0) {
nRead = populateReadBuffer(to);
}
}
return nRead;
}
private int fillReadBuffer(boolean block) throws IOException {
socketBufferHandler.configureReadBufferForWrite();
return fillReadBuffer(block, socketBufferHandler.getReadBuffer());
}
private int fillReadBuffer(boolean block, ByteBuffer to) throws IOException {
Lock readLock = getBlockingStatusReadLock();
WriteLock writeLock = getBlockingStatusWriteLock();
boolean readDone = false;
int result = 0;
readLock.lock();
try {
checkClosed();
if (getBlockingStatus() == block) {
if (block) {
Socket.timeoutSet(getSocket().longValue(), getReadTimeout() * 1000);
}
result = Socket.recvb(getSocket().longValue(), to, to.position(),
to.remaining());
readDone = true;
}
} finally {
readLock.unlock();
}
if (!readDone) {
writeLock.lock();
try {
checkClosed();
// Set the current settings for this socket
setBlockingStatus(block);
if (block) {
Socket.timeoutSet(getSocket().longValue(), getReadTimeout() * 1000);
} else {
Socket.timeoutSet(getSocket().longValue(), 0);
}
// Downgrade the lock
readLock.lock();
try {
writeLock.unlock();
result = Socket.recvb(getSocket().longValue(), to, to.position(),
to.remaining());
} finally {
readLock.unlock();
}
} finally {
// Should have been released above but may not have been on some
// exception paths
if (writeLock.isHeldByCurrentThread()) {
writeLock.unlock();
}
}
}
if (result > 0) {
to.position(to.position() + result);
return result;
} else if (result == 0 || -result == Status.EAGAIN) {
return 0;
} else if ((-result) == Status.ETIMEDOUT || (-result) == Status.TIMEUP) {
if (block) {
throw new SocketTimeoutException(sm.getString("iib.readtimeout"));
} else {
// Attempting to read from the socket when the poller
// has not signalled that there is data to read appears
// to behave like a blocking read with a short timeout
// on OSX rather than like a non-blocking read. If no
// data is read, treat the resulting timeout like a
// non-blocking read that returned no data.
return 0;
}
} else if (-result == Status.APR_EOF) {
return -1;
} else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
(-result == Status.APR_OS_START_SYSERR + 10053)) {
// 10053 on Windows is connection aborted
throw new EOFException(sm.getString("socket.apr.clientAbort"));
} else {
throw new IOException(sm.getString("socket.apr.read.error",
Integer.valueOf(-result), getSocket(), this));
}
}
@Override
public boolean isReadyForRead() throws IOException {
socketBufferHandler.configureReadBufferForRead();
if (socketBufferHandler.getReadBuffer().remaining() > 0) {
return true;
}
int read = fillReadBuffer(false);
boolean isReady = socketBufferHandler.getReadBuffer().position() > 0 || read == -1;
return isReady;
}
private void checkClosed() throws IOException {
if (isClosed()) {
throw new IOException(sm.getString("socket.apr.closed", getSocket()));
}
}
@Override
protected void doClose() {
if (log.isDebugEnabled()) {
log.debug("Calling [" + getEndpoint() + "].closeSocket([" + this + "])");
}
getEndpoint().connections.remove(getSocket());
socketBufferHandler.free();
socketBufferHandler = SocketBufferHandler.EMPTY;
nonBlockingWriteBuffer.clear();
if (sslOutputBuffer != null) {
ByteBufferUtils.cleanDirectBuffer(sslOutputBuffer);
}
Poller poller = ((AprEndpoint) getEndpoint()).getPoller();
if (poller != null) {
poller.close(getSocket().longValue());
}
}
@Override
protected void doWrite(boolean block, ByteBuffer from) throws IOException {
Lock readLock = getBlockingStatusReadLock();
WriteLock writeLock = getBlockingStatusWriteLock();
readLock.lock();
try {
checkClosed();
if (getBlockingStatus() == block) {
if (block) {
Socket.timeoutSet(getSocket().longValue(), getWriteTimeout() * 1000);
}
doWriteInternal(from);
return;
}
} finally {
readLock.unlock();
}
writeLock.lock();
try {
checkClosed();
// Set the current settings for this socket
setBlockingStatus(block);
if (block) {
Socket.timeoutSet(getSocket().longValue(), getWriteTimeout() * 1000);
} else {
Socket.timeoutSet(getSocket().longValue(), 0);
}
// Downgrade the lock
readLock.lock();
try {
writeLock.unlock();
doWriteInternal(from);
} finally {
readLock.unlock();
}
} finally {
// Should have been released above but may not have been on some
// exception paths
if (writeLock.isHeldByCurrentThread()) {
writeLock.unlock();
}
}
}
private void doWriteInternal(ByteBuffer from) throws IOException {
if (previousIOException != null) {
/*
* Socket has previously seen an IOException on write.
*
* Blocking writes assume that buffer is always fully written so
* there is no code checking for incomplete writes, retaining
* the unwritten data and attempting to write it as part of a
* subsequent write call.
*
* Because of the above, when an IOException is triggered we
* need so skip subsequent attempts to write as otherwise it
* will appear to the client as if some data was dropped just
* before the connection is lost. It is better if the client
* just sees the dropped connection.
*/
throw new IOException(previousIOException);
}
int thisTime;
do {
thisTime = 0;
if (getEndpoint().isSSLEnabled()) {
if (sslOutputBuffer.remaining() == 0) {
// Buffer was fully written last time around
sslOutputBuffer.clear();
transfer(from, sslOutputBuffer);
sslOutputBuffer.flip();
} else {
// Buffer still has data from previous attempt to write
// APR + SSL requires that exactly the same parameters are
// passed when re-attempting the write
}
thisTime = Socket.sendb(getSocket().longValue(), sslOutputBuffer,
sslOutputBuffer.position(), sslOutputBuffer.limit());
if (thisTime > 0) {
sslOutputBuffer.position(sslOutputBuffer.position() + thisTime);
}
} else {
thisTime = Socket.sendb(getSocket().longValue(), from, from.position(),
from.remaining());
if (thisTime > 0) {
from.position(from.position() + thisTime);
}
}
if (Status.APR_STATUS_IS_EAGAIN(-thisTime)) {
thisTime = 0;
} else if (-thisTime == Status.APR_EOF) {
throw new EOFException(sm.getString("socket.apr.clientAbort"));
} else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
(-thisTime == Status.APR_OS_START_SYSERR + 10053)) {
// 10053 on Windows is connection aborted
throw new EOFException(sm.getString("socket.apr.clientAbort"));
} else if (thisTime < 0) {
previousIOException = new IOException(sm.getString("socket.apr.write.error",
Integer.valueOf(-thisTime), getSocket(), this));
throw previousIOException;
}
} while ((thisTime > 0 || getBlockingStatus()) && from.hasRemaining());
// If there is data left in the buffer the socket will be registered for
// write further up the stack. This is to ensure the socket is only
// registered for write once as both container and user code can trigger
// write registration.
}
@Override
public void registerReadInterest() {
// Make sure an already closed socket is not added to the poller
synchronized (closed) {
if (isClosed()) {
return;
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.debug.registerRead", this));
}
Poller p = ((AprEndpoint) getEndpoint()).getPoller();
if (p != null) {
p.add(getSocket().longValue(), getReadTimeout(), Poll.APR_POLLIN);
}
}
}
@Override
public void registerWriteInterest() {
// Make sure an already closed socket is not added to the poller
synchronized (closed) {
if (isClosed()) {
return;
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.debug.registerWrite", this));
}
((AprEndpoint) getEndpoint()).getPoller().add(
getSocket().longValue(), getWriteTimeout(), Poll.APR_POLLOUT);
}
}
@Override
public SendfileDataBase createSendfileData(String filename, long pos, long length) {
return new SendfileData(filename, pos, length);
}
@Override
public SendfileState processSendfile(SendfileDataBase sendfileData) {
((SendfileData) sendfileData).socket = getSocket().longValue();
return ((AprEndpoint) getEndpoint()).getSendfile().add((SendfileData) sendfileData);
}
@Override
protected void populateRemoteAddr() {
if (isClosed()) {
return;
}
try {
long socket = getSocket().longValue();
long sa = Address.get(Socket.APR_REMOTE, socket);
remoteAddr = Address.getip(sa);
} catch (Exception e) {
log.warn(sm.getString("endpoint.warn.noRemoteAddr", getSocket()), e);
}
}
@Override
protected void populateRemoteHost() {
if (isClosed()) {
return;
}
try {
long socket = getSocket().longValue();
long sa = Address.get(Socket.APR_REMOTE, socket);
remoteHost = Address.getnameinfo(sa, 0);
if (remoteAddr == null) {
remoteAddr = Address.getip(sa);
}
} catch (Exception e) {
log.warn(sm.getString("endpoint.warn.noRemoteHost", getSocket()), e);
}
}
@Override
protected void populateRemotePort() {
if (isClosed()) {
return;
}
try {
long socket = getSocket().longValue();
long sa = Address.get(Socket.APR_REMOTE, socket);
Sockaddr addr = Address.getInfo(sa);
remotePort = addr.port;
} catch (Exception e) {
log.warn(sm.getString("endpoint.warn.noRemotePort", getSocket()), e);
}
}
@Override
protected void populateLocalName() {
if (isClosed()) {
return;
}
try {
long socket = getSocket().longValue();
long sa = Address.get(Socket.APR_LOCAL, socket);
localName =Address.getnameinfo(sa, 0);
} catch (Exception e) {
log.warn(sm.getString("endpoint.warn.noLocalName"), e);
}
}
@Override
protected void populateLocalAddr() {
if (isClosed()) {
return;
}
try {
long socket = getSocket().longValue();
long sa = Address.get(Socket.APR_LOCAL, socket);
localAddr = Address.getip(sa);
} catch (Exception e) {
log.warn(sm.getString("endpoint.warn.noLocalAddr"), e);
}
}
@Override
protected void populateLocalPort() {
if (isClosed()) {
return;
}
try {
long socket = getSocket().longValue();
long sa = Address.get(Socket.APR_LOCAL, socket);
Sockaddr addr = Address.getInfo(sa);
localPort = addr.port;
} catch (Exception e) {
log.warn(sm.getString("endpoint.warn.noLocalPort"), e);
}
}
@Override
public SSLSupport getSslSupport(String clientCertProvider) {
if (getEndpoint().isSSLEnabled()) {
return new AprSSLSupport(this, clientCertProvider);
} else {
return null;
}
}
@Override
public SSLSupport getSslSupport() {
throw new UnsupportedOperationException();
}
@Override
public void doClientAuth(SSLSupport sslSupport) throws IOException {
long socket = getSocket().longValue();
// Configure connection to require a certificate. This requires a
// re-handshake and must block until the re-handshake completes.
// Therefore, make sure socket is in blocking mode.
Lock readLock = getBlockingStatusReadLock();
WriteLock writeLock = getBlockingStatusWriteLock();
boolean renegotiateDone = false;
try {
readLock.lock();
try {
if (getBlockingStatus()) {
Socket.timeoutSet(getSocket().longValue(), getReadTimeout() * 1000);
SSLSocket.setVerify(socket, SSL.SSL_CVERIFY_REQUIRE, -1);
SSLSocket.renegotiate(socket);
renegotiateDone = true;
}
} finally {
readLock.unlock();
}
if (!renegotiateDone) {
writeLock.lock();
try {
// Set the current settings for this socket
setBlockingStatus(true);
Socket.timeoutSet(getSocket().longValue(), getReadTimeout() * 1000);
// Downgrade the lock
readLock.lock();
try {
writeLock.unlock();
SSLSocket.setVerify(socket, SSL.SSL_CVERIFY_REQUIRE, -1);
SSLSocket.renegotiate(socket);
} finally {
readLock.unlock();
}
} finally {
// Should have been released above but may not have been on some
// exception paths
if (writeLock.isHeldByCurrentThread()) {
writeLock.unlock();
}
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
throw new IOException(sm.getString("socket.sslreneg"), t);
}
}
@Override
public void setAppReadBufHandler(ApplicationBufferHandler handler) {
// no-op
}
String getSSLInfoS(int id) {
synchronized (closed) {
if (isClosed()) {
return null;
}
try {
return SSLSocket.getInfoS(getSocket().longValue(), id);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
int getSSLInfoI(int id) {
synchronized (closed) {
if (isClosed()) {
return 0;
}
try {
return SSLSocket.getInfoI(getSocket().longValue(), id);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
byte[] getSSLInfoB(int id) {
synchronized (closed) {
if (isClosed()) {
return null;
}
try {
return SSLSocket.getInfoB(getSocket().longValue(), id);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
@Override
protected OperationState newOperationState(boolean read,
ByteBuffer[] buffers, int offset, int length,
BlockingMode block, long timeout, TimeUnit unit, A attachment,
CompletionCheck check, CompletionHandler handler,
Semaphore semaphore, VectoredIOCompletionHandler completion) {
return new AprOperationState<>(read, buffers, offset, length, block,
timeout, unit, attachment, check, handler, semaphore, completion);
}
private class AprOperationState extends OperationState {
private volatile boolean inline = true;
private volatile long flushBytes = 0;
private AprOperationState(boolean read, ByteBuffer[] buffers, int offset, int length,
BlockingMode block, long timeout, TimeUnit unit, A attachment, CompletionCheck check,
CompletionHandler handler, Semaphore semaphore,
VectoredIOCompletionHandler completion) {
super(read, buffers, offset, length, block,
timeout, unit, attachment, check, handler, semaphore, completion);
}
@Override
protected boolean isInline() {
return inline;
}
@Override
public void run() {
// Perform the IO operation
// Called from the poller to continue the IO operation
long nBytes = 0;
if (getError() == null) {
try {
synchronized (this) {
if (!completionDone) {
// This filters out same notification until processing
// of the current one is done
if (log.isDebugEnabled()) {
log.debug("Skip concurrent " + (read ? "read" : "write") + " notification");
}
return;
}
// Find the buffer on which the operation will be performed (no vectoring with APR)
ByteBuffer buffer = null;
for (int i = 0; i < length; i++) {
if (buffers[i + offset].hasRemaining()) {
buffer = buffers[i + offset];
break;
}
}
if (buffer == null && flushBytes == 0) {
// Nothing to do
completion.completed(Long.valueOf(0), this);
return;
}
if (read) {
nBytes = read(false, buffer);
} else {
if (!flush(block == BlockingMode.BLOCK)) {
if (flushBytes > 0) {
// Flushing was done, continue processing
nBytes = flushBytes;
flushBytes = 0;
} else {
@SuppressWarnings("null") // Not possible
int remaining = buffer.remaining();
write(block == BlockingMode.BLOCK, buffer);
nBytes = remaining - buffer.remaining();
if (nBytes > 0 && flush(block == BlockingMode.BLOCK)) {
// We have to flush and it's incomplete, save the bytes written until done
inline = false;
registerWriteInterest();
flushBytes = nBytes;
return;
}
}
} else {
// Continue flushing
inline = false;
registerWriteInterest();
return;
}
}
if (nBytes != 0) {
completionDone = false;
}
}
} catch (IOException e) {
setError(e);
}
}
if (nBytes > 0) {
// The bytes processed are only updated in the completion handler
completion.completed(Long.valueOf(nBytes), this);
} else if (nBytes < 0 || getError() != null) {
IOException error = getError();
if (error == null) {
error = new EOFException();
}
completion.failed(error, this);
} else {
// As soon as the operation uses the poller, it is no longer inline
inline = false;
if (read) {
registerReadInterest();
} else {
registerWriteInterest();
}
}
}
}
}
}