Index: JobExecutorThread.java
===================================================================
--- JobExecutorThread.java	(revision 5957)
+++ JobExecutorThread.java	(working copy)
@@ -43,8 +43,8 @@
    * @deprecated use {@link #JobExecutorThread(String, JobExecutor)} instead
    */
   public JobExecutorThread(String name, JobExecutor jobExecutor,
-      JbpmConfiguration jbpmConfiguration, int idleInterval, int maxIdleInterval,
-      long maxLockTime, int maxHistory) {
+      JbpmConfiguration jbpmConfiguration, int idleInterval,
+      int maxIdleInterval, long maxLockTime, int maxHistory) {
     super(name);
     this.jobExecutor = jobExecutor;
     this.jbpmConfiguration = jbpmConfiguration;
@@ -60,7 +60,12 @@
         Collection acquiredJobs = acquireJobs();
         for (Iterator i = acquiredJobs.iterator(); i.hasNext() && isActive;) {
           Job job = (Job) i.next();
-          executeJob(job);
+          try {
+            executeJob(job);
+          }
+          catch (Exception e) {
+            saveJobException(job, e);
+          }
         }
         if (isActive) {
           long waitPeriod = getWaitPeriod(currentIdleInterval);
@@ -75,21 +80,24 @@
       }
       catch (RuntimeException e) {
         if (isActive) {
-          log.error("exception in " + getName() + ", waiting " + currentIdleInterval + " ms", e);
+          log.error("exception in " + getName() + ", waiting "
+              + currentIdleInterval + " ms", e);
           try {
             synchronized (jobExecutor) {
               jobExecutor.wait(currentIdleInterval);
             }
+            // after an exception, double the current idle interval to prevent
+            // continuous exception throwing while an anomalous condition
+            // prevails
+            currentIdleInterval *= 2;
+            if (currentIdleInterval > maxIdleInterval
+                || currentIdleInterval < 0) {
+              currentIdleInterval = maxIdleInterval;
+            }
           }
           catch (InterruptedException ie) {
             log.debug(getName() + " got interrupted: " + e.getMessage());
           }
-          // after an exception, double the current idle interval to prevent
-          // continuous exception throwing while an anomalous condition prevails
-          currentIdleInterval *= 2;
-          if (currentIdleInterval > maxIdleInterval || currentIdleInterval < 0) {
-            currentIdleInterval = maxIdleInterval;
-          }
         }
       }
       catch (InterruptedException e) {
@@ -157,7 +165,7 @@
     return acquiredJobs;
   }
 
-  protected void executeJob(Job job) {
+  protected void executeJob(Job job) throws Exception {
     JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
     try {
       JobSession jobSession = jbpmContext.getJobSession();
@@ -174,17 +182,13 @@
         }
       }
       catch (Exception e) {
-        log.debug("exception while executing " + job, e);
-        if (!DbPersistenceService.isPersistenceException(e)) {
-          StringWriter memoryWriter = new StringWriter();
-          e.printStackTrace(new PrintWriter(memoryWriter));
-          job.setException(memoryWriter.toString());
-          job.setRetries(job.getRetries() - 1);
-        }
-        else {
-          // prevent unsafe use of the session after an exception occurs
-          jbpmContext.setRollbackOnly();
-        }
+        // prevent unsafe use of the session after an exception occurs
+        jbpmContext.setRollbackOnly();
+
+        if (!DbPersistenceService.isLockingException(e)) throw e;
+        // if this is a locking exception, keep it quiet
+        StaleObjectLogConfigurer.getStaleObjectExceptionsLog()
+            .error("failed to complete " + job);
       }
       catch (Error e) {
         jbpmContext.setRollbackOnly();
@@ -192,7 +196,8 @@
       }
 
       // if this job is locked too long
-      long totalLockTimeInMillis = System.currentTimeMillis() - job.getLockTime().getTime();
+      long totalLockTimeInMillis = System.currentTimeMillis()
+          - job.getLockTime().getTime();
       if (totalLockTimeInMillis > maxLockTime) {
         jbpmContext.setRollbackOnly();
       }
@@ -204,12 +209,28 @@
       catch (RuntimeException e) {
         if (!DbPersistenceService.isLockingException(e)) throw e;
         // if this is a locking exception, keep it quiet
-        StaleObjectLogConfigurer.getStaleObjectExceptionsLog().error("failed to complete job "
-            + job);
+        StaleObjectLogConfigurer.getStaleObjectExceptionsLog()
+            .error("failed to complete " + job);
       }
     }
   }
 
+  private void saveJobException(Job job, Exception exception) {
+    JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
+    try {
+      JobSession jobSession = jbpmContext.getJobSession();
+      jobSession.reattachJob(job);
+
+      StringWriter out = new StringWriter();
+      exception.printStackTrace(new PrintWriter(out));
+      job.setException(out.toString());
+      job.setRetries(job.getRetries() - 1);
+    }
+    finally {
+      jbpmContext.close();
+    }
+  }
+
   protected long getWaitPeriod(int currentIdleInterval) {
     long waitPeriod = currentIdleInterval;
 
@@ -255,7 +276,7 @@
         if (!DbPersistenceService.isLockingException(e)) throw e;
         // if this is a locking exception, keep it quiet
         StaleObjectLogConfigurer.getStaleObjectExceptionsLog()
-            .error("failed to determine next due date for job executor thread " + lockOwner);
+            .error("failed to determine next due date");
         nextDueDate = null;
       }
     }
@@ -270,7 +291,8 @@
   }
 
   /**
-   * Indicates that this thread should stop running. Execution will cease shortly afterwards.
+   * Indicates that this thread should stop running. Execution will cease
+   * shortly afterwards.
    */
   public void deactivate() {
     if (isActive) {
