Title: [518] trunk/rails-integration: Implemented Charles idea for performance improvement.
Revision
518
Author
tantalon
Date
2007-04-28 03:23:00 -0400 (Sat, 28 Apr 2007)

Log Message

Implemented Charles idea for performance improvement.
When a request is waiting for a runtime to be created, it will take another runtime if it becomes available earlier.

Modified Paths

Added Paths

Diff

Modified: trunk/rails-integration/samples/helloworld-1.2.3/WEB-INF/web-test.xml (517 => 518)


--- trunk/rails-integration/samples/helloworld-1.2.3/WEB-INF/web-test.xml	2007-04-28 07:16:36 UTC (rev 517)
+++ trunk/rails-integration/samples/helloworld-1.2.3/WEB-INF/web-test.xml	2007-04-28 07:23:00 UTC (rev 518)
@@ -14,7 +14,7 @@
 </context-param>
 <context-param>
   <param-name>jruby.pool.minIdle</param-name>
-  <param-value>0</param-value>
+  <param-value>1</param-value>
 </context-param>
 
 <servlet>

Added: trunk/rails-integration/samples/helloworld-1.2.3/config/war.rb (0 => 518)


--- trunk/rails-integration/samples/helloworld-1.2.3/config/war.rb	                        (rev 0)
+++ trunk/rails-integration/samples/helloworld-1.2.3/config/war.rb	2007-04-28 07:23:00 UTC (rev 518)
@@ -0,0 +1,3 @@
+#compile_ruby true
+#keep_source false
+maven_library 'org.jruby.extras', 'rails-integration', '1.2-SNAPSHOT'

Modified: trunk/rails-integration/src/main/java/org/jruby/webapp/AbstractRailsServlet.java (517 => 518)


--- trunk/rails-integration/src/main/java/org/jruby/webapp/AbstractRailsServlet.java	2007-04-28 07:16:36 UTC (rev 517)
+++ trunk/rails-integration/src/main/java/org/jruby/webapp/AbstractRailsServlet.java	2007-04-28 07:23:00 UTC (rev 518)
@@ -1,8 +1,8 @@
 package org.jruby.webapp;
 import org.apache.commons.pool.ObjectPool;
-import org.apache.commons.pool.impl.GenericObjectPool;
 import org.jruby.RubyException;
 import org.jruby.Ruby;
+import org.jruby.webapp.util.CustomObjectPool;
 import org.jruby.exceptions.RaiseException;
 import java.io.IOException;
 import java.io.PrintStream;
@@ -63,6 +63,14 @@
 		createObjectPool(railsFactory);
 	}
 
+	public void destroy() {
+		try {
+			runtimePool.close();
+		} catch (Exception e) {
+			log("Failed to close runtime pool", e);
+		}
+	}
+
 	private String findGemPath() {
 		if (isStandalone()) {
 			// look for a local copy to override the default
@@ -95,19 +103,10 @@
 	 * Create the pool of JRuby runtimes.
 	 */
 	protected void createObjectPool(RailsFactory railsFactory) {
-		GenericObjectPool.Config config = new GenericObjectPool.Config();
-		// when the server is loaded, pausing for the request makes it worse
-		// instead, we'll just fail and return a message to the user
-		config.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_FAIL;
-		config.maxActive = getIntegerProperty("jruby.pool.maxActive", 4);
-		config.maxIdle = getIntegerProperty("jruby.pool.maxIdle", 4);
-		config.minIdle = getIntegerProperty("jruby.pool.minIdle", 2);
-		config.timeBetweenEvictionRunsMillis = getIntegerProperty("jruby.pool.timeBetweenEvictionRunsMillis", 10000);
-		// create the pool
-		runtimePool = new GenericObjectPool(railsFactory, config);
-		// preload a minimum number of objects
-		Runnable preloader = new Preloader(config.minIdle);
-		new Thread(preloader).start();
+		int maxObjects = getIntegerProperty("jruby.pool.maxActive", 4);
+		int minIdle = getIntegerProperty("jruby.pool.minIdle", 2);
+		int checkInterval = getIntegerProperty("jruby.pool.checkInterval", 100);
+		runtimePool = new CustomObjectPool(railsFactory, maxObjects, minIdle, checkInterval);
 	}
 
 	/**
@@ -190,31 +189,4 @@
 		}
 	}
 
-	/**
-	 * Preload to ensure we have at least a minimum number of objects available,
-	 * the evictor will create more as required if and when it starts
-	 */
-	private class Preloader implements Runnable {
-
-		private int minObjects;
-
-		public Preloader(int minObjects) {
-			this.minObjects = minObjects;
-		}
-
-		public void run() {
-			try {
-				while (runtimePool.getNumIdle() + runtimePool.getNumActive() < minObjects) {
-					runtimePool.addObject();
-					// small delay between starting
-					try {
-						Thread.sleep(100);
-					} catch (InterruptedException ignore) {
-					}
-				}
-			} catch (Exception e) {
-				log("Failed to preload JRuby: " + e.getMessage());
-			}
-		}
-	}
 }

Added: trunk/rails-integration/src/main/java/org/jruby/webapp/util/CustomObjectPool.java (0 => 518)


--- trunk/rails-integration/src/main/java/org/jruby/webapp/util/CustomObjectPool.java	                        (rev 0)
+++ trunk/rails-integration/src/main/java/org/jruby/webapp/util/CustomObjectPool.java	2007-04-28 07:23:00 UTC (rev 518)
@@ -0,0 +1,157 @@
+package org.jruby.webapp.util;
+import org.apache.commons.pool.ObjectPool;
+import org.apache.commons.pool.PoolableObjectFactory;
+import edu.emory.mathcs.backport.java.util.concurrent.BlockingQueue;
+import edu.emory.mathcs.backport.java.util.concurrent.ArrayBlockingQueue;
+import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
+import java.util.NoSuchElementException;
+/**
+ * An object pool which assumes that object creation is slow, and should be done in a separate thread.
+ *
+ * @author Robert Egglestone
+ */
+public class CustomObjectPool implements ObjectPool {
+
+	private static final int DEFAULT_MAX_WAIT = 30000;
+	private static final int DEFAULT_CHECK_INTERVAL = 100;
+	private PoolableObjectFactory factory;
+	private BlockingQueue pool;
+	private int maxObjects;
+	private int minIdle;
+	private int numObjects;
+	private int numActive;
+	private boolean open;
+	private int checkInterval;
+
+	private int maxWait = DEFAULT_MAX_WAIT;
+
+	public CustomObjectPool(PoolableObjectFactory factory, int maxObjects, int minIdle) {
+		this(factory, maxObjects, minIdle, DEFAULT_CHECK_INTERVAL);
+	}
+
+	public CustomObjectPool(PoolableObjectFactory factory, int maxObjects, int minIdle, int checkInterval) {
+		this.open = true;
+		this.checkInterval = checkInterval;
+		this.minIdle = minIdle;
+		this.maxObjects = maxObjects;
+		this.factory = factory;
+		this.pool = new ArrayBlockingQueue(maxObjects);
+
+		if (checkInterval != 0) {
+			new Thread(new PoolSizeManager(), "ObjectPoolManager").start();
+		}
+	}
+
+	public Object borrowObject() throws InterruptedException {
+		Object object = pool.poll(maxWait, TimeUnit.MILLISECONDS);
+		if (object == null) throw new NoSuchElementException("Time out waiting for object");
+
+		synchronized (this) {
+			numActive++;
+		}
+		return object;
+	}
+
+	public void returnObject(Object obj) throws Exception {
+		if (open && pool.offer(obj)) {
+			// added ok
+			synchronized (this) {
+				numActive--;
+			}
+		} else {
+			invalidateObject(obj);
+		}
+	}
+
+	public void invalidateObject(Object obj) throws Exception {
+		try {
+			factory.destroyObject(obj);
+		} finally {
+			synchronized (this) {
+				numActive--;
+				numObjects--;
+			}
+		}
+	}
+
+	public synchronized void addObject() throws Exception {
+		// enforce the constraints, so this only happens if it makes sense to
+		if (numObjects < maxObjects && getNumIdle() < minIdle) {
+			Object object = factory.makeObject();
+			try {
+				pool.add(object);
+				numObjects++;
+			} catch (IllegalStateException e) {
+				factory.destroyObject(object);
+				throw e;
+			}
+		}
+	}
+
+	public void addObjects(int count) throws Exception {
+		for(int i=0; i<count; i++) {
+			addObject();
+		}
+	}
+
+	public synchronized int getNumIdle() {
+		return numObjects - numActive;
+	}
+
+	public synchronized int getNumActive() {
+		return numActive;
+	}
+
+	public int getMinIdle() {
+		return minIdle;
+	}
+
+	public void setMinIdle(int minIdle) {
+		this.minIdle = minIdle;
+	}
+
+	public void setMaxWait(int maxWait) {
+		this.maxWait = maxWait;
+	}
+
+	public int getNumObjects() {
+		return numObjects;
+	}
+
+	public void clear() {
+		throw new UnsupportedOperationException();
+	}
+
+	public synchronized void close() throws Exception {
+		open = false;
+		Object object;
+		while ((object = pool.poll()) != null) {
+			try {
+				factory.destroyObject(object);
+			} catch (Exception e) {
+				System.err.println("Failed to destroy object: " + e.getMessage());
+			}
+		}
+	}
+
+	public void setFactory(PoolableObjectFactory factory) {
+		throw new UnsupportedOperationException("You cannot change the factory after the pool is created");
+	}
+
+	private class PoolSizeManager implements Runnable {
+		public void run() {
+			while (open) {
+				try {
+					addObject();
+					Thread.sleep(checkInterval);
+				} catch (InterruptedException e) {
+					// ignore
+				} catch (Exception e) {
+					// add object failed
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+}

Modified: trunk/rails-integration/src/test/java/org/jruby/webapp/RailsServletTomcat4Test.java (517 => 518)


--- trunk/rails-integration/src/test/java/org/jruby/webapp/RailsServletTomcat4Test.java	2007-04-28 07:16:36 UTC (rev 517)
+++ trunk/rails-integration/src/test/java/org/jruby/webapp/RailsServletTomcat4Test.java	2007-04-28 07:23:00 UTC (rev 518)
@@ -27,7 +27,7 @@
 		Context context = embedded.createContext("/hw", documentRoot.getAbsolutePath());
 		context.addParameter("jruby.pool.maxActive", "1");
 		context.addParameter("jruby.pool.maxIdle", "1");
-		context.addParameter("jruby.pool.minIdle", "0");
+		context.addParameter("jruby.pool.minIdle", "1");
 
 		Wrapper servlet = context.createWrapper();
 		servlet.setLogger(logger);

Added: trunk/rails-integration/src/test/java/org/jruby/webapp/util/CustomObjectPoolTest.java (0 => 518)


--- trunk/rails-integration/src/test/java/org/jruby/webapp/util/CustomObjectPoolTest.java	                        (rev 0)
+++ trunk/rails-integration/src/test/java/org/jruby/webapp/util/CustomObjectPoolTest.java	2007-04-28 07:23:00 UTC (rev 518)
@@ -0,0 +1,168 @@
+package org.jruby.webapp.util;
+import junit.framework.TestCase;
+import org.easymock.MockControl;
+import org.apache.commons.pool.PoolableObjectFactory;
+import java.util.NoSuchElementException;
+/**
+ * Tests for CustomObjectPool.
+ *
+ * @author Robert Egglestone
+ */
+public class CustomObjectPoolTest extends TestCase {
+
+	private MockControl factoryControl;
+	private PoolableObjectFactory factory;
+
+	protected void setUp() throws Exception {
+		super.setUp();
+		factoryControl = MockControl.createStrictControl(PoolableObjectFactory.class);
+		factory = (PoolableObjectFactory)factoryControl.getMock();
+	}
+
+	public void testBasicCreation() throws Exception {
+		factoryControl.expectAndReturn(factory.makeObject(), "1");
+		factoryControl.expectAndReturn(factory.makeObject(), "2");
+		factoryControl.expectAndReturn(factory.makeObject(), "3");
+		factory.destroyObject("1");
+		factory.destroyObject("2");
+		factory.destroyObject("3");
+		factoryControl.replay();
+
+		CustomObjectPool pool = new CustomObjectPool(factory, 3, 3, 0);
+		pool.addObjects(10);
+		pool.close();
+
+		factoryControl.verify();
+	}
+
+	public void testManagerThread() throws Exception {
+		factoryControl.expectAndReturn(factory.makeObject(), "1");
+		factoryControl.expectAndReturn(factory.makeObject(), "2");
+		factoryControl.expectAndReturn(factory.makeObject(), "3");
+		factory.destroyObject("1");
+		factory.destroyObject("2");
+		factory.destroyObject("3");
+		factoryControl.replay();
+
+		CustomObjectPool pool = new CustomObjectPool(factory, 3, 3, 1);
+		Thread.sleep(100);
+		pool.close();
+
+		factoryControl.verify();
+	}
+
+	public void testMinIdle() throws Exception {
+		factoryControl.expectAndReturn(factory.makeObject(), "1");
+		factory.destroyObject("1");
+		factoryControl.expectAndReturn(factory.makeObject(), "2");
+		factory.destroyObject("2");
+		factoryControl.replay();
+
+		CustomObjectPool pool = new CustomObjectPool(factory, 5, 1, 0);
+		pool.addObjects(10);
+		Object object = pool.borrowObject();
+		pool.invalidateObject(object);
+		pool.addObjects(10);
+		pool.close();
+
+		factoryControl.verify();
+	}
+
+	public void testBorrowMany() throws Exception {
+		factoryControl.expectAndReturn(factory.makeObject(), "1");
+		factoryControl.expectAndReturn(factory.makeObject(), "2");
+		factory.destroyObject("2");
+		factory.destroyObject("1");
+		factoryControl.replay();
+
+		CustomObjectPool pool = new CustomObjectPool(factory, 3, 1);
+		Object object1 = pool.borrowObject();
+		// there should be a delay here as an object is created, since object1 doesn't get released
+		Object object2 = pool.borrowObject();
+		pool.returnObject(object2);
+		pool.returnObject(object1);
+		pool.close();
+
+		factoryControl.verify();
+	}
+
+	public void testAddDuringRequest() throws Exception {
+		factoryControl.expectAndReturn(factory.makeObject(), "1");
+		factory.destroyObject("1");
+		factoryControl.replay();
+
+		CustomObjectPool pool = new CustomObjectPool(factory, 3, 1, 0);
+		pool.setMaxWait(200);
+		addSoon(pool);
+		Object object1 = pool.borrowObject();
+		pool.returnObject(object1);
+		pool.close();
+
+		factoryControl.verify();
+	}
+
+	public void testSlowAdd() throws Exception {
+		factoryControl.expectAndReturn(factory.makeObject(), "1");
+		factory.destroyObject("1");
+		factoryControl.replay();
+
+		CustomObjectPool pool = new CustomObjectPool(factory, 3, 1, 0);
+		pool.setMaxWait(200);
+		pool.addObject();
+
+		Object object1 = pool.borrowObject();
+		returnSoon(pool, object1);
+		addSoon(pool);
+
+		Object object2 = pool.borrowObject();
+		pool.returnObject(object2);
+
+		pool.close();
+
+		factoryControl.verify();
+		assertSame(object2, object1);
+	}
+
+	public void testTimeout() throws Exception {
+		factoryControl.replay();
+
+		CustomObjectPool pool = new CustomObjectPool(factory, 3, 0, 0);
+		pool.setMaxWait(100);
+		try {
+			pool.borrowObject();
+			fail("Timeout didn't occur");
+		} catch (NoSuchElementException e) {
+			// expected
+		}
+		pool.close();
+
+		factoryControl.verify();
+	}
+
+	private void addSoon(final CustomObjectPool pool) {
+		new Thread() {
+			public void run() {
+				try {
+					Thread.sleep(100);
+					pool.addObject();
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		}.start();
+	}
+
+	private void returnSoon(final CustomObjectPool pool, final Object object) {
+		new Thread() {
+			public void run() {
+				try {
+					Thread.sleep(50);
+					pool.returnObject(object);
+				} catch (Exception e) {
+					e.printStackTrace();
+				}
+			}
+		}.start();
+	}
+
+}
\ No newline at end of file
_______________________________________________
Jruby-extras-devel mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/jruby-extras-devel

Reply via email to