I am using Java Enterprise (3.1) with Glassfish. I have two seperate EARs which are communicating synchronously via JMS. More specifically:
EAR1 uses JMS messaging to tell EAR2 what to do. EAR1 starts to listen for an answer from EAR2 (QueueReceiver.receive). EAR2 receives the message and does some processing accordingly and then sends a JMS message back to EAR1 with the output.
All this works fine. Until I get this exception:
[#|2011-05-10T15:05:27.382+0200|WARNING|glassfish3.1|javax.enterprise.resource.resourceadapter.com.sun.enterprise.connectors|_ThreadID=90;_ThreadName=Thread-1;|RAR5117 : Failed to obtain/create connection from connection pool [ jms/QueueConnectionFactory ]. Reason : com.sun.appserv.connectors.internal.api.PoolingException: In-use connections equal max-pool-size and expired max-wait-time. Cannot allocate more connections.|#]
So it seems like t开发者_如何学Gohe container doesnt reuse MDBs. Instead it creates new ones until I reach the limit. I know the reason for this is because the MDBs in EAR2 are using JMS to send back the results. My guess is that there are still some resources allocated in the MDB instance which causes the behaviour.
If I just use the MDBs to print out the message received I can continue sending messages all day long, so it is definately related to the JMS connection.
I have been at this for two days now, so if anyone cares to offer some help, it would be greatly appreciated.
this code works all day long:
package xxx;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
@MessageDriven(activationConfig = {
@ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Queue")
}, mappedName = "AssociationQueue1")
public class AssociationMDB implements MessageListener {
@Override
public void onMessage(Message arg0) {
MapMessage msg = (MapMessage)arg0;
String source = null;
String target = null;
try {
source = msg.getString("source");
target = msg.getString("target");
} catch (JMSException e) {
e.printStackTrace();
}
System.out.println(source + " " + target);
}
}
While this one doesnt:
package xxx;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
@MessageDriven(activationConfig = {
@ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Queue")
}, mappedName = "AssociationQueue1")
public class AssociationMDB implements MessageListener {
@Override
public void onMessage(Message arg0) {
Logger logger = Logger.getLogger(this.getClass().getSimpleName());
QueueConnection qConnect = null;
QueueSession qSession = null;
QueueSender qSender = null;
try {
InitialContext context = new InitialContext();
Queue responseQ = (Queue)context.lookup("AssociationQueue2");
QueueConnectionFactory factory = (QueueConnectionFactory) context.lookup("jms/QueueConnectionFactory");
qConnect = factory.createQueueConnection();
qSession = qConnect.createQueueSession(false,Session.AUTO_ACKNOWLEDGE);
qConnect.start();
qSender = qSession.createSender(responseQ);
TextMessage answer = qSession.createTextMessage();
answer.setText("hey");
qSender.send(answer);
logger.info("message sent");
}
catch (JMSException jmse) {
jmse.printStackTrace();
} catch (NamingException e) {
e.printStackTrace();
}
finally {
try {
if(qSender != null) {
qSender.close();
logger.info("cleaning qSender");
}
if(qSession != null) {
qSession.close();
logger.info("cleaning qSession");
}
if(qConnect != null) {
qConnect.close();
logger.info("cleaning qConnect");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
(I have also tried using newer more fancy EJB stuff like notations and so but didnt work either...)
Sebastian
The pattern itself does not seem to be problematic. A MDB is surely allowed to post a message to another queue. Whether this is in reply to something or just delegates the work isn't relevant to the problem at hand.
What does seem to be a problem is that you call qConnect.start()
. This prepares the connection for listening to inbound traffic. For sending a message this is thus not necessary. You also don't have to close the session and the sender explicitly. And although you do close the connection in a finally block, it's easily vulnerable to connection leaks if any of the code above it throws.
Your wording of "doesn't reuse MDBs" is btw also not entirely correct. I think you meant "doesn't reuse connections"?
It also seems that you are running both EARs on the same Glassfish instance. This means that EAR1 is using the same connection pool as EAR2 and thus the connection leak might also be caused by the code that calls QueueReceiver.receive
.
Finally note that listening directly to JMS connections for inbound traffic is actually not at all allowed in Java EE, especially EJBs*. This is one of the irritating parts where the JMS API behaves differently standalone and used inside a Java EE product. Some servers (e.g. JBoss AS) have different connection factories, where some can be used for listening and some can't. I don't know the exact details for Glassfish, but the fact that you start the connection for listening silently violates the spec and may be the main root of your problem.
*) There is additionally some unclarity whether it only concerns EJBs or also Servlets. E.g. JBoss AS 6 forbids both EJBs and Servlets to listen on connections using the Java EE compliant java:/JmsXA, but Weblogic 8 only EJBs and not Servlets.
Arjan gave a very good explanation. Thanks for that. I would just like to add something.
In my case, I was getting the connection factory by using @Resource instead of the context lookup, which I guess is pretty much very similar. Now it is important to note that QueueConnection.close() is handled by the container. So firing a close doesn't necessarily imply that your connection gets closed when this call gets fired. This is important to remember on all @Resource's. On creation of the MDB, the resources will initialise by means of DI. On destruction of the MDB, they will be freed again. This is for commit and rollback purposes.
So, if you are perhaps sending multiple messages from the session of your MDB, these connections will only be freed once the session of your MDB completes. Yes, I know, usually one request = one response, but in my case I got a CSV as request, to generate multiple XML messages and route each line to a queue, if you get the idea. So, generally, calling ConnectionFactory.createQueueConnection from within your sendMessage method, might be a risky idea. Rather pass the QueueConnection instance as a parameter.
精彩评论