awips2/edexOsgi/com.raytheon.uf.common.jms/src/com/raytheon/uf/common/jms/JmsPooledProducer.java
Richard Peter ea806139d3 Issue #1642: Fix QPID Deadlock scenario
- Address peer review comments

Change-Id: I3c52fd57f762a762d54fdce3651c7f17a8fc6462

Former-commit-id: 7d4206420c [formerly 9a0177861f] [formerly 5b2650599d [formerly 2071d4f3cc54a3da43443bc71cecf038736194d8]]
Former-commit-id: 5b2650599d
Former-commit-id: 4deaf372a1
2013-02-26 16:01:25 -06:00

222 lines
6.6 KiB
Java

/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.jms;
import java.util.ArrayList;
import java.util.List;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import com.raytheon.uf.common.jms.wrapper.JmsProducerWrapper;
import com.raytheon.uf.common.status.IUFStatusHandler;
import com.raytheon.uf.common.status.UFStatus;
import com.raytheon.uf.common.status.UFStatus.Priority;
/**
* Jms Pooled Producer. Tracks references to the producers to know when the
* producer can be released to the pool. Any exception will close pooled
* producer instead of returning to the pool.
*
* Synchronization Principle To prevent deadlocks: Chained sync blocks can only
* happen in a downward direction. A manager has a synchronized lock can make a
* call down to a wrapper, but not nice versa. Also a session inside a sync
* block can make a call down to a producer but not vice versa.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Apr 18, 2011 rjpeter Initial creation
* Mar 08, 2012 194 njensen Improved logging
* Feb 26, 2013 1642 rjpeter Removed lazy initialization
* </pre>
*
* @author rjpeter
* @version 1.0
*/
public class JmsPooledProducer {
private final IUFStatusHandler statusHandler = UFStatus
.getHandler(JmsPooledProducer.class);
private final JmsPooledSession sess;
private final MessageProducer producer;
private final String destKey;
private volatile boolean exceptionOccurred = false;
private final Object stateLock = new Object();
private volatile State state = State.InUse;
/**
* Technically a pooled producer should only have 1 reference at a time.
* Bullet proofing in case 3rd party ever tries to get multiple producers to
* the same destination.
*/
private final List<JmsProducerWrapper> references = new ArrayList<JmsProducerWrapper>(
1);
public JmsPooledProducer(JmsPooledSession sess, String destKey,
MessageProducer producer) {
this.sess = sess;
this.destKey = destKey;
this.producer = producer;
}
public String getDestKey() {
return destKey;
}
public boolean isValid() {
return isValid(State.Closed, false);
}
/**
* Verifies if an exception has occurred, the state is the desired state,
* and the underlying resource is still valid.
*
* @param requiredState
* @param mustBeRequiredState
* If true, current state must match requiredState for isValid to
* be true. If false, current state must not be the
* requiredState.
* @return
*/
public boolean isValid(State requiredState, boolean mustBeRequiredState) {
boolean valid = false;
if (!exceptionOccurred) {
valid = state.equals(requiredState);
if (!mustBeRequiredState) {
valid = !valid;
}
if (valid) {
// check underlying resource
try {
if (producer != null) {
producer.getDeliveryMode();
}
} catch (JMSException e) {
// underlying producer has been closed
valid = false;
}
}
}
return valid;
}
public boolean isExceptionOccurred() {
return exceptionOccurred;
}
public void setExceptionOccurred(boolean exceptionOccurred) {
this.exceptionOccurred = exceptionOccurred;
}
/**
* Close down this pooled producer, closes the internal producer reference,
* and removes from session pool.
*/
public void close() {
boolean close = false;
// only thing in sync block is setting close to prevent dead locking
// between manager and wrapper, general design principal on sync blocks
// is chained blocks only in a downward direction (i.e. a
synchronized (stateLock) {
if (!State.Closed.equals(state)) {
state = State.Closed;
close = true;
for (JmsProducerWrapper wrapper : references) {
wrapper.closeWrapper();
}
references.clear();
}
}
if (close) {
try {
statusHandler.info("Closing producer " + producer); // njensen
producer.close();
} catch (Throwable e) {
statusHandler.handle(Priority.WARN, "Failed to close producer",
e);
}
sess.removeProducerFromPool(this);
}
}
public JmsProducerWrapper createReference() {
synchronized (stateLock) {
if (isValid(State.InUse, true)) {
JmsProducerWrapper wrapper = new JmsProducerWrapper(this);
references.add(wrapper);
return wrapper;
}
}
return null;
}
public void removeReference(JmsProducerWrapper wrapper) {
boolean returnToPool = false;
synchronized (stateLock) {
if (references.remove(wrapper) && references.isEmpty()
&& State.InUse.equals(state)) {
state = State.Available;
returnToPool = true;
}
}
boolean valid = isValid();
if (valid && returnToPool) {
valid = sess.returnProducerToPool(this);
}
if (!valid) {
close();
}
}
public MessageProducer getProducer() throws JMSException {
return producer;
}
public State getState() {
return this.state;
}
public void setState(State state) {
this.state = state;
}
public Object getStateLock() {
return stateLock;
}
}