3 Replies Latest reply on Aug 13, 2014 4:27 AM by akoskm

    Closing the RuntimeManager

    akoskm

      Hello!

       

      I'm unable to dispose my runtime manager when the process is finished. Here is how I'm creating my RuntimeManager:

       

      public AppRuntime createSession(Long execId, List<Resource> resources, HashMap<String, Object> globals) throws ServiceException {
              KieBase kbase = createKieBase(execId, resources, null);
            
              RuntimeEnvironment environment = RuntimeEnvironmentBuilder.getDefault()
                                                                        .persistence(false)
                                                                        .entityManagerFactory(emf)
                                                                        .userGroupCallback(this.getUsersAndGroups())
                                                                        .knowledgeBase(kbase)
                                                                        .get();
            
              DefaultRegisterableItemsFactory rif = (DefaultRegisterableItemsFactory) environment.getRegisterableItemsFactory();
            
              RuntimeManager runtimeManager = RuntimeManagerFactory.Factory.get().newPerProcessInstanceRuntimeManager("runtime-" + execId);
              RuntimeEngine runtime = runtimeManager.getRuntimeEngine(ProcessInstanceIdContext.get());
            
              KieSession session = runtime.getKieSession();
              modifySession(session, null, globals);
            
              AppRuntime appRuntime = new AppRuntime(runtimeManager, runtime);
            
              return appRuntime;
          }
      
      
      

      And here is how I'm destroying it:

       

      public class BpmProcessListener implements ProcessEventListener {
        
          // .. other method implementations
      
      
          @Override
          public void afterProcessCompleted(ProcessCompletedEvent event) {
              if (DEBUG_LISTENER) {
                  LOG.debug("afterProcessCompleted: " + event.toString());
              }
              WorkflowProcessInstance pi = (WorkflowProcessInstance) event.getProcessInstance();
              Long applicationId = (Long) event.getKieRuntime().getGlobal("__APPID");
              Long execId = (Long) event.getKieRuntime().getGlobal("__EXECID");
              String processId = (String) event.getKieRuntime().getGlobal("__MAIN_PROCESS_ID");
           
              if (pi.getProcessId().equals(processId)) {
                  if (applicationId != null) {
                      bpmAppManager.removeFromSession(applicationId); // the method is called which initiates the dispose procedure, see below
                      LOG.debug("Application {} was removed from session",
                               applicationId);
                  } else {
                      LOG.warn("Application ID was null. Nothing to be removed from sessions map.");
                  }
                  this.broadcastStatus(execId);
              }
          }
      }
      
      
      

       

      where the bpmAppManager.removeFromSession(applicationId) does the following:

       

          /**
           * Holds [Application ID, AppSession] pairs.
           */
          public final ConcurrentHashMap<Long, AppSession> sessions = new ConcurrentHashMap<>();
        
          /**
           * Removes the session object associated with <i>applicationId</i> from the
           * session map.
           *
           * @param applicationId
           * @return true if the session object was found and was removed, false if no
           * associated session object was found in the session map
           */
          public void removeFromSession(Long applicationId) {
              AppSession removed = sessions.remove(applicationId);
              removed.shutdownRuntime();
          }
      
      
      

       

      finally:

       

      public class AppSession implements Serializable {
        
          private Long applicationId;
          private Long executionId;
          private Long processInstanceId;
          private AppRuntime appRuntime;
      
      
          // ...
        
          public void shutdownRuntime() {
              RuntimeManager runtimeManager = this.appRuntime.getRuntimeManager();
              RuntimeEngine runtimeEngine = runtimeManager.getRuntimeEngine(ProcessInstanceIdContext.get(processInstanceId));
              runtimeManager.disposeRuntimeEngine(runtimeEngine);
              runtimeManager.close();
          }
      }
      
      
      

       

      The AppRuntime is something similar to what you call DeployedUnit in kie. Since the user can start multiple processes, I'm holding the references to each created RuntimeManager.

      My test processes mainly containing Human Tasks. During the interaction with the TaskService I discovered that sometimes taskService.complete tries to complete a process which is connected with a RuntimeManager instance which should be already destroyed, but for some reason ProcessInstanceIdContext is still seeing it. My guess was that I end up with broken links to already disposed RuntimeEngines.

       

      To prove that I changed the code

       

      RuntimeManager runtimeManager = RuntimeManagerFactory.Factory.get().newPerProcessInstanceRuntimeManager(environment, "exec-" + execId);
      
      
      

      to

       

      RuntimeManager runtimeManager = RuntimeManagerFactory.Factory.get().newPerProcessInstanceRuntimeManager(environment);
      
      
      

       

      When happened is that after the first started and successfully completed process, when I started the second one I got the following exception:

       

      ! java.lang.IllegalStateException: RuntimeManager with id default-per-pinstance is already active
      
      
      

       

      which shows that the previous RuntimeManager wasn't disposed even if I did it in shutdownRuntime().

       

      Is there any other way to dispose the RuntimeEngine?

        • 1. Re: Disposing a RuntimeManager
          akoskm

          After running some experiments and simple test cases I figured out if I don't explicity dispose the runtimeEngine only do:

           

          public void shutdownRuntime() {
                  RuntimeManager runtimeManager = this.appRuntime.getRuntimeManager();
              //        runtimeManager.disposeRuntimeEngine(this.appRuntime.getRuntimeEngine());
                  runtimeManager.close();
              }
          

          the RuntimeManager is actually closed and I'm not getting the exception

           

          ! java.lang.IllegalStateException: RuntimeManager with id default-per-pinstance is already active 
          

           

          if I'm starting my process again with the same identifier.

           

          However, there are still some dead references left in the engine. After the second start of my process, the taskService.complete fails with the following exception:

           

          Caused by: ! java.lang.IllegalStateException: Runtime manager default-per-pinstance is already closed
          ! at org.jbpm.runtime.manager.impl.PerProcessInstanceRuntimeManager.getRuntimeEngine(PerProcessInstanceRuntimeManager.java:85) ~[jbpm-runtime-manager-6.0.0.Final.jar:6.0.0.Final]
          ! at org.jbpm.services.task.wih.ExternalTaskEventListener.afterTaskCompletedEvent(ExternalTaskEventListener.java:146) ~[jbpm-human-task-workitems-6.0.0.Final.jar:6.0.0.Final]
          ! at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_55]
          ! at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_55]
          ! at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_55]
          ! at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_55]
          ! at org.jbpm.shared.services.impl.events.JbpmServicesEventImpl.fire(JbpmServicesEventImpl.java:74) ~[jbpm-shared-services-6.0.0.Final.jar:6.0.0.Final]
          !... 63 common frames omitted
          

          This isn't true and if I try to complete the task though the taskService multiple times, the complete action succeeds after a while.

          • 2. Re: Re: Disposing a RuntimeManager
            akoskm

            I realized that I shouldn't destroy the runtimeManager in the afterProcessCompleted event, since at that point the runtime is still active (krisv already pointed this out on IRC). I guess this is what broke the task competition and caused the exceptions.

            To prove that, instead of immediately destroying the runtimeManager, I start a thread and destroy it there. This isn't really a solution I just set it up to prove my point:

             

            class Destroy implements Runnable {
            
            
                RuntimeManager runtime;
               
                Destroy(RuntimeManager runtime) {
                    this.runtime = runtime;
                }
               
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000l);
                        runtime.close();
                    } catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
            
            

            What I need is some trigger or event listener which is fired when the runtimeManager has no active processes and can be safely closed, but I haven't found anything similar.

            • 3. Re: Re: Re: Disposing a RuntimeManager
              akoskm

              A slightly better solution I come up with is this:

               

                  public void shutdownRuntime() {
                      new Thread(new CloseRuntimeManagerThread(this.getRuntimeManager(), this.getProcessInstanceId())).start();
                  }
              
              

               

              class CloseRuntimeManagerThread implements Runnable {
              
              
                  private final RuntimeManager runtime;
                  private final Long processInstanceId;
                 
                  CloseRuntimeManagerThread(RuntimeManager runtime, Long processInstanceId) {
                      this.runtime = runtime;
                      this.processInstanceId = processInstanceId;
                  }
                 
                  @Override
                  public void run() {
                      RuntimeEngine engine = runtime.getRuntimeEngine(ProcessInstanceIdContext.get(this.processInstanceId));
                      ProcessInstance pi = engine.getKieSession().getProcessInstance(processInstanceId);
                      if (pi == null) {
                          runtime.close();
                      } else {
                          long start = System.nanoTime();
                          while (ProcessInstance.STATE_COMPLETED != pi.getState()) {
                              runtime.close();
                          }
                          System.out.println("shut down took: " + TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS));
                      }
                  }
              }
              
              

               

              But this introduces a race-condition:

               

              Exception in thread "Log" org.jbpm.workflow.instance.WorkflowRuntimeException: [com.mycompany.sample:1 - End:2] -- Illegal method call. This session was previously disposed.
                at org.jbpm.workflow.instance.impl.NodeInstanceImpl.trigger(NodeInstanceImpl.java:161)
                at org.jbpm.workflow.instance.impl.NodeInstanceImpl.triggerNodeInstance(NodeInstanceImpl.java:337)
                at org.jbpm.workflow.instance.impl.NodeInstanceImpl.triggerCompleted(NodeInstanceImpl.java:296)
                at org.jbpm.workflow.instance.impl.ExtendedNodeInstanceImpl.triggerCompleted(ExtendedNodeInstanceImpl.java:44)
                at org.jbpm.workflow.instance.node.StateBasedNodeInstance.triggerCompleted(StateBasedNodeInstance.java:286)
                at org.jbpm.workflow.instance.node.StateBasedNodeInstance.triggerCompleted(StateBasedNodeInstance.java:265)
                at org.jbpm.workflow.instance.node.WorkItemNodeInstance.triggerCompleted(WorkItemNodeInstance.java:275)
                at org.jbpm.workflow.instance.node.WorkItemNodeInstance.workItemCompleted(WorkItemNodeInstance.java:337)
                at org.jbpm.workflow.instance.node.WorkItemNodeInstance.signalEvent(WorkItemNodeInstance.java:313)
                at org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl.signalEvent(WorkflowProcessInstanceImpl.java:399)
                at org.drools.core.process.instance.impl.DefaultWorkItemManager.completeWorkItem(DefaultWorkItemManager.java:131)
                at rs.digitaz.dirigent.jbpm.handler.LogHandlerThread.run(LogHandler.java:51)
                at java.lang.Thread.run(Thread.java:745)
              Caused by: java.lang.IllegalStateException: Illegal method call. This session was previously disposed.
                at org.drools.core.reteoo.DisposedReteooWorkingMemory.getEnvironment(DisposedReteooWorkingMemory.java:282)
                at org.drools.core.impl.StatefulKnowledgeSessionImpl.getEnvironment(StatefulKnowledgeSessionImpl.java:409)
                at org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl.setState(WorkflowProcessInstanceImpl.java:295)
                at org.jbpm.workflow.instance.impl.WorkflowProcessInstanceImpl.setState(WorkflowProcessInstanceImpl.java:307)
                at org.jbpm.workflow.instance.node.EndNodeInstance.internalTrigger(EndNodeInstance.java:65)
                at org.jbpm.workflow.instance.impl.NodeInstanceImpl.trigger(NodeInstanceImpl.java:155)
                ... 12 more
              
              

              Basically, in my background thread the processInstance returned by the runtimeManager is already null:

               

              public ProcessInstance getProcessInstance(long processInstanceId)
              Returns the process instance with the given id. Note that only active process instances will be returned. If a process instance has been completed already, this method will return null.    
              
              

              but of a trigger is still active. The exception above is happening in the last handler of my process, when it calls:

               

              this.session.getKsession().getWorkItemManager().completeWorkItem(this.item.getId(), null);
              
              

              The exception is always thrown, but so far seems that all states are set correctly and the process instance is actually completed and the manager is closed.

               

              Does anyone have a better solution to close the runtimeManager?