[jboss-jira] [JBoss JIRA] (JBRULES-3559) Memory leakage when no transaction manager is used

Fabio Strozzi (JIRA) jira-events at lists.jboss.org
Tue Jul 10 11:20:12 EDT 2012


    [ https://issues.jboss.org/browse/JBRULES-3559?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12705204#comment-12705204 ] 

Fabio Strozzi commented on JBRULES-3559:
----------------------------------------

Hi, I have some updates about the problems I reported.

There're two main issues involved here, some of which may have been fixed in Drools 5.4:
# a memory leakage happens when transaction management is done in a Oracle Weblogic 10.3 application server and no transaction manager is esplicitly set in the knowledge-base environment.
# even if I can get around the first issue by esplicitly set a transaction manager, another memory leakage happens due to an event listener being registered twice when a persisted knowledge session is loaded while being removed only once when the session is disposed (read ahead for the details).

In order to fix issue #1 class org.drools.persistence.jta.JtaTransactionManager must be changed as follows:
# a new value has to be inserted into array FALLBACK_TRANSACTION_MANAGER_NAMES in order to support the naming convention of Oracle Weblogic 10.3:
{code}
public static final String[] FALLBACK_TRANSACTION_MANAGER_NAMES = new String[]{"java:comp/TransactionManager", "java:appserver/TransactionManager", "java:pm/TransactionManager", "java:/TransactionManager", "javax.transaction.TransactionManager"};
{code}
# method findTransactionSynchronizationRegistry has to be fixed by changing these lines
{code}
InitialContext context = new InitialContext();
context.lookup(jndiName);
logger.debug("JTA TransactionSynchronizationRegistry found at default JNDI location [{}]", jndiName);
return tsr;
{code}
into these
{code}
InitialContext context = new InitialContext();
Object tsr = context.lookup(jndiName);
logger.debug("JTA TransactionSynchronizationRegistry found at default JNDI location [{}]", jndiName);
return tsr;
{code}
# moreover, if no transaction manager or transaction sysnchronizionation register were found, then there's no need to update the synchronization map. A potential fix may be obtained by changing method registerTransactionSynchronization of JtaTransactionManager as follows:
{code}
public boolean registerTransactionSynchronization(final TransactionSynchronization ts) {
    if ( this.tsr != null ) {
        TransactionSynchronizationRegistryHelper.registerTransactionSynchronization(this.tsr, ts);
        return true;
    } else if (this.tm != null) {
        try {
            this.tm.getTransaction().registerSynchronization(new JtaTransactionSynchronizationAdapter(ts));
            return true;
        } catch (Exception e) {
            // No JTA TransactionManager available - log a warning.
            logger.warn("Participating in existing JTA transaction, but no JTA TransactionManager or TransactionSychronizationRegistry available: ", e);
        }
    } else {
        // No JTA TransactionManager available - log a warning.
        logger.warn("Participating in existing JTA transaction, but no JTA TransactionManager or TransactionSychronizationRegistry available: ");
    }
    return false;
}
{code}
Also, method registerRollbackSync of class SingleSessionCommandService must be changed accordingly
{code}
private void registerRollbackSync() {
    if ( synchronizations.get( this ) == null ) {
        if (txm.registerTransactionSynchronization( new SynchronizationImpl( this ) ))
            synchronizations.put( this, this );
    }
}
{code}

Concerning issue #2, the memory leakage is due to this method
{code}InputMarshaller.readSession(MarshallerReaderContext context, int id, ExecutorService executor, Environment environment, SessionConfiguration config){code}
when it performs the following steps:
{code}
ReteooStatefulSession session = new ReteooStatefulSession( id,
                                                                   context.ruleBase,
                                                                   executor,
                                                                   handleFactory,
                                                                   initialFactHandle,
                                                                   propagationCounter,
                                                                   config,
                                                                   agenda,
                                                                   environment );
session.setKnowledgeRuntime( new StatefulKnowledgeSessionImpl( session ) );
{code}
The constructor of StatefulKnowledgeSessionImpl sets the knowledge runtime once again; this in turn instances a new ProcessRuntimeImpl class which adds an event listener by using method initProcessEventListeners.

Whenever a new listener is added twice (see method addEventListener of KnowledgeBaseImpl) a new KnowledgeBaseEventListenerWrapper object is created and then it's added to the list ruleBase; also, the listener is added to the hashmap called mappedKnowledgeBaseListeners. Problem comes because if addEventListener is called more than one time for the same listener, then the map will contain only one reference to the listener while the ruleBase list will contain two references to two different wrappers. This becomes clear when the dispose method of ProcessRuntimeImpl is called and the listener is removed from the knowledge base: every time this happens, only one wrapper is removed from the ruleBase list hence causing the leakage.

I'm attaching two test apps that prove this fact: application AppOk always instantiates a new session while AppKo loads an existing session.

One possible fix could be to rewrite the readSession method as done with Drools 5.4:
{code}
ReteooStatefulSession session = new ReteooStatefulSession( id,
                                                                   context.ruleBase,
                                                                   executor,
                                                                   handleFactory,
                                                                   initialFactHandle,
                                                                   propagationCounter,
                                                                   config,
                                                                   agenda,
                                                                   environment );
new StatefulKnowledgeSessionImpl( session ) ;
{code}

Even if issue #2 has been fixed in Drools 5.4, I cannot manage to update to this version of the library as it would need several regression tests on my application.

In order to fix issue #1 is it safe to fix the JtaTransactionManager the way I described?
In order to avoid the memory leakage associated to issue #2, do you think it's reasonable not to load sessions but always creating new ones? Could this cause performance problems for instance?

Regards

Fabio

                
> Memory leakage when no transaction manager is used
> --------------------------------------------------
>
>                 Key: JBRULES-3559
>                 URL: https://issues.jboss.org/browse/JBRULES-3559
>             Project: Drools
>          Issue Type: Bug
>      Security Level: Public(Everyone can see) 
>          Components: drools-core
>    Affects Versions: 5.3.1.Final, 5.4.0.Final
>         Environment: JBPM 5.2.Final, Drools 5.3.1.Final, Weblogic 10.3.5, JRockit R28.2.2-7-148152-1.6.0_29-20111221-2104-linux-x86_64, Spring 3.0.5
>            Reporter: Fabio Strozzi
>            Assignee: Mark Proctor
>              Labels: drools-persistence-jpa, jbpm5, leak, memory, memory_leak, memoryleak
>         Attachments: bpmLeak.zip
>
>
> Hi,
>     a memory leak appears in Drools if a StatefulKnowledgeSession is created without a transaction manager being speciifed (despite the fact that the dispose method is called for each session, be it created or loaded).
> To be more clear, this is the code that creates the session:
> 		Environment env = KnowledgeBaseFactory.newEnvironment();
>         env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, emf);
>         return JPAKnowledgeService.newStatefulKnowledgeSession(readKnowledgeBase(), null, env);
> When analyzing the memory dumps of a Spring-based webapplication that uses JBPM, under stress conditions, the Eclipse Memory Analyzer blames org.drools.persistence.SingleSessionCommandService class as the main cause of the leakage. This class is taking up to the 46% of the whole heap by accumulating instances of java.lang.Object[] arrays. These arrays are actually elements of a synchronized map called sysnchronizations (see SingleSessionCommandService.java, line 73).
> For my understanding of the source code of Drools, the sysnchronizations map is filled every time the registerRollbackSync method of SingleSessionCommandService is called; on the other hand, its elements are removed only if a JtaTransactionSynchronizationAdapter is successfully registered against the transaction (via javax.transaction.Transaction.registerSynchronization method).
> If I understand it correctly, this happens only if a javax.transaction.TransactionManager object is specified in the environment properties or one is found by JtaTransactionManager.findTransactionManager.
> When no transaction manager is present (or none is found by findTransactionManager), method SynchronizationImpl.afterCompletion (the only one eligible to remove elements from SingleSessionCommandService.synchronizations) is never called and memory increases with each new session.
> Please note that if no transaction synchronization manager is passed as an environement property, then org.drools.persistence.jta.JtaTransactionManager never finds it, even if the JNDI lookup successfully retrieves it; method findTransactionSynchronizationRegistry never sets the tsr attribute, hence if it was null at the time the method is called, so it is when it returns.
> I also tried to set the transaction manager but I still get a constant memory consumption. The Eclipse Memory Analyzer claims that, in this case, class org.drools.reteoo.ReteooStatefulSession is suspected for the leakage.
> As a final note, there's a minor issue in class JtaTransactionSynchronizationAdapter: at line 31, the afterCompletion method is called on object this rather than on object ts (TransactionSynchronization). This could cause a loop if status is STATUS_ACTIVE.
> I'm working on a test case to reproduce the memory issues. I will provide one as soon as possible along with a heap dump.
> Fabio

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators: https://issues.jboss.org/secure/ContactAdministrators!default.jspa
For more information on JIRA, see: http://www.atlassian.com/software/jira

        


More information about the jboss-jira mailing list