/*
* 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.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
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.SSLSocket;
import org.apache.tomcat.jni.Sockaddr;
import org.apache.tomcat.jni.Socket;
import org.apache.tomcat.jni.Status;
import org.apache.tomcat.util.res.StringManager;
/**
* APR tailored thread pool, providing the following services:
* <ul>
* <li>Socket acceptor thread</li>
* <li>Socket poller thread</li>
* <li>Sendfile thread</li>
* <li>Worker threads pool</li>
* </ul>
*
* When switching to Java 5, there's an opportunity to use the virtual
* machine's thread pool.
*
* @author Mladen Turk
* @author Remy Maucherat
*/
public class AprEndpoint extends AbstractEndpoint {
// -------------------------------------------------------------- Constants
protected static Log log = LogFactory.getLog(AprEndpoint.class);
protected static StringManager sm =
StringManager.getManager("org.apache.tomcat.util.net.res");
/**
* The Request attribute key for the cipher suite.
*/
public static final String CIPHER_SUITE_KEY = "javax.servlet.request.cipher_suite";
/**
* The Request attribute key for the key size.
*/
public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size";
/**
* The Request attribute key for the client certificate chain.
*/
public static final String CERTIFICATE_KEY = "javax.servlet.request.X509Certificate";
/**
* The Request attribute key for the session id.
* This one is a Tomcat extension to the Servlet spec.
*/
public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session";
// ----------------------------------------------------------------- Fields
/**
* Available workers.
*/
protected WorkerStack workers = null;
/**
* Running state of the endpoint.
*/
protected volatile boolean running = false;
/**
* Will be set to true whenever the endpoint is paused.
*/
protected volatile boolean paused = false;
/**
* Track the initialization state of the endpoint.
*/
protected boolean initialized = false;
/**
* Current worker threads busy count.
*/
protected int curThreadsBusy = 0;
/**
* Current worker threads count.
*/
protected int curThreads = 0;
/**
* Sequence number used to generate thread names.
*/
protected int sequence = 0;
/**
* Root APR memory pool.
*/
protected long rootPool = 0;
/**
* Server socket "pointer".
*/
protected long serverSock = 0;
/**
* APR memory pool for the server socket.
*/
protected long serverSockPool = 0;
/**
* SSL context.
*/
protected long sslContext = 0;
/* Acceptor thread array */
private Acceptor acceptors[] = null;
private Map<Long,Object> locks = new ConcurrentHashMap<Long, Object>();
// ------------------------------------------------------------- Properties
/**
* Defer accept.
*/
protected boolean deferAccept = true;
public void setDeferAccept(boolean deferAccept) { this.deferAccept = deferAccept; }
public boolean getDeferAccept() { return deferAccept; }
/**
* External Executor based thread pool.
*/
protected Executor executor = null;
public void setExecutor(Executor executor) { this.executor = executor; }
public Executor getExecutor() { return executor; }
/**
* Maximum amount of worker threads.
*/
protected int maxThreads = 200;
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
if (running) {
synchronized(workers) {
workers.resize(maxThreads);
}
}
}
public int getMaxThreads() {
if (executor != null) {
return -1;
} else {
return maxThreads;
}
}
/**
* Priority of the acceptor and poller threads.
*/
protected int threadPriority = Thread.NORM_PRIORITY;
public void setThreadPriority(int threadPriority) { this.threadPriority = threadPriority; }
public int getThreadPriority() { return threadPriority; }
/**
* Size of the socket poller.
*/
protected int pollerSize = 8 * 1024;
public void setPollerSize(int pollerSize) { this.pollerSize = pollerSize; }
public int getPollerSize() { return pollerSize; }
/**
* 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; }
/**
* Server socket port.
*/
protected int port;
public int getPort() { return port; }
public void setPort(int port ) { this.port=port; }
/**
* Address for the server socket.
*/
protected InetAddress address;
public InetAddress getAddress() { return address; }
public void setAddress(InetAddress address) { this.address = address; }
/**
* Handling of accepted sockets.
*/
protected Handler handler = null;
public void setHandler(Handler handler ) { this.handler = handler; }
public Handler getHandler() { return handler; }
/**
* Allows the server developer to specify the backlog that
* should be used for server sockets. By default, this value
* is 100.
*/
protected int backlog = 100;
public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
public int getBacklog() { return backlog; }
/**
* Socket TCP no delay.
*/
protected boolean tcpNoDelay = false;
public boolean getTcpNoDelay() { return tcpNoDelay; }
public void setTcpNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; }
/**
* Socket linger.
*/
protected int soLinger = 100;
public int getSoLinger() { return soLinger; }
public void setSoLinger(int soLinger) { this.soLinger = soLinger; }
/**
* Socket timeout.
*/
protected int soTimeout = -1;
public int getSoTimeout() { return soTimeout; }
public void setSoTimeout(int soTimeout) { this.soTimeout = soTimeout; }
/**
* Keep-Alive timeout.
*/
protected int keepAliveTimeout = -1;
public int getKeepAliveTimeout() { return keepAliveTimeout; }
public void setKeepAliveTimeout(int keepAliveTimeout) { this.keepAliveTimeout = keepAliveTimeout; }
/**
* 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; } }
/**
* The default is true - the created threads will be
* in daemon mode. If set to false, the control thread
* will not be daemon - and will keep the process alive.
*/
protected boolean daemon = true;
public void setDaemon(boolean b) { daemon = b; }
public boolean getDaemon() { return daemon; }
/**
* Name of the thread pool, which will be used for naming child threads.
*/
protected String name = "TP";
public void setName(String name) { this.name = name; }
public String getName() { return name; }
/**
* Use endfile for sending static files.
*/
protected boolean useSendfile = Library.APR_HAS_SENDFILE;
public void setUseSendfile(boolean useSendfile) { this.useSendfile = useSendfile; }
public boolean getUseSendfile() { return useSendfile; }
/**
* Allow comet request handling.
*/
protected boolean useComet = true;
public void setUseComet(boolean useComet) { this.useComet = useComet; }
public boolean getUseComet() { return useComet; }
/**
* Acceptor thread count.
*/
protected int acceptorThreadCount = 0;
public void setAcceptorThreadCount(int acceptorThreadCount) { this.acceptorThreadCount = acceptorThreadCount; }
public int getAcceptorThreadCount() { return acceptorThreadCount; }
/**
* Sendfile thread count.
*/
protected int sendfileThreadCount = 0;
public void setSendfileThreadCount(int sendfileThreadCount) { this.sendfileThreadCount = sendfileThreadCount; }
public int getSendfileThreadCount() { return sendfileThreadCount; }
/**
* Poller thread count.
*/
protected int pollerThreadCount = 0;
public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
public int getPollerThreadCount() { return pollerThreadCount; }
/**
* The socket poller.
*/
protected Poller[] pollers = null;
protected int pollerRoundRobin = 0;
public Poller getPoller() {
pollerRoundRobin = (pollerRoundRobin + 1) % pollers.length;
return pollers[pollerRoundRobin];
}
/**
* The socket poller used for Comet support.
*/
protected Poller[] cometPollers = null;
protected int cometPollerRoundRobin = 0;
public Poller getCometPoller() {
cometPollerRoundRobin = (cometPollerRoundRobin + 1) % cometPollers.length;
return cometPollers[cometPollerRoundRobin];
}
/**
* The static file sender.
*/
protected Sendfile[] sendfiles = null;
protected int sendfileRoundRobin = 0;
public Sendfile getSendfile() {
sendfileRoundRobin = (sendfileRoundRobin + 1) % sendfiles.length;
return sendfiles[sendfileRoundRobin];
}
/**
* Dummy maxSpareThreads property.
*/
public int getMaxSpareThreads() { return 0; }
/**
* Dummy minSpareThreads property.
*/
public int getMinSpareThreads() { return 0; }
/**
* Unlock timeout.
*/
protected int unlockTimeout = 250;
public int getUnlockTimeout() { return unlockTimeout; }
public void setUnlockTimeout(int unlockTimeout) {
this.unlockTimeout = unlockTimeout;
}
/**
* SSL engine.
*/
protected boolean SSLEnabled = false;
public boolean isSSLEnabled() { return SSLEnabled; }
public void setSSLEnabled(boolean SSLEnabled) { this.SSLEnabled = SSLEnabled; }
/**
* SSL protocols.
*/
protected String SSLProtocol = "all";
public String getSSLProtocol() { return SSLProtocol; }
public void setSSLProtocol(String SSLProtocol) { this.SSLProtocol = SSLProtocol; }
/**
* SSL password (if a cert is encrypted, and no password has been provided, a callback
* will ask for a password).
*/
protected String SSLPassword = null;
public String getSSLPassword() { return SSLPassword; }
public void setSSLPassword(String SSLPassword) { this.SSLPassword = SSLPassword; }
/**
* SSL cipher suite.
*/
protected String SSLCipherSuite = "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!kRSA";
public String getSSLCipherSuite() { return SSLCipherSuite; }
public void setSSLCipherSuite(String SSLCipherSuite) { this.SSLCipherSuite = SSLCipherSuite; }
/**
* SSL certificate file.
*/
protected String SSLCertificateFile = null;
public String getSSLCertificateFile() { return SSLCertificateFile; }
public void setSSLCertificateFile(String SSLCertificateFile) { this.SSLCertificateFile = SSLCertificateFile; }
/**
* SSL certificate key file.
*/
protected String SSLCertificateKeyFile = null;
public String getSSLCertificateKeyFile() { return SSLCertificateKeyFile; }
public void setSSLCertificateKeyFile(String SSLCertificateKeyFile) { this.SSLCertificateKeyFile = SSLCertificateKeyFile; }
/**
* SSL certificate chain file.
*/
protected String SSLCertificateChainFile = null;
public String getSSLCertificateChainFile() { return SSLCertificateChainFile; }
public void setSSLCertificateChainFile(String SSLCertificateChainFile) { this.SSLCertificateChainFile = SSLCertificateChainFile; }
/**
* SSL CA certificate path.
*/
protected String SSLCACertificatePath = null;
public String getSSLCACertificatePath() { return SSLCACertificatePath; }
public void setSSLCACertificatePath(String SSLCACertificatePath) { this.SSLCACertificatePath = SSLCACertificatePath; }
/**
* SSL CA certificate file.
*/
protected String SSLCACertificateFile = null;
public String getSSLCACertificateFile() { return SSLCACertificateFile; }
public void setSSLCACertificateFile(String SSLCACertificateFile) { this.SSLCACertificateFile = SSLCACertificateFile; }
/**
* SSL CA revocation path.
*/
protected String SSLCARevocationPath = null;
public String getSSLCARevocationPath() { return SSLCARevocationPath; }
public void setSSLCARevocationPath(String SSLCARevocationPath) { this.SSLCARevocationPath = SSLCARevocationPath; }
/**
* SSL CA revocation file.
*/
protected String SSLCARevocationFile = null;
public String getSSLCARevocationFile() { return SSLCARevocationFile; }
public void setSSLCARevocationFile(String SSLCARevocationFile) { this.SSLCARevocationFile = SSLCARevocationFile; }
/**
* SSL verify client.
*/
protected String SSLVerifyClient = "none";
public String getSSLVerifyClient() { return SSLVerifyClient; }
public void setSSLVerifyClient(String SSLVerifyClient) { this.SSLVerifyClient = SSLVerifyClient; }
/**
* SSL verify depth.
*/
protected int SSLVerifyDepth = 10;
public int getSSLVerifyDepth() { return SSLVerifyDepth; }
public void setSSLVerifyDepth(int SSLVerifyDepth) { this.SSLVerifyDepth = SSLVerifyDepth; }
protected boolean SSLHonorCipherOrder = false;
/**
* Set to <code>true</code> to enforce the <i>server's</i> cipher order
* instead of the default which is to allow the client to choose a
* preferred cipher.
*/
public void setSSLHonorCipherOrder(boolean SSLHonorCipherOrder) { this.SSLHonorCipherOrder = SSLHonorCipherOrder; }
public boolean getSSLHonorCipherOrder() { return SSLHonorCipherOrder; }
/**
* Disables compression of the SSL stream. This thwarts CRIME attack
* and possibly improves performance by not compressing uncompressible
* content such as JPEG, etc.
*/
protected boolean SSLDisableCompression = false;
/**
* Set to <code>true</code> to disable SSL compression. This thwarts CRIME
* attack.
*/
public void setSSLDisableCompression(boolean SSLDisableCompression) { this.SSLDisableCompression = SSLDisableCompression; }
public boolean getSSLDisableCompression() { return SSLDisableCompression; }
/**
* Port in use.
*/
@Override
public int getLocalPort() {
long s = serverSock;
if (s == 0) {
return -1;
} else {
long sa;
try {
sa = Address.get(Socket.APR_LOCAL, s);
Sockaddr addr = Address.getInfo(sa);
return addr.port;
} catch (Exception e) {
return -1;
}
}
}
// --------------------------------------------------------- Public Methods
/**
* Number of keepalive sockets.
*/
public int getKeepAliveCount() {
if (pollers == null) {
return 0;
} else {
int keepAliveCount = 0;
for (int i = 0; i < pollers.length; i++) {
keepAliveCount += pollers[i].getKeepAliveCount();
}
return keepAliveCount;
}
}
/**
* Number of sendfile sockets.
*/
public int getSendfileCount() {
if (sendfiles == null) {
return 0;
} else {
int sendfileCount = 0;
for (int i = 0; i < sendfiles.length; i++) {
sendfileCount += sendfiles[i].getSendfileCount();
}
return sendfileCount;
}
}
/**
* Return the amount of threads that are managed by the pool.
*
* @return the amount of threads that are managed by the pool
*/
public int getCurrentThreadCount() {
if (executor != null) {
return -1;
} else {
return curThreads;
}
}
/**
* Return the amount of threads that are in use
*
* @return the amount of threads that are in use
*/
public int getCurrentThreadsBusy() {
if (executor!=null) {
return -1;
} else {
return workers!=null?curThreads - workers.size():0;
}
}
/**
* Return the state of the endpoint.
*
* @return true if the endpoint is running, false otherwise
*/
public boolean isRunning() {
return running;
}
/**
* Return the state of the endpoint.
*
* @return true if the endpoint is paused, false otherwise
*/
public boolean isPaused() {
return paused;
}
// ----------------------------------------------- Public Lifecycle Methods
/**
* Initialize the endpoint.
*/
public void init()
throws Exception {
if (initialized)
return;
if (rootPool != 0) {
// A previous init() call failed
throw new IllegalStateException(
sm.getString("endpoint.apr.previousInitFailed"));
}
// Create the root APR memory pool
rootPool = Pool.create(0);
// Create the pool for the server socket
serverSockPool = Pool.create(rootPool);
// Create the APR address that will be bound
String addressStr = null;
if (address == null) {
addressStr = null;
} else {
addressStr = address.getHostAddress();
}
int family = Socket.APR_INET;
if (Library.APR_HAVE_IPV6) {
if (addressStr == null) {
if (!OS.IS_BSD && !OS.IS_WIN32 && !OS.IS_WIN64)
family = Socket.APR_UNSPEC;
} else if (addressStr.indexOf(':') >= 0) {
family = Socket.APR_UNSPEC;
}
}
long inetAddress = Address.info(addressStr, family,
port, 0, rootPool);
// Create the APR server socket
serverSock = Socket.create(Address.getInfo(inetAddress).family,
Socket.SOCK_STREAM,
Socket.APR_PROTO_TCP, rootPool);
if (OS.IS_UNIX) {
Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
}
// 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, inetAddress);
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, backlog);
if (ret != 0) {
throw new Exception(sm.getString("endpoint.init.listen", "" + ret, Error.strerror(ret)));
}
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);
}
// Sendfile usage on systems which don't support it cause major problems
if (useSendfile && !Library.APR_HAS_SENDFILE) {
useSendfile = false;
}
// Initialize thread count defaults for acceptor, poller and sendfile
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount == 0) {
if ((OS.IS_WIN32 || OS.IS_WIN64) && (pollerSize > 1024)) {
// The maximum per poller to get reasonable performance is 1024
pollerThreadCount = pollerSize / 1024;
// Adjust poller size so that it won't reach the limit
pollerSize = pollerSize - (pollerSize % 1024);
} else {
// No explicit poller size limitation
pollerThreadCount = 1;
}
}
if (sendfileThreadCount == 0) {
if ((OS.IS_WIN32 || OS.IS_WIN64) && (sendfileSize > 1024)) {
// The maximum per poller to get reasonable performance is 1024
sendfileThreadCount = sendfileSize / 1024;
// Adjust poller size so that it won't reach the limit
sendfileSize = sendfileSize - (sendfileSize % 1024);
} else {
// No explicit poller size limitation
// FIXME: Default to one per CPU ?
sendfileThreadCount = 1;
}
}
// 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 (SSLEnabled) {
// SSL protocol
int value = SSL.SSL_PROTOCOL_NONE;
if (SSLProtocol == null || SSLProtocol.length() == 0) {
value = SSL.SSL_PROTOCOL_ALL;
} else {
for (String protocol : SSLProtocol.split("\\+")) {
protocol = protocol.trim();
if ("SSLv2".equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_SSLV2;
} else if ("SSLv3".equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_SSLV3;
} else if ("TLSv1".equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_TLSV1;
} else if ("TLSv1.1".equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_TLSV1_1;
} else if ("TLSv1.2".equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_TLSV1_2;
} else if ("all".equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_ALL;
} else {
// Protocol not recognized, fail to start as it is safer than
// continuing with the default which might enable more than the
// is required
throw new Exception(sm.getString(
"endpoint.apr.invalidSslProtocol", SSLProtocol));
}
}
}
// Create SSL Context
try {
sslContext = SSLContext.make(rootPool, value, SSL.SSL_MODE_SERVER);
} catch (Exception e) {
// If the sslEngine is disabled on the AprLifecycleListener
// there will be an Exception here but there is no way to check
// the AprLifecycleListener settings from here
throw new Exception(
sm.getString("endpoint.apr.failSslContextMake"), e);
}
// Set cipher order: client (default) or server
if (SSLHonorCipherOrder) {
boolean orderCiphersSupported = false;
try {
orderCiphersSupported = SSL.hasOp(SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
if (orderCiphersSupported)
SSLContext.setOptions(sslContext, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
} catch (UnsatisfiedLinkError e) {
// Ignore
}
if (!orderCiphersSupported) {
// OpenSSL does not support ciphers ordering.
log.warn(sm.getString("endpoint.warn.noHonorCipherOrder",
SSL.versionString()));
}
}
// Disable compression if requested
if (SSLDisableCompression) {
boolean disableCompressionSupported = false;
try {
disableCompressionSupported = SSL.hasOp(SSL.SSL_OP_NO_COMPRESSION);
if (disableCompressionSupported)
SSLContext.setOptions(sslContext, SSL.SSL_OP_NO_COMPRESSION);
} catch (UnsatisfiedLinkError e) {
// Ignore
}
if (!disableCompressionSupported) {
// OpenSSL does not support ciphers ordering.
log.warn(sm.getString("endpoint.warn.noDisableCompression",
SSL.versionString()));
}
}
// List the ciphers that the client is permitted to negotiate
SSLContext.setCipherSuite(sslContext, SSLCipherSuite);
// Load Server key and certificate
SSLContext.setCertificate(sslContext, SSLCertificateFile, SSLCertificateKeyFile, SSLPassword, SSL.SSL_AIDX_RSA);
// Set certificate chain file
SSLContext.setCertificateChainFile(sslContext, SSLCertificateChainFile, false);
// Support Client Certificates
SSLContext.setCACertificate(sslContext, SSLCACertificateFile, SSLCACertificatePath);
// Set revocation
SSLContext.setCARevocation(sslContext, SSLCARevocationFile, SSLCARevocationPath);
// Client certificate verification
value = SSL.SSL_CVERIFY_NONE;
if ("optional".equalsIgnoreCase(SSLVerifyClient)) {
value = SSL.SSL_CVERIFY_OPTIONAL;
} else if ("require".equalsIgnoreCase(SSLVerifyClient)) {
value = SSL.SSL_CVERIFY_REQUIRE;
} else if ("optionalNoCA".equalsIgnoreCase(SSLVerifyClient)) {
value = SSL.SSL_CVERIFY_OPTIONAL_NO_CA;
}
SSLContext.setVerify(sslContext, value, SSLVerifyDepth);
// For now, sendfile is not supported with SSL
useSendfile = false;
}
initialized = true;
}
/**
* Start the APR endpoint, creating acceptor, poller and sendfile threads.
*/
public void start()
throws Exception {
// Initialize socket if not done before
if (!initialized) {
init();
}
if (!running) {
running = true;
paused = false;
// Create worker collection
if (executor == null) {
workers = new WorkerStack(maxThreads);
}
// Start poller threads
pollers = new Poller[pollerThreadCount];
for (int i = 0; i < pollerThreadCount; i++) {
pollers[i] = new Poller(false);
pollers[i].init();
pollers[i].setName(getName() + "-Poller-" + i);
pollers[i].setPriority(threadPriority);
pollers[i].setDaemon(true);
pollers[i].start();
}
// Start comet poller threads
cometPollers = new Poller[pollerThreadCount];
for (int i = 0; i < pollerThreadCount; i++) {
cometPollers[i] = new Poller(true);
cometPollers[i].init();
cometPollers[i].setName(getName() + "-CometPoller-" + i);
cometPollers[i].setPriority(threadPriority);
cometPollers[i].setDaemon(true);
cometPollers[i].start();
}
// Start sendfile threads
if (useSendfile) {
sendfiles = new Sendfile[sendfileThreadCount];
for (int i = 0; i < sendfileThreadCount; i++) {
sendfiles[i] = new Sendfile();
sendfiles[i].init();
sendfiles[i].setName(getName() + "-Sendfile-" + i);
sendfiles[i].setPriority(threadPriority);
sendfiles[i].setDaemon(true);
sendfiles[i].start();
}
}
// Start acceptor threads
acceptors = new Acceptor[acceptorThreadCount];
for (int i = 0; i < acceptorThreadCount; i++) {
acceptors[i] = new Acceptor();
acceptors[i].setName(getName() + "-Acceptor-" + i);
acceptors[i].setPriority(threadPriority);
acceptors[i].setDaemon(getDaemon());
acceptors[i].start();
}
}
}
/**
* Pause the endpoint, which will make it stop accepting new sockets.
*/
public void pause() {
if (running && !paused) {
paused = true;
unlockAccept();
}
}
/**
* Resume the endpoint, which will make it start accepting new sockets
* again.
*/
public void resume() {
if (running) {
paused = false;
}
}
/**
* Stop the endpoint. This will cause all processing threads to stop.
*/
public void stop() {
if (!paused) {
pause();
}
if (running) {
running = false;
unlockAccept();
for (int i = 0; i < acceptors.length; i++) {
long s = System.currentTimeMillis() + 10000;
while (acceptors[i].isAlive() && serverSock != 0) {
try {
acceptors[i].interrupt();
acceptors[i].join(1000);
} catch (InterruptedException e) {
// Ignore
}
if (System.currentTimeMillis() >= s) {
log.warn(sm.getString("endpoint.warn.unlockAcceptorFailed",
acceptors[i].getName()));
// If the Acceptor is still running force
// the hard socket close.
if (serverSock != 0) {
Socket.shutdown(serverSock, Socket.APR_SHUTDOWN_READ);
serverSock = 0;
}
}
}
}
for (int i = 0; i < pollers.length; i++) {
try {
pollers[i].destroy();
} catch (Exception e) {
// Ignore
}
}
pollers = null;
for (int i = 0; i < cometPollers.length; i++) {
try {
cometPollers[i].destroy();
} catch (Exception e) {
// Ignore
}
}
cometPollers = null;
if (useSendfile) {
for (int i = 0; i < sendfiles.length; i++) {
try {
sendfiles[i].destroy();
} catch (Exception e) {
// Ignore
}
}
sendfiles = null;
}
}
}
/**
* Deallocate APR memory pools, and close server socket.
*/
public void destroy() throws Exception {
if (running) {
stop();
}
// Destroy pool if it was initialised
if (serverSockPool != 0) {
Pool.destroy(serverSockPool);
serverSockPool = 0;
}
// Close server socket if it was initialised
if (serverSock != 0) {
Socket.close(serverSock);
serverSock = 0;
}
sslContext = 0;
// Close all APR memory pools and resources if initialised
if (rootPool != 0) {
Pool.destroy(rootPool);
rootPool = 0;
}
initialized = false;
}
// ------------------------------------------------------ Protected Methods
/**
* Get a sequence number used for thread naming.
*/
protected int getSequence() {
return sequence++;
}
/**
* Unlock the server socket accept using a bugus connection.
*/
protected void unlockAccept() {
java.net.Socket s = null;
InetSocketAddress saddr = null;
try {
// Need to create a connection to unlock the accept();
if (address == null) {
saddr = new InetSocketAddress("localhost", getLocalPort());
} else {
saddr = new InetSocketAddress(address, getLocalPort());
}
s = new java.net.Socket();
s.setSoTimeout(soTimeout > 0 ? soTimeout : 60000);
s.setSoLinger(true ,0);
if (log.isDebugEnabled()) {
log.debug("About to unlock socket for: " + saddr);
}
s.connect(saddr, unlockTimeout);
/*
* In the case of a deferred accept / accept filters we need to
* send data to wake up the accept. Send OPTIONS * to bypass even
* BSD accept filters. The Acceptor will discard it.
*/
if (deferAccept) {
OutputStreamWriter sw;
sw = new OutputStreamWriter(s.getOutputStream(), "ISO-8859-1");
sw.write("OPTIONS * HTTP/1.0\r\n"
+ "User-Agent: Tomcat wakeup connection\r\n\r\n");
sw.flush();
}
} catch(Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.debug.unlock", "" + getLocalPort()), e);
}
} finally {
if (s != null) {
try {
s.close();
} catch (Exception e) {
// Ignore
}
}
}
}
/**
* Process the specified connection.
*/
protected boolean setSocketOptions(long socket) {
// Process the connection
int step = 1;
try {
// 1: Set socket options: timeout, linger, etc
if (soLinger >= 0)
Socket.optSet(socket, Socket.APR_SO_LINGER, soLinger);
if (tcpNoDelay)
Socket.optSet(socket, Socket.APR_TCP_NODELAY, (tcpNoDelay ? 1 : 0));
if (soTimeout > 0)
Socket.timeoutSet(socket, soTimeout * 1000);
// 2: SSL handshake
step = 2;
if (sslContext != 0) {
SSLSocket.attach(sslContext, socket);
if (SSLSocket.handshake(socket) != 0) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.err.handshake") + ": " + SSL.getLastError());
}
return false;
}
}
} catch (Throwable 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;
}
/**
* Create (or allocate) and return an available processor for use in
* processing a specific HTTP request, if possible. If the maximum
* allowed processors have already been created and are in use, return
* <code>null</code> instead.
*/
protected Worker createWorkerThread() {
synchronized (workers) {
if (workers.size() > 0) {
curThreadsBusy++;
return (workers.pop());
}
if ((maxThreads > 0) && (curThreads < maxThreads)) {
curThreadsBusy++;
if (curThreadsBusy == maxThreads) {
log.info(sm.getString("endpoint.info.maxThreads",
Integer.toString(maxThreads), address,
Integer.toString(getLocalPort())));
}
return (newWorkerThread());
} else {
if (maxThreads < 0) {
curThreadsBusy++;
return (newWorkerThread());
} else {
return (null);
}
}
}
}
/**
* Create and return a new processor suitable for processing HTTP
* requests and returning the corresponding responses.
*/
protected Worker newWorkerThread() {
Worker workerThread = new Worker();
workerThread.start();
return (workerThread);
}
/**
* Return a new worker thread, and block while to worker is available.
*/
protected Worker getWorkerThread() {
// Allocate a new worker thread
synchronized (workers) {
Worker workerThread;
while ((workerThread = createWorkerThread()) == null) {
try {
workers.wait();
} catch (InterruptedException e) {
// Ignore
}
}
return workerThread;
}
}
/**
* Recycle the specified Processor so that it can be used again.
*
* @param workerThread The processor to be recycled
*/
protected void recycleWorkerThread(Worker workerThread) {
synchronized (workers) {
workers.push(workerThread);
curThreadsBusy--;
workers.notify();
}
}
/**
* Allocate a new poller of the specified size.
*/
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));
return 0;
} else {
log.error(sm.getString("endpoint.poll.initfail"), e);
return -1;
}
}
}
/**
* Process given socket.
*/
protected boolean processSocketWithOptions(long socket) {
Long key = Long.valueOf(socket);
locks.put(key, new Object());
try {
if (executor == null) {
getWorkerThread().assignWithOptions(socket);
} else {
executor.execute(new SocketWithOptionsProcessor(socket));
}
} catch (Throwable 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);
locks.remove(key);
return false;
}
return true;
}
/**
* Process given socket.
*/
protected boolean processSocket(long socket) {
try {
if (executor == null) {
getWorkerThread().assign(socket);
} else {
executor.execute(new SocketProcessor(socket));
}
} catch (Throwable 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;
}
return true;
}
/**
* Process given socket for an event.
*/
protected boolean processSocket(long socket, SocketStatus status) {
try {
if (executor == null) {
getWorkerThread().assign(socket, status);
} else {
executor.execute(new SocketEventProcessor(socket, status));
}
} catch (Throwable 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;
}
return true;
}
private void destroySocket(long socket) {
Long key = Long.valueOf(socket);
locks.remove(key);
if (running && socket != 0) {
// If not running the socket will be destroyed by
// parent pool or acceptor socket.
// In any case disable double free which would cause JVM core.
Socket.destroy(socket);
}
}
// --------------------------------------------------- Acceptor Inner Class
/**
* Server socket acceptor thread.
*/
protected class Acceptor extends Thread {
private final Log log = LogFactory.getLog(AprEndpoint.Acceptor.class);
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
public void run() {
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
try {
// Accept the next incoming connection from the server socket
long socket = Socket.accept(serverSock);
/*
* In the case of a deferred accept unlockAccept needs to
* send data. This data will be rubbish, so destroy the
* socket and don't process it.
*/
if (deferAccept && (paused || !running)) {
destroySocket(socket);
continue;
}
// Hand this socket off to an appropriate processor
if (!processSocketWithOptions(socket)) {
// Close socket and pool right away
destroySocket(socket);
}
} catch (Throwable t) {
if (running) {
String msg = sm.getString("endpoint.accept.fail");
if (t instanceof Error) {
Error e = (Error) t;
if (e.getError() == 233) {
// Not an error on HP-UX so log as a warning
// so it can be filtered out on that platform
// See bug 50273
log.warn(msg, t);
} else {
log.error(msg, t);
}
} else {
log.error(msg, t);
}
}
}
// The processor will recycle itself when it finishes
}
}
}
// ----------------------------------------------------- Poller Inner Class
/**
* Poller class.
*/
public class Poller extends Thread {
protected long serverPollset = 0;
protected long pool = 0;
protected long[] desc;
protected long[] addS;
protected volatile int addCount = 0;
protected boolean comet = true;
protected volatile int keepAliveCount = 0;
public int getKeepAliveCount() { return keepAliveCount; }
public Poller(boolean comet) {
this.comet = comet;
}
/**
* Create the poller. With some versions of APR, the maximum poller size will
* be 62 (recompiling APR is necessary to remove this limitation).
*/
protected void init() {
pool = Pool.create(serverSockPool);
int size = pollerSize / pollerThreadCount;
int timeout = keepAliveTimeout;
if (timeout < 0) {
timeout = soTimeout;
}
serverPollset = allocatePoller(size, pool, timeout);
if (serverPollset == 0 && size > 1024) {
size = 1024;
serverPollset = allocatePoller(size, pool, timeout);
}
if (serverPollset == 0) {
size = 62;
serverPollset = allocatePoller(size, pool, timeout);
}
desc = new long[size * 2];
keepAliveCount = 0;
addS = new long[size];
addCount = 0;
}
/**
* Destroy the poller.
*/
public void destroy() {
// Close all sockets in the add queue
for (int i = 0; i < addCount; i++) {
if (comet) {
processSocket(addS[i], SocketStatus.STOP);
} else {
destroySocket(addS[i]);
}
}
// Close all sockets still in the poller
int rv = Poll.pollset(serverPollset, desc);
if (rv > 0) {
for (int n = 0; n < rv; n++) {
if (comet) {
processSocket(desc[n*2+1], SocketStatus.STOP);
} else {
destroySocket(desc[n*2+1]);
}
}
}
Pool.destroy(pool);
keepAliveCount = 0;
addCount = 0;
try {
while (this.isAlive()) {
this.interrupt();
this.join(1000);
}
} catch (InterruptedException e) {
// Ignore
}
}
/**
* 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).
*
* @param socket to add to the poller
*/
public void add(long socket) {
synchronized (this) {
// Add socket to the list. Newly added sockets will wait
// at most for pollTime before being polled
if (addCount >= addS.length) {
// Can't do anything: close the socket right away
if (comet) {
processSocket(socket, SocketStatus.ERROR);
} else {
destroySocket(socket);
}
return;
}
addS[addCount] = socket;
addCount++;
this.notify();
}
}
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
public void run() {
long maintainTime = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
if (keepAliveCount < 1 && addCount < 1) {
synchronized (this) {
while (keepAliveCount < 1 && addCount < 1 && running) {
// Reset maintain time.
maintainTime = 0;
try {
this.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
}
if (!running) {
break;
}
try {
// Add sockets which are waiting to the poller
if (addCount > 0) {
synchronized (this) {
int successCount = 0;
try {
for (int i = (addCount - 1); i >= 0; i--) {
int rv = Poll.add
(serverPollset, addS[i], Poll.APR_POLLIN);
if (rv == Status.APR_SUCCESS) {
successCount++;
} else {
// Can't do anything: close the socket right away
if (comet) {
processSocket(addS[i], SocketStatus.ERROR);
} else {
destroySocket(addS[i]);
}
}
}
} finally {
keepAliveCount += successCount;
addCount = 0;
}
}
}
maintainTime += pollTime;
// Pool for the specified interval
int rv = Poll.poll(serverPollset, pollTime, desc, true);
if (rv > 0) {
keepAliveCount -= rv;
for (int n = 0; n < rv; n++) {
// 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)
|| (comet && (!processSocket(desc[n*2+1], SocketStatus.OPEN)))
|| (!comet && (!processSocket(desc[n*2+1])))) {
// Close socket and clear pool
if (comet) {
processSocket(desc[n*2+1], SocketStatus.DISCONNECT);
} else {
destroySocket(desc[n*2+1]);
}
continue;
}
}
} 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;
}
log.error(sm.getString("endpoint.poll.fail", "" + errn, Error.strerror(errn)));
// Handle poll critical failure
synchronized (this) {
destroy();
init();
}
continue;
}
}
if (soTimeout > 0 && maintainTime > 1000000L && running) {
rv = Poll.maintain(serverPollset, desc, true);
maintainTime = 0;
if (rv > 0) {
keepAliveCount -= rv;
for (int n = 0; n < rv; n++) {
// Close socket and clear pool
if (comet) {
processSocket(desc[n], SocketStatus.TIMEOUT);
} else {
destroySocket(desc[n]);
}
}
}
}
} catch (Throwable t) {
log.error(sm.getString("endpoint.poll.error"), t);
}
}
synchronized (this) {
this.notifyAll();
}
}
}
// ----------------------------------------------------- Worker Inner Class
/**
* Server processor class.
*/
protected class Worker implements Runnable {
protected Thread thread = null;
protected boolean available = false;
protected long socket = 0;
protected SocketStatus status = null;
protected boolean options = false;
/**
* Process an incoming TCP/IP connection on the specified socket. Any
* exception that occurs during processing must be logged and swallowed.
* <b>NOTE</b>: This method is called from our Connector's thread. We
* must assign it to our own thread so that multiple simultaneous
* requests can be handled.
*
* @param socket TCP socket to process
*/
protected synchronized void assignWithOptions(long socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
status = null;
options = true;
available = true;
notifyAll();
}
/**
* Process an incoming TCP/IP connection on the specified socket. Any
* exception that occurs during processing must be logged and swallowed.
* <b>NOTE</b>: This method is called from our Connector's thread. We
* must assign it to our own thread so that multiple simultaneous
* requests can be handled.
*
* @param socket TCP socket to process
*/
protected synchronized void assign(long socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
status = null;
options = false;
available = true;
notifyAll();
}
protected synchronized void assign(long socket, SocketStatus status) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
this.status = status;
options = false;
available = true;
notifyAll();
}
/**
* Await a newly assigned Socket from our Connector, or <code>null</code>
* if we are supposed to shut down.
*/
protected synchronized long await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
long socket = this.socket;
available = false;
notifyAll();
return (socket);
}
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
public void run() {
// Process requests until we receive a shutdown signal
while (running) {
// Wait for the next socket to be assigned
long socket = await();
if (socket == 0)
continue;
if (!deferAccept && options) {
if (setSocketOptions(socket)) {
getPoller().add(socket);
} else {
// Close socket and pool
destroySocket(socket);
socket = 0;
}
} else {
Long key = Long.valueOf(socket);
Object lock = locks.get(key);
synchronized (lock) {
// Process the request from this socket
if ((status != null) && (handler.event(socket, status) == Handler.SocketState.CLOSED)) {
// Close socket and pool
destroySocket(socket);
socket = 0;
} else if ((status == null) && ((options && !setSocketOptions(socket))
|| handler.process(socket) == Handler.SocketState.CLOSED)) {
// Close socket and pool
destroySocket(socket);
socket = 0;
}
}
}
// Finish up this request
recycleWorkerThread(this);
}
}
/**
* Start the background processing thread.
*/
public void start() {
thread = new Thread(this);
thread.setName(getName() + "-" + (++curThreads));
thread.setDaemon(true);
thread.start();
}
}
// ----------------------------------------------- SendfileData Inner Class
/**
* SendfileData class.
*/
public static class SendfileData {
// File
public String fileName;
public long fd;
public long fdpool;
// Range information
public long start;
public long end;
// Socket and socket pool
public long socket;
// Position
public long pos;
// KeepAlive flag
public SendfileKeepAliveState keepAliveState = SendfileKeepAliveState.NONE;
}
// --------------------------------------------------- Sendfile Inner Class
/**
* Sendfile class.
*/
public class Sendfile extends Thread {
protected long sendfilePollset = 0;
protected long pool = 0;
protected long[] desc;
protected HashMap<Long, SendfileData> sendfileData;
protected volatile int sendfileCount;
public int getSendfileCount() { return sendfileCount; }
protected ArrayList<SendfileData> addS;
protected volatile int addCount;
/**
* Create the sendfile poller. With some versions of APR, the maximum poller size will
* be 62 (reocmpiling APR is necessary to remove this limitation).
*/
protected void init() {
pool = Pool.create(serverSockPool);
int size = sendfileSize / sendfileThreadCount;
sendfilePollset = allocatePoller(size, pool, soTimeout);
if (sendfilePollset == 0 && size > 1024) {
size = 1024;
sendfilePollset = allocatePoller(size, pool, soTimeout);
}
if (sendfilePollset == 0) {
size = 62;
sendfilePollset = allocatePoller(size, pool, soTimeout);
}
desc = new long[size * 2];
sendfileData = new HashMap<Long, SendfileData>(size);
addS = new ArrayList<SendfileData>();
addCount = 0;
}
/**
* Destroy the poller.
*/
public void destroy() {
// Close any socket remaining in the add queue
addCount = 0;
for (int i = (addS.size() - 1); i >= 0; i--) {
SendfileData data = addS.get(i);
destroySocket(data.socket);
}
addS.clear();
// Close all sockets still in the poller
int rv = Poll.pollset(sendfilePollset, desc);
if (rv > 0) {
for (int n = 0; n < rv; n++) {
destroySocket(desc[n*2+1]);
}
}
Pool.destroy(pool);
sendfileData.clear();
try {
while (this.isAlive()) {
this.interrupt();
this.join(1000);
}
} catch (InterruptedException e) {
// Ignore
}
}
/**
* 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 snet
* @return true if all the data has been sent right away, and false
* otherwise
*/
public boolean add(SendfileData data) {
// 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);
data.pos = data.start;
// Set the socket to nonblocking mode
Socket.timeoutSet(data.socket, 0);
while (true) {
long nw = Socket.sendfilen(data.socket, data.fd,
data.pos, data.end - data.pos, 0);
if (nw < 0) {
if (!(-nw == Status.EAGAIN)) {
Pool.destroy(data.fdpool);
// No need to close socket, this will be done by
// calling code since data.socket == 0
data.socket = 0;
return false;
} else {
// Break the loop and add the socket to poller.
break;
}
} else {
data.pos = data.pos + nw;
if (data.pos >= data.end) {
// Entire file has been sent
Pool.destroy(data.fdpool);
// Set back socket to blocking mode
Socket.timeoutSet(data.socket, soTimeout * 1000);
return true;
}
}
}
} catch (Exception e) {
log.error(sm.getString("endpoint.sendfile.error"), e);
return false;
}
// Add socket to the list. Newly added sockets will wait
// at most for pollTime before being polled
synchronized (this) {
addS.add(data);
addCount++;
this.notify();
}
return false;
}
/**
* 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(new Long(data.socket));
}
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
public void run() {
long maintainTime = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
if (sendfileCount < 1 && addCount < 1) {
synchronized (this) {
while (sendfileCount < 1 && addS.size() < 1 && running) {
// Reset maintain time.
maintainTime = 0;
try {
this.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
}
if (!running) {
break;
}
try {
// Add socket to the poller
if (addCount > 0) {
synchronized (this) {
int successCount = 0;
try {
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(new Long(data.socket), data);
successCount++;
} else {
log.warn(sm.getString("endpoint.sendfile.addfail", "" + rv, Error.strerror(rv)));
// Can't do anything: close the socket right away
destroySocket(data.socket);
}
}
} finally {
sendfileCount += successCount;
addS.clear();
addCount = 0;
}
}
}
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(new Long(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 reponse would be incomplete
destroySocket(state.socket);
continue;
}
// Write some data using sendfile
long nw = Socket.sendfilen(state.socket, state.fd,
state.pos,
state.end - state.pos, 0);
if (nw < 0) {
// Close socket and clear pool
remove(state);
// Close the socket, as the reponse would be incomplete
// This will close the file too.
destroySocket(state.socket);
continue;
}
state.pos = state.pos + nw;
if (state.pos >= state.end) {
remove(state);
switch (state.keepAliveState) {
case NONE: {
// Close the socket since this is
// the end of the not keep-alive request.
destroySocket(state.socket);
break;
}
case PIPELINED: {
// Destroy file descriptor pool, which should close the file
Pool.destroy(state.fdpool);
Socket.timeoutSet(state.socket, soTimeout * 1000);
// Process the pipelined request data
if (!processSocket(state.socket, null)) {
destroySocket(state.socket);
}
break;
}
case OPEN: {
// Destroy file descriptor pool, which should close the file
Pool.destroy(state.fdpool);
Socket.timeoutSet(state.socket, soTimeout * 1000);
// Put the socket back in the poller for
// processing of further requests
getPoller().add(state.socket);
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;
}
log.error(sm.getString("endpoint.poll.fail", "" + errn, Error.strerror(errn)));
// Handle poll critical failure
synchronized (this) {
destroy();
init();
}
continue;
}
}
// Call maintain for the sendfile poller
if (soTimeout > 0 && maintainTime > 1000000L && running) {
rv = Poll.maintain(sendfilePollset, desc, true);
maintainTime = 0;
if (rv > 0) {
for (int n = 0; n < rv; n++) {
// Get the sendfile state
SendfileData state = sendfileData.get(new Long(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
destroySocket(state.socket);
}
}
}
} catch (Throwable t) {
log.error(sm.getString("endpoint.poll.error"), t);
}
}
synchronized (this) {
this.notifyAll();
}
}
}
// ------------------------------------------------ Handler Inner Interface
/**
* Bare bones interface used for socket processing. Per thread data is to be
* stored in the ThreadWithAttributes extra folders, or alternately in
* thread local fields.
*/
public interface Handler {
public enum SocketState {
OPEN, CLOSED, LONG, SENDFILE
}
public SocketState process(long socket);
public SocketState event(long socket, SocketStatus status);
}
// ------------------------------------------------- WorkerStack Inner Class
public class WorkerStack {
protected Worker[] workers = null;
protected int end = 0;
public WorkerStack(int size) {
workers = new Worker[size];
}
/**
* Put the worker into the queue. If the queue is full (for example if
* the queue has been reduced in size) the worker will be dropped.
*
* @param worker the worker to be appended to the queue (first
* element).
*/
public void push(Worker worker) {
if (end < workers.length) {
workers[end++] = worker;
} else {
curThreads--;
}
}
/**
* Get the first object out of the queue. Return null if the queue
* is empty.
*/
public Worker pop() {
if (end > 0) {
return workers[--end];
}
return null;
}
/**
* Get the first object out of the queue, Return null if the queue
* is empty.
*/
public Worker peek() {
return workers[end];
}
/**
* Is the queue empty?
*/
public boolean isEmpty() {
return (end == 0);
}
/**
* How many elements are there in this queue?
*/
public int size() {
return (end);
}
/**
* Resize the queue. If there are too many objects in the queue for the
* new size, drop the excess.
*
* @param newSize
*/
public void resize(int newSize) {
Worker[] newWorkers = new Worker[newSize];
int len = workers.length;
if (newSize < len) {
len = newSize;
}
System.arraycopy(workers, 0, newWorkers, 0, len);
workers = newWorkers;
}
}
// ---------------------------------------------- SocketProcessor 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.
*/
protected class SocketWithOptionsProcessor implements Runnable {
protected long socket = 0;
public SocketWithOptionsProcessor(long socket) {
this.socket = socket;
}
public void run() {
if (!deferAccept) {
if (setSocketOptions(socket)) {
getPoller().add(socket);
} else {
// Close socket and pool
destroySocket(socket);
socket = 0;
}
} else {
// Process the request from this socket
Long key = Long.valueOf(socket);
Object lock = locks.get(key);
synchronized (lock) {
if (!setSocketOptions(socket)
|| handler.process(socket) == Handler.SocketState.CLOSED) {
// Close socket and pool
destroySocket(socket);
socket = 0;
}
}
}
}
}
// ---------------------------------------------- SocketProcessor Inner Class
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketProcessor implements Runnable {
protected long socket = 0;
public SocketProcessor(long socket) {
this.socket = socket;
}
public void run() {
Long key = Long.valueOf(socket);
Object lock = locks.get(key);
synchronized (lock) {
// Process the request from this socket
if (handler.process(socket) == Handler.SocketState.CLOSED) {
// Close socket and pool
destroySocket(socket);
socket = 0;
}
}
}
}
// --------------------------------------- SocketEventProcessor Inner Class
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketEventProcessor implements Runnable {
protected long socket = 0;
protected SocketStatus status = null;
public SocketEventProcessor(long socket, SocketStatus status) {
this.socket = socket;
this.status = status;
}
public void run() {
Long key = Long.valueOf(socket);
Object lock = locks.get(key);
synchronized (lock) {
// Process the request from this socket
if (handler.event(socket, status) == Handler.SocketState.CLOSED) {
// Close socket and pool
destroySocket(socket);
socket = 0;
}
}
}
}
}