/* * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net> * Distributed under the terms of either: * - the common development and distribution license (CDDL), v1.0; or * - the GNU Lesser General Public License, v2.1 or later */ package winstone; import java.io.IOException; import java.net.Socket; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Holds the object pooling code for Winstone. Presently this is only responses * and requests, but may increase. * * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a> * @version $Id: ObjectPool.java,v 1.9 2006/11/18 14:56:59 rickknowles Exp $ */ public class ObjectPool implements Runnable { private static final long FLUSH_PERIOD = 60000L; private int STARTUP_REQUEST_HANDLERS_IN_POOL = 5; private int MAX_IDLE_REQUEST_HANDLERS_IN_POOL = 50; private int MAX_REQUEST_HANDLERS_IN_POOL = 1000; private long RETRY_PERIOD = 1000; private int START_REQUESTS_IN_POOL = 10; private int MAX_REQUESTS_IN_POOL = 1000; private int START_RESPONSES_IN_POOL = 10; private int MAX_RESPONSES_IN_POOL = 1000; private List unusedRequestHandlerThreads; private List usedRequestHandlerThreads; private List usedRequestPool; private List unusedRequestPool; private List usedResponsePool; private List unusedResponsePool; private Object requestHandlerSemaphore = new Boolean(true); private Object requestPoolSemaphore = new Boolean(true); private Object responsePoolSemaphore = new Boolean(true); private int threadIndex = 0; private boolean simulateModUniqueId; private boolean saveSessions; private Thread thread; /** * Constructs an instance of the object pool, including handlers, requests * and responses */ public ObjectPool(Map args) throws IOException { this.simulateModUniqueId = WebAppConfiguration.booleanArg(args, "simulateModUniqueId", false); this.saveSessions = WebAppConfiguration.useSavedSessions(args); // Build the initial pool of handler threads this.unusedRequestHandlerThreads = new ArrayList(); this.usedRequestHandlerThreads = new ArrayList(); // Build the request/response pools this.usedRequestPool = new ArrayList(); this.usedResponsePool = new ArrayList(); this.unusedRequestPool = new ArrayList(); this.unusedResponsePool = new ArrayList(); // Get handler pool options if (args.get("handlerCountStartup") != null) { STARTUP_REQUEST_HANDLERS_IN_POOL = Integer.parseInt((String) args .get("handlerCountStartup")); } if (args.get("handlerCountMax") != null) { MAX_IDLE_REQUEST_HANDLERS_IN_POOL = Integer.parseInt((String) args .get("handlerCountMax")); } if (args.get("handlerCountMaxIdle") != null) { MAX_IDLE_REQUEST_HANDLERS_IN_POOL = Integer.parseInt((String) args .get("handlerCountMaxIdle")); } // Start the base set of handler threads for (int n = 0; n < STARTUP_REQUEST_HANDLERS_IN_POOL; n++) { this.unusedRequestHandlerThreads .add(new RequestHandlerThread(this, this.threadIndex++, this.simulateModUniqueId, this.saveSessions)); } // Initialise the request/response pools for (int n = 0; n < START_REQUESTS_IN_POOL; n++) { this.unusedRequestPool.add(new WinstoneRequest()); } for (int n = 0; n < START_RESPONSES_IN_POOL; n++) { this.unusedResponsePool.add(new WinstoneResponse()); } this.thread = new Thread(this, "WinstoneObjectPoolMgmt"); this.thread.setDaemon(true); this.thread.start(); } public void run() { boolean interrupted = false; while (!interrupted) { try { Thread.sleep(FLUSH_PERIOD); removeUnusedRequestHandlers(); } catch (InterruptedException err) { interrupted = true; } } this.thread = null; } private void removeUnusedRequestHandlers() { // Check max idle requestHandler count synchronized (this.requestHandlerSemaphore) { // If we have too many idle request handlers while (this.unusedRequestHandlerThreads.size() > MAX_IDLE_REQUEST_HANDLERS_IN_POOL) { RequestHandlerThread rh = (RequestHandlerThread) this.unusedRequestHandlerThreads.get(0); rh.destroy(); this.unusedRequestHandlerThreads.remove(rh); } } } public void destroy() { synchronized (this.requestHandlerSemaphore) { Collection usedHandlers = new ArrayList(this.usedRequestHandlerThreads); for (Iterator i = usedHandlers.iterator(); i.hasNext();) releaseRequestHandler((RequestHandlerThread) i.next()); Collection unusedHandlers = new ArrayList(this.unusedRequestHandlerThreads); for (Iterator i = unusedHandlers.iterator(); i.hasNext();) ((RequestHandlerThread) i.next()).destroy(); this.unusedRequestHandlerThreads.clear(); } if (this.thread != null) { this.thread.interrupt(); } } /** * Once the socket request comes in, this method is called. It reserves a * request handler, then delegates the socket to that class. When it * finishes, the handler is released back into the pool. */ public void handleRequest(Socket socket, Listener listener) throws IOException, InterruptedException { RequestHandlerThread rh = null; synchronized (this.requestHandlerSemaphore) { // If we have any spare, get it from the pool int unused = this.unusedRequestHandlerThreads.size(); if (unused > 0) { rh = (RequestHandlerThread) this.unusedRequestHandlerThreads.remove(unused - 1); this.usedRequestHandlerThreads.add(rh); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.UsingRHPoolThread", new String[] { "" + this.usedRequestHandlerThreads.size(), "" + this.unusedRequestHandlerThreads.size() }); } // If we are out (and not over our limit), allocate a new one else if (this.usedRequestHandlerThreads.size() < MAX_REQUEST_HANDLERS_IN_POOL) { rh = new RequestHandlerThread(this, this.threadIndex++, this.simulateModUniqueId, this.saveSessions); this.usedRequestHandlerThreads.add(rh); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.NewRHPoolThread", new String[] { "" + this.usedRequestHandlerThreads.size(), "" + this.unusedRequestHandlerThreads.size() }); } // otherwise throw fail message - we've blown our limit else { // Possibly insert a second chance here ? Delay and one retry ? // Remember to release the lock first Logger.log(Logger.WARNING, Launcher.RESOURCES, "ObjectPool.NoRHPoolThreadsRetry"); // socket.close(); // throw new UnavailableException("NoHandlersAvailable"); } } if (rh != null) rh.commenceRequestHandling(socket, listener); else { // Sleep for a set period and try again from the pool Thread.sleep(RETRY_PERIOD); synchronized (this.requestHandlerSemaphore) { if (this.usedRequestHandlerThreads.size() < MAX_REQUEST_HANDLERS_IN_POOL) { rh = new RequestHandlerThread(this, this.threadIndex++, this.simulateModUniqueId, this.saveSessions); this.usedRequestHandlerThreads.add(rh); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.NewRHPoolThread", new String[] { "" + this.usedRequestHandlerThreads.size(), "" + this.unusedRequestHandlerThreads.size() }); } } if (rh != null) rh.commenceRequestHandling(socket, listener); else { Logger.log(Logger.WARNING, Launcher.RESOURCES, "ObjectPool.NoRHPoolThreads"); socket.close(); } } } /** * Release the handler back into the pool */ public void releaseRequestHandler(RequestHandlerThread rh) { synchronized (this.requestHandlerSemaphore) { this.usedRequestHandlerThreads.remove(rh); this.unusedRequestHandlerThreads.add(rh); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.ReleasingRHPoolThread", new String[] { "" + this.usedRequestHandlerThreads.size(), "" + this.unusedRequestHandlerThreads.size() }); } } /** * An attempt at pooling request objects for reuse. */ public WinstoneRequest getRequestFromPool() throws IOException { WinstoneRequest req = null; synchronized (this.requestPoolSemaphore) { // If we have any spare, get it from the pool int unused = this.unusedRequestPool.size(); if (unused > 0) { req = (WinstoneRequest) this.unusedRequestPool.remove(unused - 1); this.usedRequestPool.add(req); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.UsingRequestFromPool", "" + this.unusedRequestPool.size()); } // If we are out, allocate a new one else if (this.usedRequestPool.size() < MAX_REQUESTS_IN_POOL) { req = new WinstoneRequest(); this.usedRequestPool.add(req); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.NewRequestForPool", "" + this.usedRequestPool.size()); } else throw new WinstoneException(Launcher.RESOURCES .getString("ObjectPool.PoolRequestLimitExceeded")); } return req; } public void releaseRequestToPool(WinstoneRequest req) { req.cleanUp(); synchronized (this.requestPoolSemaphore) { this.usedRequestPool.remove(req); this.unusedRequestPool.add(req); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.RequestReleased", "" + this.unusedRequestPool.size()); } } /** * An attempt at pooling request objects for reuse. */ public WinstoneResponse getResponseFromPool() throws IOException { WinstoneResponse rsp = null; synchronized (this.responsePoolSemaphore) { // If we have any spare, get it from the pool int unused = this.unusedResponsePool.size(); if (unused > 0) { rsp = (WinstoneResponse) this.unusedResponsePool.remove(unused - 1); this.usedResponsePool.add(rsp); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.UsingResponseFromPool", "" + this.unusedResponsePool.size()); } // If we are out, allocate a new one else if (this.usedResponsePool.size() < MAX_RESPONSES_IN_POOL) { rsp = new WinstoneResponse(); this.usedResponsePool.add(rsp); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.NewResponseForPool", "" + this.usedResponsePool.size()); } else throw new WinstoneException(Launcher.RESOURCES .getString("ObjectPool.PoolResponseLimitExceeded")); } return rsp; } public void releaseResponseToPool(WinstoneResponse rsp) { rsp.cleanUp(); synchronized (this.responsePoolSemaphore) { this.usedResponsePool.remove(rsp); this.unusedResponsePool.add(rsp); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "ObjectPool.ResponseReleased", "" + this.unusedResponsePool.size()); } } }