/** {@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); } }