/** {@inheritDoc} */
 public RecurringTaskHandle scheduleRecurringTask(
     KernelRunnable task, Identity owner, long startTime, long period) {
   ScheduledTaskImpl scheduledTask =
       new ScheduledTaskImpl(task, owner, defaultPriority, startTime, period);
   RecurringTaskHandle handle = backingQueue.createRecurringTaskHandle(scheduledTask);
   scheduledTask.setRecurringTaskHandle(handle);
   return handle;
 }
 /** Private method to schedule the next task, if any. */
 void scheduleNextTask() {
   synchronized (this) {
     if (queue.isEmpty()) {
       inScheduler = false;
     } else {
       dependencyCount.decrementAndGet();
       // re-set the start time before scheduling, since the
       // task isn't really requested to start until all
       // tasks ahead of it have run
       ScheduledTaskImpl schedTask = queue.poll();
       schedTask.resetStartTime();
       backingQueue.addTask(schedTask);
     }
   }
 }
    /** {@inheritDoc} */
    public void addTask(KernelRunnable task, Identity owner) {
      ScheduledTaskImpl schedTask =
          new ScheduledTaskImpl(task, owner, defaultPriority, System.currentTimeMillis());
      schedTask.setTaskQueue(this);

      synchronized (this) {
        if (inScheduler) {
          dependencyCount.incrementAndGet();
          queue.offer(schedTask);
        } else {
          inScheduler = true;
          backingQueue.addTask(schedTask);
        }
      }
    }
  /**
   * Private method that determines whether a given task should be re-tried based on the given
   * {@code Throwable} that caused failure. If this returns {@code true} then the task should be
   * re-tried. Otherwise, the task should be dropped.
   */
  private boolean shouldRetry(ScheduledTaskImpl task, Throwable t) {
    // NOTE: as a first-pass implementation this simply instructs the
    // caller to try again if retry is requested, but other strategies
    // (like the number of times re-tried) might be considered later
    if ((t instanceof ExceptionRetryStatus) && (((ExceptionRetryStatus) t).shouldRetry())) {
      return true;
    }

    // we're not re-trying the task, so log that it's being dropped
    if (logger.isLoggable(Level.WARNING)) {
      if (task.isRecurring()) {
        logger.logThrow(
            Level.WARNING,
            t,
            "skipping a recurrence of "
                + "a task that failed with a non-retryable "
                + "exception: {0}",
            task);
      } else {
        logger.logThrow(
            Level.WARNING,
            t,
            "dropping a task that " + "failed with a non-retryable exception: {0}",
            task);
      }
    }

    return false;
  }
  /**
   * Private method that blocks until the task has completed, re-throwing any exception resulting
   * from the task failing.
   */
  private void waitForTask(ScheduledTaskImpl task, boolean unbounded) throws Exception {
    Throwable t = null;

    try {
      // NOTE: calling executeTask() directly means that we're trying
      // to run the transaction in the calling thread, so there are
      // actually more threads running tasks simulaneously than there
      // are threads in the scheduler pool. This could be changed to
      // hand-off the task and wait for the result if we wanted more
      // direct control over concurrent transactions
      executeTask(task, unbounded, false);
      // wait for the task to complete...at this point it may have
      // already completed, or else it is being re-tried in a
      // scheduler thread
      t = task.get();
    } catch (InterruptedException ie) {
      // we were interrupted, so try to cancel the task, re-throwing
      // the interruption if that succeeds or looking at the result
      // if the task completes before it can be cancelled
      if (task.cancel(false)) {
        backingQueue.notifyCancelled(task);
        throw ie;
      }
      if (task.isCancelled()) {
        throw ie;
      }
      t = task.get();
    }

    // if the result of the task was a permananent failure, then
    // re-throw the exception
    if (t != null) {
      if (t instanceof Exception) {
        throw (Exception) t;
      }
      throw (Error) t;
    }
  }
    /** {@inheritDoc} */
    public void run() {
      logger.log(Level.FINE, "Starting a consumer for transactions");
      notifyThreadJoining();

      try {
        while (true) {
          // wait for the next task, at which point we may get
          // interrupted and should therefore return
          ScheduledTaskImpl task = (ScheduledTaskImpl) (backingQueue.getNextTask(true));

          // run the task, checking if it completed
          if (executeTask(task, false, true)) {
            // if it's a recurring task, schedule the next run
            if (task.isRecurring()) {
              long nextStart = task.getStartTime() + task.getPeriod();
              task = new ScheduledTaskImpl(task, nextStart);
              backingQueue.addTask(task);
            }
            // if it has dependent tasks, schedule the next one
            TaskQueueImpl queue = (TaskQueueImpl) (task.getTaskQueue());
            if (queue != null) {
              queue.scheduleNextTask();
            }
          }
        }
      } catch (InterruptedException ie) {
        if (logger.isLoggable(Level.FINE)) {
          logger.logThrow(Level.FINE, ie, "Consumer is finishing");
        }
      } catch (Exception e) {
        // this should never happen, since running the task should
        // never throw an exception that isn't handled
        logger.logThrow(Level.SEVERE, e, "Fatal error for consumer");
      } finally {
        notifyThreadLeaving();
      }
    }
  /**
   * Private method that executes a single task, creating the transaction state and handling re-try
   * as appropriate. If the thread calling this method is interrupted before the task can complete
   * then this method attempts to re-schedule the task to run in another thread if {@code
   * retryOnInterruption} is {@code true} and always re-throws the associated {@code
   * InterruptedException}. Providing {@code true} for the {@code unbounded} parameter results in a
   * transaction with timeout value as specified by the value of the {@code
   * TransactionCoordinator.TXN_UNBOUNDED_TIMEOUT_PROPERTY} property.
   *
   * <p>This method returns {@code true} if the task was completed or failed permanently, and {@code
   * false} otherwise. If {@code false} is returned then the task is scheduled to be re-tried at
   * some point in the future, possibly by another thread, by this method. The caller may query the
   * status of the task and wait for the task to complete or fail permanently through the {@code
   * ScheduledTaskImpl} interface.
   */
  private boolean executeTask(
      ScheduledTaskImpl task, boolean unbounded, boolean retryOnInterruption)
      throws InterruptedException {
    logger.log(Level.FINEST, "starting a new transactional task");

    // store the current owner, and then push the new thread detail
    Identity parent = ContextResolver.getCurrentOwner();
    ContextResolver.setTaskState(kernelContext, task.getOwner());

    try {
      // keep trying to run the task until we succeed, tracking how
      // many tries it actually took
      while (true) {
        if (!task.setRunning(true)) {
          // this task is already finished
          return true;
        }

        // NOTE: We could report the two queue sizes separately,
        // so we should figure out how we want to represent these
        int waitSize = backingQueue.getReadyCount() + dependencyCount.get();
        profileCollectorHandle.startTask(
            task.getTask(), task.getOwner(), task.getStartTime(), waitSize);
        task.incrementTryCount();

        Transaction transaction = null;

        try {
          // setup the transaction state
          TransactionHandle handle = transactionCoordinator.createTransaction(unbounded);
          transaction = handle.getTransaction();
          ContextResolver.setCurrentTransaction(transaction);

          try {
            // notify the profiler and access coordinator
            profileCollectorHandle.noteTransactional(transaction.getId());
            accessCoordinator.notifyNewTransaction(
                transaction, task.getStartTime(), task.getTryCount());

            // run the task in the new transactional context
            task.getTask().run();
          } finally {
            // regardless of the outcome, always clear the current
            // transaction state before proceeding...
            ContextResolver.clearCurrentTransaction(transaction);
          }

          // try to commit the transaction...note that there's the
          // chance that the application code masked the orginal
          // cause of a failure, so we'll check for that first,
          // re-throwing the root cause in that case
          if (transaction.isAborted()) {
            throw transaction.getAbortCause();
          }
          handle.commit();

          // the task completed successfully, so we're done
          profileCollectorHandle.finishTask(task.getTryCount());
          task.setDone(null);
          return true;
        } catch (InterruptedException ie) {
          // make sure the transaction was aborted
          if (!transaction.isAborted()) {
            transaction.abort(ie);
          }
          profileCollectorHandle.finishTask(task.getTryCount(), ie);
          // if the task didn't finish because of the interruption
          // then we want to note that and possibly re-queue the
          // task to run in a usable thread
          if (task.setInterrupted() && retryOnInterruption) {
            if (!handoffRetry(task, ie)) {
              // if the task couldn't be re-queued, then there's
              // nothing left to do but drop it
              task.setDone(ie);
              if (logger.isLoggable(Level.WARNING)) {
                logger.logThrow(Level.WARNING, ie, "dropping " + "an interrupted task: {0}" + task);
              }
            }
          }
          // always re-throw the interruption
          throw ie;
        } catch (Throwable t) {
          // make sure the transaction was aborted
          if ((transaction != null) && (!transaction.isAborted())) {
            transaction.abort(t);
          }
          profileCollectorHandle.finishTask(task.getTryCount(), t);
          // some error occurred, so see if we should re-try
          if (!shouldRetry(task, t)) {
            // the task is not being re-tried
            task.setDone(t);
            return true;
          } else {
            // see if the re-try should be handed-off
            task.setRunning(false);
            if (handoffRetry(task, t)) {
              return false;
            }
          }
        }
      }
    } finally {
      // always restore the previous owner before leaving...
      ContextResolver.setTaskState(kernelContext, parent);
    }
  }