/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.network.listen; import java.io.IOException; import java.net.ConnectException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.PostConstruct; import com.caucho.config.ConfigException; import com.caucho.config.Configurable; import com.caucho.config.program.ConfigProgram; import com.caucho.config.types.Period; import com.caucho.env.thread.ThreadPool; import com.caucho.lifecycle.Lifecycle; import com.caucho.management.server.PortMXBean; import com.caucho.management.server.TcpConnectionInfo; import com.caucho.server.cluster.Server; import com.caucho.server.util.CauchoSystem; import com.caucho.util.Alarm; import com.caucho.util.AlarmListener; import com.caucho.util.FreeList; import com.caucho.util.Friend; import com.caucho.util.L10N; import com.caucho.vfs.JsseSSLFactory; import com.caucho.vfs.QJniServerSocket; import com.caucho.vfs.QServerSocket; import com.caucho.vfs.QSocket; import com.caucho.vfs.ReadStream; import com.caucho.vfs.SSLFactory; /** * Represents a protocol connection. */ @Configurable public class TcpSocketLinkListener { private static final L10N L = new L10N(TcpSocketLinkListener.class); private static final Logger log = Logger.getLogger(TcpSocketLinkListener.class.getName()); private static final int ACCEPT_IDLE_MIN = 16; private static final int ACCEPT_IDLE_MAX = 64; private static final int ACCEPT_THROTTLE_LIMIT = 1024; private static final long ACCEPT_THROTTLE_SLEEP_TIME = 0; private static final int KEEPALIVE_MAX = 65536; private final AtomicInteger _connectionCount = new AtomicInteger(); // started at 128, but that seems wasteful since the active threads // themselves are buffering the free connections private FreeList<TcpSocketLink> _idleConn = new FreeList<TcpSocketLink>(32); // The owning server // private ProtocolDispatchServer _server; private ThreadPool _threadPool = ThreadPool.getThreadPool(); private SocketLinkThreadLauncher _launcher; private ClassLoader _classLoader = Thread.currentThread().getContextClassLoader(); // The id private String _serverId = ""; // The address private String _address; // The port private int _port = -1; // URL for debugging private String _url; // The protocol private Protocol _protocol; // The SSL factory, if any private SSLFactory _sslFactory; // Secure override for load-balancers/proxies private boolean _isSecure; private InetAddress _socketAddress; private int _acceptListenBacklog = 4000; private int _connectionMax = 1024 * 1024; private int _keepaliveMax = -1; private long _keepaliveTimeMax = 10 * 60 * 1000L; private long _keepaliveTimeout = 120 * 1000L; private boolean _isKeepaliveSelectEnable = true; private long _keepaliveSelectThreadTimeout = 1000; // default timeout private long _socketTimeout = 120 * 1000L; private long _suspendReaperTimeout = 60000L; private long _suspendTimeMax = 600 * 1000L; // after for 120s start checking for EOF on comet requests private long _suspendCloseTimeMax = 120 * 1000L; private long _requestTimeout = -1; private boolean _tcpNoDelay = true; private boolean _isEnableJni = true; // The virtual host name private String _virtualHost; private final SocketLinkAdmin _admin = new SocketLinkAdmin(this); // the server socket private QServerSocket _serverSocket; // the throttle private Throttle _throttle; // the selection manager private AbstractSelectManager _selectManager; // active set of all connections private Set<TcpSocketLink> _activeConnectionSet = Collections.synchronizedSet(new HashSet<TcpSocketLink>()); private final AtomicInteger _activeConnectionCount = new AtomicInteger(); // server push (comet) suspend set private Set<TcpSocketLink> _suspendConnectionSet = Collections.synchronizedSet(new HashSet<TcpSocketLink>()); // active requests that are closing after the request like an access-log // but should not trigger a new thread launch. private final AtomicInteger _shutdownRequestCount = new AtomicInteger(); // reaper alarm for timed out comet requests private Alarm _suspendAlarm; // statistics private final AtomicLong _lifetimeRequestCount = new AtomicLong(); private final AtomicLong _lifetimeKeepaliveCount = new AtomicLong(); private final AtomicLong _lifetimeClientDisconnectCount = new AtomicLong(); private final AtomicLong _lifetimeRequestTime = new AtomicLong(); private final AtomicLong _lifetimeReadBytes = new AtomicLong(); private final AtomicLong _lifetimeWriteBytes = new AtomicLong(); // total keepalive private AtomicInteger _keepaliveAllocateCount = new AtomicInteger(); // thread-based private AtomicInteger _keepaliveThreadCount = new AtomicInteger(); // True if the port has been bound private final AtomicBoolean _isBind = new AtomicBoolean(); private final AtomicBoolean _isPostBind = new AtomicBoolean(); // The port lifecycle private final Lifecycle _lifecycle = new Lifecycle(); public TcpSocketLinkListener() { if (CauchoSystem.is64Bit()) { // on 64-bit machines we can use more threads before parking in nio _keepaliveSelectThreadTimeout = 60000; } _launcher = new SocketLinkThreadLauncher(this); if (Alarm.isTest()) { _launcher.setIdleMin(2); _launcher.setIdleMax(ACCEPT_IDLE_MAX); } else { _launcher.setIdleMin(ACCEPT_IDLE_MIN); _launcher.setIdleMax(ACCEPT_IDLE_MAX); } _launcher.setThrottleLimit(ACCEPT_THROTTLE_LIMIT); _launcher.setThrottleSleepTime(ACCEPT_THROTTLE_SLEEP_TIME); } /** * Sets the id. */ // exists only for QA regressions @Deprecated public void setId(String id) { } public String getDebugId() { return getUrl(); } public ClassLoader getClassLoader() { return _classLoader; } public PortMXBean getAdmin() { return _admin; } /** * Set protocol. */ public void setProtocol(Protocol protocol) throws ConfigException { _protocol = protocol; } /** * Returns the protocol handler responsible for generating protocol-specific * ProtocolConnections. */ public Protocol getProtocol() { return _protocol; } /** * Gets the protocol name. */ public String getProtocolName() { if (_protocol != null) return _protocol.getProtocolName(); else return null; } /** * Sets the address */ @Configurable public void setAddress(String address) throws UnknownHostException { if ("*".equals(address)) address = null; _address = address; if (address != null) _socketAddress = InetAddress.getByName(address); } /** * Gets the IP address */ public String getAddress() { return _address; } /** * @deprecated */ public void setHost(String address) throws UnknownHostException { setAddress(address); } /** * Sets the port. */ @Configurable public void setPort(int port) { _port = port; } /** * Gets the port. */ public int getPort() { return _port; } /** * Gets the local port (for ephemeral ports) */ public int getLocalPort() { if (_serverSocket != null) return _serverSocket.getLocalPort(); else return _port; } /** * Sets the virtual host for IP-based virtual host. */ @Configurable public void setVirtualHost(String host) { _virtualHost = host; } /** * Gets the virtual host for IP-based virtual host. */ public String getVirtualHost() { return _virtualHost; } /** * Sets the SSL factory */ public void setSSL(SSLFactory factory) { _sslFactory = factory; } /** * Sets the SSL factory */ @Configurable public SSLFactory createOpenssl() throws ConfigException { try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class<?> cl = Class.forName("com.caucho.vfs.OpenSSLFactory", false, loader); _sslFactory = (SSLFactory) cl.newInstance(); return _sslFactory; } catch (Throwable e) { e.printStackTrace(); log.log(Level.FINER, e.toString(), e); throw new ConfigException(L.l("<openssl> requires Resin Professional. See http://www.caucho.com for more information."), e); } } /** * Sets the SSL factory */ public JsseSSLFactory createJsse() { // should probably check that openssl exists return new JsseSSLFactory(); } /** * Sets the SSL factory */ public void setJsseSsl(JsseSSLFactory factory) { _sslFactory = factory; } /** * Gets the SSL factory. */ public SSLFactory getSSL() { return _sslFactory; } /** * Returns true for ssl. */ public boolean isSSL() { return _sslFactory != null; } /** * Sets true for secure */ @Configurable public void setSecure(boolean isSecure) { _isSecure = isSecure; } /** * Return true for secure */ public boolean isSecure() { return _isSecure || _sslFactory != null; } /** * Sets the server socket. */ public void setServerSocket(QServerSocket socket) { _serverSocket = socket; } // // Configuration/Tuning // /** * Sets the minimum spare listen. */ @Configurable public void setAcceptThreadMin(int minSpare) throws ConfigException { if (minSpare < 1) throw new ConfigException(L.l("accept-thread-min must be at least 1.")); _launcher.setIdleMin(minSpare); } /** * The minimum spare threads. */ public int getAcceptThreadMin() { return _launcher.getIdleMin(); } /** * Sets the minimum spare listen. */ @Configurable public void setAcceptThreadMax(int maxSpare) throws ConfigException { if (maxSpare < 1) throw new ConfigException(L.l("accept-thread-max must be at least 1.")); _launcher.setIdleMax(maxSpare); } /** * The maximum spare threads. */ public int getAcceptThreadMax() { return _launcher.getIdleMax(); } /** * Sets the minimum spare idle timeout. */ @Configurable public void setAcceptThreadIdleTimeout(Period timeout) throws ConfigException { _launcher.setIdleTimeout(timeout.getPeriod()); } /** * Sets the minimum spare idle timeout. */ public long getAcceptThreadIdleTimeout() throws ConfigException { return _launcher.getIdleTimeout(); } // // launcher throttle configuration // /** * Sets the throttle period. */ public void setThrottlePeriod(long period) { _launcher.setThrottlePeriod(period); } /** * Sets the throttle limit. */ public void setThrottleLimit(int limit) { _launcher.setThrottleLimit(limit); } /** * Sets the throttle sleep time. */ public void setThrottleSleepTime(long period) { _launcher.setThrottleSleepTime(period); } /** * Sets the operating system listen backlog */ @Configurable public void setAcceptListenBacklog(int listen) throws ConfigException { if (listen < 1) throw new ConfigException(L.l("accept-listen-backlog must be at least 1.")); _acceptListenBacklog = listen; } /** * The operating system listen backlog */ public int getAcceptListenBacklog() { return _acceptListenBacklog; } /** * Sets the connection max. */ @Configurable public void setConnectionMax(int max) { _connectionMax = max; } /** * Gets the connection max. */ public int getConnectionMax() { return _connectionMax; } /** * Sets the read/write timeout for the accepted sockets. */ @Configurable public void setSocketTimeout(Period period) { _socketTimeout = period.getPeriod(); } /** * Sets the read/write timeout for the accepted sockets. */ public void setSocketTimeoutMillis(long timeout) { _socketTimeout = timeout; } /** * Gets the read timeout for the accepted sockets. */ public long getSocketTimeout() { return _socketTimeout; } public void setRequestTimeout(Period period) { _requestTimeout = period.getPeriod(); } /** * Returns the max time for a request. */ public long getRequestTimeout() { return _requestTimeout; } /** * Gets the tcp-no-delay property */ public boolean getTcpNoDelay() { return _tcpNoDelay; } /** * Sets the tcp-no-delay property */ @Configurable public void setTcpNoDelay(boolean tcpNoDelay) { _tcpNoDelay = tcpNoDelay; } /** * Configures the throttle. */ @Configurable public void setThrottleConcurrentMax(int max) { Throttle throttle = createThrottle(); if (throttle != null) throttle.setMaxConcurrentRequests(max); } /** * Configures the throttle. */ public long getThrottleConcurrentMax() { if (_throttle != null) return _throttle.getMaxConcurrentRequests(); else return -1; } public void setEnableJni(boolean isEnableJni) { _isEnableJni = isEnableJni; } public boolean isJniEnabled() { if (_serverSocket != null) return _serverSocket.isJni(); else return false; } private Throttle createThrottle() { if (_throttle == null) { _throttle = Throttle.createPro(); if (_throttle == null && Server.getCurrent() != null && ! Server.getCurrent().isWatchdog()) throw new ConfigException(L.l("throttle configuration requires Resin Professional")); } return _throttle; } // // compat config // /** * Sets the keepalive max. */ public void setKeepaliveMax(int max) { _keepaliveMax = max; } /** * Gets the keepalive max. */ public int getKeepaliveMax() { return _keepaliveMax; } /** * Sets the keepalive max. */ public void setKeepaliveConnectionTimeMax(Period period) { _keepaliveTimeMax = period.getPeriod(); } /** * Gets the keepalive max. */ public long getKeepaliveConnectionTimeMax() { return _keepaliveTimeMax; } public void setKeepaliveConnectionTimeMaxMillis(long timeout) { _keepaliveTimeMax = timeout; } /** * Gets the suspend max. */ public long getSuspendTimeMax() { return _suspendTimeMax; } public void setSuspendTimeMax(Period period) { _suspendTimeMax = period.getPeriod(); } public void setSuspendReaperTimeout(Period period) { _suspendReaperTimeout = period.getPeriod(); } public void setKeepaliveTimeout(Period period) { setKeepaliveTimeoutMillis(period.getPeriod()); } public long getKeepaliveTimeout() { return _keepaliveTimeout; } public void setKeepaliveTimeoutMillis(long timeout) { _keepaliveTimeout = timeout; } public boolean isKeepaliveSelectEnabled() { return _isKeepaliveSelectEnable; } public void setKeepaliveSelectEnabled(boolean isKeepaliveSelect) { _isKeepaliveSelectEnable = isKeepaliveSelect; } public void setKeepaliveSelectEnable(boolean isKeepaliveSelect) { setKeepaliveSelectEnabled(isKeepaliveSelect); } public void setKeepaliveSelectMax(int max) { } public long getKeepaliveSelectThreadTimeout() { return _keepaliveSelectThreadTimeout; } public long getKeepaliveThreadTimeout() { return _keepaliveSelectThreadTimeout; } public void setKeepaliveSelectThreadTimeout(Period period) { setKeepaliveSelectThreadTimeoutMillis(period.getPeriod()); } public void setKeepaliveThreadTimeout(Period period) { setKeepaliveSelectThreadTimeoutMillis(period.getPeriod()); } public void setKeepaliveSelectThreadTimeoutMillis(long timeout) { _keepaliveSelectThreadTimeout = timeout; } public long getBlockingTimeoutForSelect() { long timeout = _keepaliveSelectThreadTimeout; if (timeout <= 10) return timeout; else if (_threadPool.getFreeThreadCount() < 64) return 10; else return timeout; } public int getKeepaliveSelectMax() { if (getSelectManager() != null) return getSelectManager().getSelectMax(); else return -1; } /** * Ignore unknown tags. * * server/0940 */ @Configurable public void addContentProgram(ConfigProgram program) { } /** * Returns the thread launcher for the link. */ SocketLinkThreadLauncher getLauncher() { return _launcher; } ThreadPool getThreadPool() { return _threadPool; } // // statistics // /** * Returns the thread count. */ public int getThreadCount() { return _launcher.getThreadCount(); } /** * Returns the active thread count. */ public int getActiveThreadCount() { return _launcher.getThreadCount() - _launcher.getIdleCount(); } /** * Returns the count of idle threads. */ public int getIdleThreadCount() { return _launcher.getIdleCount(); } /** * Returns the count of start threads. */ public int getStartThreadCount() { return _launcher.getStartingCount(); } /** * Returns the number of keepalive connections */ public int getKeepaliveCount() { return _keepaliveAllocateCount.get(); } public Lifecycle getLifecycleState() { return _lifecycle; } public boolean isAfterBind() { return _isBind.get(); } /** * Returns true if the port is active. */ public boolean isActive() { return _lifecycle.isActive(); } /** * Returns the active connections. */ public int getActiveConnectionCount() { return getActiveThreadCount(); } /** * Returns the keepalive connections. */ public int getKeepaliveConnectionCount() { return getKeepaliveCount(); } /** * Returns the number of keepalive connections */ public int getKeepaliveThreadCount() { return _keepaliveThreadCount.get(); } /** * Returns the number of connections in the select. */ public int getSelectConnectionCount() { if (_selectManager != null) return _selectManager.getSelectCount(); else return -1; } /** * Returns the server socket class name for debugging. */ public String getServerSocketClassName() { QServerSocket ss = _serverSocket; if (ss != null) return ss.getClass().getName(); else return null; } /** * Initializes the port. */ @PostConstruct public void init() throws ConfigException { if (! _lifecycle.toInit()) return; } public String getUrl() { if (_url == null) { StringBuilder url = new StringBuilder(); if (_protocol != null) url.append(_protocol.getProtocolName()); else url.append("unknown"); url.append("://"); if (getAddress() != null) url.append(getAddress()); else url.append("*"); url.append(":"); url.append(getPort()); if (_serverId != null && ! "".equals(_serverId)) { url.append("("); url.append(_serverId); url.append(")"); } _url = url.toString(); } return _url; } /** * Starts the port listening. */ public void bind() throws Exception { if (_isBind.getAndSet(true)) return; if (_protocol == null) throw new IllegalStateException(L.l("'{0}' must have a configured protocol before starting.", this)); // server 1e07 if (_port < 0) return; if (_throttle == null) _throttle = new Throttle(); if (_serverSocket != null) { if (_address != null) log.info("listening to " + _address + ":" + _serverSocket.getLocalPort()); else log.info("listening to " + _serverSocket.getLocalPort()); } else if (_sslFactory != null && _socketAddress != null) { _serverSocket = _sslFactory.create(_socketAddress, _port); log.info(_protocol.getProtocolName() + "s listening to " + _socketAddress.getHostName() + ":" + _port); } else if (_sslFactory != null) { if (_address == null) { _serverSocket = _sslFactory.create(null, _port); log.info(_protocol.getProtocolName() + "s listening to *:" + _port); } else { InetAddress addr = InetAddress.getByName(_address); _serverSocket = _sslFactory.create(addr, _port); log.info(_protocol.getProtocolName() + "s listening to " + _address + ":" + _port); } } else if (_socketAddress != null) { _serverSocket = QJniServerSocket.create(_socketAddress, _port, _acceptListenBacklog, _isEnableJni); log.info(_protocol.getProtocolName() + " listening to " + _socketAddress.getHostName() + ":" + _serverSocket.getLocalPort()); } else { _serverSocket = QJniServerSocket.create(null, _port, _acceptListenBacklog, _isEnableJni); log.info(_protocol.getProtocolName() + " listening to *:" + _serverSocket.getLocalPort()); } assert(_serverSocket != null); postBind(); } /** * Starts the port listening. */ public void bind(QServerSocket ss) throws Exception { if (ss == null) throw new NullPointerException(); _isBind.set(true); if (_protocol == null) throw new IllegalStateException(L.l("'{0}' must have a configured protocol before starting.", this)); if (_throttle == null) _throttle = new Throttle(); _serverSocket = ss; String scheme = _protocol.getProtocolName(); if (_address != null) log.info(scheme + " listening to " + _address + ":" + _port); else log.info(scheme + " listening to *:" + _port); if (_sslFactory != null) _serverSocket = _sslFactory.bind(_serverSocket); } public void postBind() { if (_isPostBind.getAndSet(true)) return; if (_serverSocket == null) return; if (_tcpNoDelay) _serverSocket.setTcpNoDelay(_tcpNoDelay); _serverSocket.setConnectionSocketTimeout((int) getSocketTimeout()); if (_serverSocket.isJni()) { SocketPollService pollService = SocketPollService.getCurrent(); if (pollService != null && isKeepaliveSelectEnabled()) { _selectManager = pollService.getSelectManager(); } } if (_keepaliveMax < 0 && _selectManager != null) _keepaliveMax = _selectManager.getSelectMax(); if (_keepaliveMax < 0) _keepaliveMax = KEEPALIVE_MAX; _admin.register(); } /** * binds for the watchdog. */ public QServerSocket bindForWatchdog() throws java.io.IOException { QServerSocket ss; // use same method for ports for testability reasons /* if (_port >= 1024) return null; else */ if (_sslFactory instanceof JsseSSLFactory) { if (_port < 1024) { log.warning(this + " cannot bind jsse in watchdog"); } return null; } if (_socketAddress != null) { ss = QJniServerSocket.createJNI(_socketAddress, _port); if (ss == null) return null; log.fine(this + " watchdog binding to " + _socketAddress.getHostName() + ":" + _port); } else { ss = QJniServerSocket.createJNI(null, _port); if (ss == null) return null; log.fine(this + " watchdog binding to *:" + _port); } if (! ss.isJni()) { ss.close(); return ss; } if (_tcpNoDelay) ss.setTcpNoDelay(_tcpNoDelay); ss.setConnectionSocketTimeout((int) getSocketTimeout()); return ss; } /** * Starts the port listening. */ public void start() throws Exception { if (_port < 0) return; if (! _lifecycle.toStarting()) return; boolean isValid = false; try { bind(); postBind(); enable(); _launcher.start(); _suspendAlarm = new Alarm(new SuspendReaper()); _suspendAlarm.queue(_suspendReaperTimeout); isValid = true; } finally { if (! isValid) close(); } } public boolean isEnabled() { return _lifecycle.isActive(); } /** * Starts the port listening for new connections. */ public void enable() { if (_lifecycle.toActive()) { if (_serverSocket != null) { _serverSocket.listen(_acceptListenBacklog); } } } /** * Stops the port from listening for new connections. */ public void disable() { if (_lifecycle.toStop()) { if (_serverSocket != null) _serverSocket.listen(0); if (_port < 0) { } else if (_address != null) log.info(_protocol.getProtocolName() + " disabled " + _address + ":" + getLocalPort()); else log.info(_protocol.getProtocolName() + " disabled *:" + getLocalPort()); } } /** * returns the connection info for jmx */ TcpConnectionInfo []connectionInfo() { TcpSocketLink []connections; connections = new TcpSocketLink[_activeConnectionSet.size()]; _activeConnectionSet.toArray(connections); long now = Alarm.getExactTime(); TcpConnectionInfo []infoList = new TcpConnectionInfo[connections.length]; for (int i = 0 ; i < connections.length; i++) { TcpSocketLink conn = connections[i]; long requestTime = -1; long startTime = conn.getRequestStartTime(); if (conn.isRequestActive() && startTime > 0) requestTime = now - startTime; TcpConnectionInfo info = new TcpConnectionInfo(conn.getId(), conn.getThreadId(), getAddress() + ":" + getPort(), conn.getState().toString(), requestTime); infoList[i] = info; } return infoList; } /** * returns the select manager. */ public AbstractSelectManager getSelectManager() { return _selectManager; } /** * Accepts a new connection. */ @Friend(TcpSocketLink.class) boolean accept(QSocket socket) { try { while (! isClosed()) { Thread.interrupted(); if (_serverSocket.accept(socket)) { //System.out.println("REMOTE: " + socket.getRemotePort()); if (_throttle.accept(socket)) { return true; } else { socket.close(); } } } } catch (Throwable e) { if (_lifecycle.isActive() && log.isLoggable(Level.FINER)) log.log(Level.FINER, e.toString(), e); } return false; } /** * Notification when a socket closes. */ void closeSocket(QSocket socket) { if (_throttle != null) _throttle.close(socket); } /** * request threads in a shutdown, but not yet idle. */ void requestShutdownBegin() { _shutdownRequestCount.incrementAndGet(); } /** * request threads in a shutdown, but not yet idle. */ void requestShutdownEnd() { _shutdownRequestCount.decrementAndGet(); } /** * Allocates a keepalive for the connection. * * @param connectionStartTime - when the connection's accept occurred. */ boolean isKeepaliveAllowed(long connectionStartTime) { if (! _lifecycle.isActive()) return false; else if (connectionStartTime + _keepaliveTimeMax < Alarm.getCurrentTime()) return false; else if (_keepaliveMax <= _keepaliveAllocateCount.get()) return false; else return true; } /** * Marks the keepalive allocation as starting. * Only called from ConnectionState. */ void keepaliveAllocate() { _keepaliveAllocateCount.incrementAndGet(); } /** * Marks the keepalive allocation as ending. * Only called from ConnectionState. */ void keepaliveFree() { int value = _keepaliveAllocateCount.decrementAndGet(); if (value < 0) { System.out.println("FAILED keep-alive; " + value); Thread.dumpStack(); } } /** * Reads data from a keepalive connection */ int keepaliveThreadRead(ReadStream is) throws IOException { if (isClosed()) return -1; int available = is.getBufferAvailable(); if (available > 0) { return available; } long timeout = getKeepaliveTimeout(); // boolean isSelectManager = getServer().isSelectManagerEnabled(); if (isKeepaliveSelectEnabled() && _selectManager != null) { long selectTimeout = getBlockingTimeoutForSelect(); if (selectTimeout < timeout) timeout = selectTimeout; } if (getSocketTimeout() < timeout) timeout = getSocketTimeout(); /* if (timeout < 0) timeout = 0; */ if (timeout <= 0) return 0; // server/2l02 _keepaliveThreadCount.incrementAndGet(); try { int result = is.fillWithTimeout(timeout); if (isClosed()) { return -1; } return result; } catch (IOException e) { if (isClosed()) { log.log(Level.FINEST, e.toString(), e); return -1; } throw e; } finally { _keepaliveThreadCount.decrementAndGet(); } } /** * Suspends the controller (for comet-style ajax) * * @return true if the connection was added to the suspend list */ @Friend(SocketLinkState.class) void cometSuspend(TcpSocketLink conn) { _suspendConnectionSet.add(conn); } /** * Remove from suspend list. */ @Friend(SocketLinkState.class) boolean cometDetach(TcpSocketLink conn) { return _suspendConnectionSet.remove(conn); } void duplexKeepaliveBegin() { } void duplexKeepaliveEnd() { } /** * Returns true if the port is closed. */ public boolean isClosed() { return _lifecycle.getState().isDestroyed(); } // // statistics // /** * Returns the number of connections */ public int getConnectionCount() { return _activeConnectionCount.get(); } /** * Returns the number of comet connections. */ public int getCometIdleCount() { return _suspendConnectionSet.size(); } /** * Returns the number of duplex connections. */ public int getDuplexCount() { return 0; } void addLifetimeRequestCount() { _lifetimeRequestCount.incrementAndGet(); } public long getLifetimeRequestCount() { return _lifetimeRequestCount.get(); } void addLifetimeKeepaliveCount() { _lifetimeKeepaliveCount.incrementAndGet(); } public long getLifetimeKeepaliveCount() { return _lifetimeKeepaliveCount.get(); } void addLifetimeClientDisconnectCount() { _lifetimeClientDisconnectCount.incrementAndGet(); } public long getLifetimeClientDisconnectCount() { return _lifetimeClientDisconnectCount.get(); } void addLifetimeRequestTime(long time) { _lifetimeRequestTime.addAndGet(time); } public long getLifetimeRequestTime() { return _lifetimeRequestTime.get(); } void addLifetimeReadBytes(long bytes) { _lifetimeReadBytes.addAndGet(bytes); } public long getLifetimeReadBytes() { return _lifetimeReadBytes.get(); } void addLifetimeWriteBytes(long bytes) { _lifetimeWriteBytes.addAndGet(bytes); } public long getLifetimeWriteBytes() { return _lifetimeWriteBytes.get(); } /** * Find the TcpConnection based on the thread id (for admin) */ public TcpSocketLink findConnectionByThreadId(long threadId) { ArrayList<TcpSocketLink> connList = new ArrayList<TcpSocketLink>(_activeConnectionSet); for (TcpSocketLink conn : connList) { if (conn.getThreadId() == threadId) return conn; } return null; } TcpSocketLink allocateConnection() throws IOException { TcpSocketLink startConn = _idleConn.allocate(); if (startConn == null) { int connId = _connectionCount.incrementAndGet(); QSocket socket = _serverSocket.createSocket(); startConn = new TcpSocketLink(connId, this, socket); } _activeConnectionSet.add(startConn); _activeConnectionCount.incrementAndGet(); return startConn; } /** * Closes the stats for the connection. */ @Friend(TcpSocketLink.class) void closeConnection(TcpSocketLink conn) { if (_activeConnectionSet.remove(conn)) { _activeConnectionCount.decrementAndGet(); } else if (! isClosed()){ Thread.dumpStack(); } _launcher.wake(); } /** * Frees the connection to the idle pool. * * only called from ConnectionState */ @Friend(TcpSocketLink.class) void free(TcpSocketLink conn) { _idleConn.free(conn); } /** * Shuts the Port down. The server gives connections 30 * seconds to complete. */ public void close() { if (! _lifecycle.toDestroy()) return; if (log.isLoggable(Level.FINE)) log.fine(this + " closing"); _launcher.destroy(); Alarm suspendAlarm = _suspendAlarm; _suspendAlarm = null; if (suspendAlarm != null) suspendAlarm.dequeue(); QServerSocket serverSocket = _serverSocket; _serverSocket = null; _selectManager = null; AbstractSelectManager selectManager = null; /* if (_server != null) { selectManager = _server.getSelectManager(); _server.initSelectManager(null); } */ InetAddress localAddress = null; int localPort = 0; if (serverSocket != null) { localAddress = serverSocket.getLocalAddress(); localPort = serverSocket.getLocalPort(); } // close the server socket if (serverSocket != null) { try { serverSocket.close(); } catch (Throwable e) { } try { synchronized (serverSocket) { serverSocket.notifyAll(); } } catch (Throwable e) { } } if (selectManager != null) { try { selectManager.close(); } catch (Throwable e) { } } Set<TcpSocketLink> activeSet; synchronized (_activeConnectionSet) { activeSet = new HashSet<TcpSocketLink>(_activeConnectionSet); } for (TcpSocketLink conn : activeSet) { try { conn.requestDestroy(); } catch (Exception e) { log.log(Level.FINEST, e.toString(), e); } } // wake the start thread _launcher.wake(); // Close the socket server socket and send some request to make // sure the Port accept thread is woken and dies. // The ping is before the server socket closes to avoid // confusing the threads // ping the accept port to wake the listening threads if (localPort > 0) { int idleCount = getIdleThreadCount() + getStartThreadCount(); for (int i = 0; i < idleCount + 10; i++) { InetSocketAddress addr; if (getIdleThreadCount() == 0) break; if (localAddress == null || localAddress.getHostAddress().startsWith("0.")) { addr = new InetSocketAddress("127.0.0.1", localPort); connectAndClose(addr); addr = new InetSocketAddress("[::1]", localPort); connectAndClose(addr); } else { addr = new InetSocketAddress(localAddress, localPort); connectAndClose(addr); } try { Thread.sleep(10); } catch (Exception e) { } } } TcpSocketLink conn; while ((conn = _idleConn.allocate()) != null) { conn.requestDestroy(); } log.finest(this + " closed"); } private void connectAndClose(InetSocketAddress addr) { try { Socket socket = new Socket(); socket.connect(addr, 100); socket.close(); } catch (ConnectException e) { } catch (Throwable e) { log.log(Level.FINEST, e.toString(), e); } } public String toURL() { return getUrl(); } /* @Override protected String getThreadName() { return "resin-port-" + getAddress() + ":" + getPort(); } */ @Override public String toString() { if (_url != null) return getClass().getSimpleName() + "[" + _url + "]"; else return getClass().getSimpleName() + "[" + getAddress() + ":" + getPort() + "]"; } public class SuspendReaper implements AlarmListener { private ArrayList<TcpSocketLink> _suspendSet = new ArrayList<TcpSocketLink>(); private ArrayList<TcpSocketLink> _timeoutSet = new ArrayList<TcpSocketLink>(); private ArrayList<TcpSocketLink> _completeSet = new ArrayList<TcpSocketLink>(); @Override public void handleAlarm(Alarm alarm) { try { _suspendSet.clear(); _timeoutSet.clear(); _completeSet.clear(); long now = Alarm.getCurrentTime(); // wake the launcher in case of freeze _launcher.wake(); _suspendSet.addAll(_suspendConnectionSet); for (int i = _suspendSet.size() - 1; i >= 0; i--) { TcpSocketLink conn = _suspendSet.get(i); if (conn.getIdleExpireTime() < now) { _timeoutSet.add(conn); continue; } long idleStartTime = conn.getIdleStartTime(); // check periodically for end of file if (idleStartTime + _suspendCloseTimeMax < now && conn.isReadEof()) { _completeSet.add(conn); } } for (int i = _timeoutSet.size() - 1; i >= 0; i--) { TcpSocketLink conn = _timeoutSet.get(i); if (log.isLoggable(Level.FINE)) log.fine(this + " suspend idle timeout " + conn); try { conn.requestCometTimeout(); } catch (Exception e) { log.log(Level.WARNING, conn + ": " + e.getMessage(), e); } } for (int i = _completeSet.size() - 1; i >= 0; i--) { TcpSocketLink conn = _completeSet.get(i); if (log.isLoggable(Level.FINE)) log.fine(this + " async end-of-file " + conn); try { conn.requestCometComplete(); } catch (Exception e) { log.log(Level.WARNING, conn + ": " + e.getMessage(), e); } /* AsyncController async = conn.getAsyncController(); if (async != null) async.complete(); */ // server/1lc2 // conn.wake(); // conn.destroy(); } } catch (Throwable e) { e.printStackTrace(); } finally { if (! isClosed()) { alarm.queue(_suspendReaperTimeout); } } } } }