/**
* 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.activemq.jms.pool;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.QueueConnectionFactory;
import javax.jms.Session;
import javax.jms.TopicConnectionFactory;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.command.ConnectionId;
import org.apache.activemq.util.Wait;
import org.apache.log4j.Logger;
import org.junit.Test;
/**
* Checks the behavior of the PooledConnectionFactory when the maximum amount of
* sessions is being reached.
*
* Older versions simply block in the call to Connection.getSession(), which
* isn't good. An exception being returned is the better option, so JMS clients
* don't block. This test succeeds if an exception is returned and fails if the
* call to getSession() blocks.
*/
public class PooledConnectionFactoryTest extends JmsPoolTestSupport {
public final static Logger LOG = Logger.getLogger(PooledConnectionFactoryTest.class);
@Test(timeout = 60000)
public void testInstanceOf() throws Exception {
PooledConnectionFactory pcf = new PooledConnectionFactory();
assertTrue(pcf instanceof QueueConnectionFactory);
assertTrue(pcf instanceof TopicConnectionFactory);
pcf.stop();
}
@Test(timeout = 60000)
public void testClearAllConnections() throws Exception {
ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(
"vm://broker1?marshal=false&broker.persistent=false&broker.useJmx=false");
PooledConnectionFactory cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(3);
PooledConnection conn1 = (PooledConnection) cf.createConnection();
PooledConnection conn2 = (PooledConnection) cf.createConnection();
PooledConnection conn3 = (PooledConnection) cf.createConnection();
assertNotSame(conn1.getConnection(), conn2.getConnection());
assertNotSame(conn1.getConnection(), conn3.getConnection());
assertNotSame(conn2.getConnection(), conn3.getConnection());
assertEquals(3, cf.getNumConnections());
cf.clear();
assertEquals(0, cf.getNumConnections());
conn1 = (PooledConnection) cf.createConnection();
conn2 = (PooledConnection) cf.createConnection();
conn3 = (PooledConnection) cf.createConnection();
assertNotSame(conn1.getConnection(), conn2.getConnection());
assertNotSame(conn1.getConnection(), conn3.getConnection());
assertNotSame(conn2.getConnection(), conn3.getConnection());
cf.stop();
}
@Test(timeout = 60000)
public void testMaxConnectionsAreCreated() throws Exception {
ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(
"vm://broker1?marshal=false&broker.persistent=false&broker.useJmx=false");
PooledConnectionFactory cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(3);
PooledConnection conn1 = (PooledConnection) cf.createConnection();
PooledConnection conn2 = (PooledConnection) cf.createConnection();
PooledConnection conn3 = (PooledConnection) cf.createConnection();
assertNotSame(conn1.getConnection(), conn2.getConnection());
assertNotSame(conn1.getConnection(), conn3.getConnection());
assertNotSame(conn2.getConnection(), conn3.getConnection());
assertEquals(3, cf.getNumConnections());
cf.stop();
}
@Test(timeout = 60000)
public void testFactoryStopStart() throws Exception {
ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(
"vm://broker1?marshal=false&broker.persistent=false&broker.useJmx=false");
PooledConnectionFactory cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(1);
PooledConnection conn1 = (PooledConnection) cf.createConnection();
cf.stop();
assertNull(cf.createConnection());
cf.start();
PooledConnection conn2 = (PooledConnection) cf.createConnection();
assertNotSame(conn1.getConnection(), conn2.getConnection());
assertEquals(1, cf.getNumConnections());
cf.stop();
}
@Test(timeout = 60000)
public void testConnectionsAreRotated() throws Exception {
ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(
"vm://broker1?marshal=false&broker.persistent=false&broker.useJmx=false");
PooledConnectionFactory cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(10);
Connection previous = null;
// Front load the pool.
for (int i = 0; i < 10; ++i) {
cf.createConnection();
}
for (int i = 0; i < 100; ++i) {
Connection current = ((PooledConnection) cf.createConnection()).getConnection();
assertNotSame(previous, current);
previous = current;
}
cf.stop();
}
@Test(timeout = 60000)
public void testConnectionsArePooled() throws Exception {
ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory("vm://broker1?marshal=false&broker.persistent=false");
PooledConnectionFactory cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(1);
PooledConnection conn1 = (PooledConnection) cf.createConnection();
PooledConnection conn2 = (PooledConnection) cf.createConnection();
PooledConnection conn3 = (PooledConnection) cf.createConnection();
assertSame(conn1.getConnection(), conn2.getConnection());
assertSame(conn1.getConnection(), conn3.getConnection());
assertSame(conn2.getConnection(), conn3.getConnection());
assertEquals(1, cf.getNumConnections());
cf.stop();
}
@Test(timeout = 60000)
public void testConnectionsArePooledAsyncCreate() throws Exception {
final ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(
"vm://broker1?marshal=false&broker.persistent=false&broker.useJmx=false");
final PooledConnectionFactory cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(1);
final ConcurrentLinkedQueue<PooledConnection> connections = new ConcurrentLinkedQueue<PooledConnection>();
final PooledConnection primary = (PooledConnection) cf.createConnection();
final ExecutorService executor = Executors.newFixedThreadPool(10);
final int numConnections = 100;
for (int i = 0; i < numConnections; ++i) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
connections.add((PooledConnection) cf.createConnection());
} catch (JMSException e) {
}
}
});
}
assertTrue("All connections should have been created.", Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return connections.size() == numConnections;
}
}, TimeUnit.SECONDS.toMillis(10), TimeUnit.MILLISECONDS.toMillis(50)));
executor.shutdown();
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
for (PooledConnection connection : connections) {
assertSame(primary.getConnection(), connection.getConnection());
}
connections.clear();
cf.stop();
}
@Test(timeout = 60000)
public void testConcurrentCreateGetsUniqueConnectionCreateOnDemand() throws Exception {
doTestConcurrentCreateGetsUniqueConnection(false);
}
@Test(timeout = 60000)
public void testConcurrentCreateGetsUniqueConnectionCreateOnStart() throws Exception {
doTestConcurrentCreateGetsUniqueConnection(true);
}
private void doTestConcurrentCreateGetsUniqueConnection(boolean createOnStart) throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setPersistent(false);
brokerService.setUseJmx(false);
brokerService.addConnector("tcp://localhost:0");
brokerService.start();
brokerService.waitUntilStarted();
try {
final int numConnections = 2;
final ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(brokerService.getTransportConnectors().get(0).getPublishableConnectString());
final PooledConnectionFactory cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(numConnections);
cf.setCreateConnectionOnStartup(createOnStart);
cf.start();
final ConcurrentMap<ConnectionId, Connection> connections = new ConcurrentHashMap<ConnectionId, Connection>();
final ExecutorService executor = Executors.newFixedThreadPool(numConnections);
for (int i = 0; i < numConnections; ++i) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
PooledConnection pooled = (PooledConnection) cf.createConnection();
ActiveMQConnection amq = (ActiveMQConnection) pooled.getConnection();
connections.put(amq.getConnectionInfo().getConnectionId(), pooled);
} catch (JMSException e) {
}
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(30, TimeUnit.SECONDS));
assertEquals("Should have all unique connections", numConnections, connections.size());
connections.clear();
cf.stop();
} finally {
brokerService.stop();
}
}
/**
* Tests the behavior of the sessionPool of the PooledConnectionFactory when
* maximum number of sessions are reached.
*/
@Test(timeout = 60000)
public void testCreateSessionDoesNotBlockWhenNotConfiguredTo() throws Exception {
// using separate thread for testing so that we can interrupt the test
// if the call to get a new session blocks.
// start test runner thread
ExecutorService executor = Executors.newSingleThreadExecutor();
final Future<Boolean> result = executor.submit(new TestRunner());
boolean testPassed = Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return result.isDone() && result.get().booleanValue();
}
}, TimeUnit.SECONDS.toMillis(10), TimeUnit.MILLISECONDS.toMillis(50));
if (!testPassed) {
PooledConnectionFactoryTest.LOG.error("2nd call to createSession() " +
"is blocking but should have returned an error instead.");
executor.shutdownNow();
fail("SessionPool inside PooledConnectionFactory is blocking if " +
"limit is exceeded but should return an exception instead.");
}
}
static class TestRunner implements Callable<Boolean> {
public final static Logger LOG = Logger.getLogger(TestRunner.class);
/**
* @return true if test succeeded, false otherwise
*/
@Override
public Boolean call() {
Connection conn = null;
Session one = null;
PooledConnectionFactory cf = null;
// wait at most 5 seconds for the call to createSession
try {
ActiveMQConnectionFactory amq = new ActiveMQConnectionFactory(
"vm://broker1?marshal=false&broker.persistent=false&broker.useJmx=false");
cf = new PooledConnectionFactory();
cf.setConnectionFactory(amq);
cf.setMaxConnections(3);
cf.setMaximumActiveSessionPerConnection(1);
cf.setBlockIfSessionPoolIsFull(false);
conn = cf.createConnection();
one = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Session two = null;
try {
// this should raise an exception as we called setMaximumActive(1)
two = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
two.close();
LOG.error("Expected JMSException wasn't thrown.");
fail("seconds call to Connection.createSession() was supposed" +
"to raise an JMSException as internal session pool" +
"is exhausted. This did not happen and indiates a problem");
return new Boolean(false);
} catch (JMSException ex) {
if (ex.getCause().getClass() == java.util.NoSuchElementException.class) {
// expected, ignore but log
LOG.info("Caught expected " + ex);
} else {
LOG.error(ex);
return new Boolean(false);
}
} finally {
if (one != null) {
one.close();
}
if (conn != null) {
conn.close();
}
}
} catch (Exception ex) {
LOG.error(ex.getMessage());
return new Boolean(false);
} finally {
if (cf != null) {
cf.stop();
}
}
// all good, test succeeded
return new Boolean(true);
}
}
}