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