Interface JobScheduler

All Known Implementing Classes:
ExecutorJobScheduler, ImmediateJobScheduler, OperationInitializerJobScheduler, UpdateGraphJobScheduler

public interface JobScheduler
An interface for submitting jobs to be executed. Submitted jobs may be executed on the current thread, or in separate threads (thus allowing true parallelism). Performance metrics are accumulated for all executions off the current thread for inclusion in overall task metrics.

The iteration methods come in two forms. iterateParallel(io.deephaven.engine.context.ExecutionContext, io.deephaven.base.log.LogOutputAppendable, java.util.function.Supplier<CONTEXT_TYPE>, int, int, io.deephaven.engine.table.impl.util.JobScheduler.IterateAction<CONTEXT_TYPE>, java.lang.Runnable, java.lang.Runnable, java.util.function.Consumer<java.lang.Exception>) and iterateSerial(io.deephaven.engine.context.ExecutionContext, io.deephaven.base.log.LogOutputAppendable, java.util.function.Supplier<CONTEXT_TYPE>, int, int, io.deephaven.engine.table.impl.util.JobScheduler.IterateResumeAction<CONTEXT_TYPE>, java.lang.Runnable, java.lang.Runnable, java.util.function.Consumer<java.lang.Exception>) report the outcome through callbacks and may return before the iteration is over, although a scheduler that runs jobs on the submitting thread can finish it first. invokeParallel(io.deephaven.engine.context.ExecutionContext, io.deephaven.base.log.LogOutputAppendable, java.util.function.Supplier<CONTEXT_TYPE>, int, int, io.deephaven.engine.table.impl.util.JobScheduler.IterateAction<CONTEXT_TYPE>) holds the calling thread, which runs tasks alongside the scheduler's threads, and returns or throws only once the iteration is over.

An iteration fails when a task throws, when an JobScheduler.IterateResumeAction task reports a failure through its nested error consumer, when a task context fails to close, or when it cannot be started because the context factory or submit(io.deephaven.engine.context.ExecutionContext, java.lang.Runnable, io.deephaven.base.log.LogOutputAppendable, java.util.function.Consumer<java.lang.Exception>) throws. No new task starts once it has failed. The callback forms then call onError with the first failure, and neither onComplete nor cleanup; a failure to start reaches their caller only that way, unless it is not an Exception, an Error in practice, which is also rethrown. invokeParallel throws the first failure instead, as it describes. Later failures are suppressed on the first.

  • Field Details

    • DEFAULT_CONTEXT

      static final JobScheduler.JobThreadContext DEFAULT_CONTEXT
    • DEFAULT_CONTEXT_FACTORY

      static final Supplier<JobScheduler.JobThreadContext> DEFAULT_CONTEXT_FACTORY
    • UNREPORTABLE_JOB_ERROR

      static final Exception UNREPORTABLE_JOB_ERROR
      Delivered when a job fails with an Error and even the wrapper for it cannot be allocated, which is to say when the heap is exhausted — the very failure this path exists for. Allocated once, when this interface is initialized, so that delivering a failure never depends on being able to allocate. Being shared, it takes no suppressed exceptions.
  • Method Details

    • asDeliverableException

      static Exception asDeliverableException(@NotNull @NotNull Throwable throwable)
      Convert a Throwable that escaped a scheduled job into something the Consumer<Exception> error handlers used throughout the scheduler can accept. Exceptions pass through unchanged; anything else is wrapped, so that the thread waiting on the job's completion fails with a diagnostic instead of waiting forever for a completion that cannot happen. That is an Error in practice, most often an OutOfMemoryError, though Groovy code can throw a plain Throwable as well.

      This never throws. The wrapper carries no stack trace of its own: filling one in is the largest allocation here, and the stack that matters belongs to the Error, which is kept as the cause. Should even that allocation fail, UNREPORTABLE_JOB_ERROR is delivered instead — a caller that fails without a diagnostic is still far better than one that waits forever.

      Parameters:
      throwable - the Throwable that escaped the job
      Returns:
      throwable itself if it is an Exception, otherwise a wrapper holding it as its cause
    • runJob

      static void runJob(@Nullable @Nullable ExecutionContext executionContext, @NotNull @NotNull Runnable runnable, @Nullable @Nullable LogOutputAppendable description, @NotNull @NotNull Consumer<Exception> onError)
      Run a submitted job on whatever thread the scheduler has chosen for it: under executionContext when one is given; with an Exception it throws delivered to onError, and should onError itself throw, that failure, carrying the Exception as suppressed, reported to the global fatal error reporter, then rethrown; and with anything else it throws, an Error in practice, delivered as asDeliverableException(java.lang.Throwable) makes it, then reported to the global fatal error reporter, then rethrown. The default reporter ends the process and does not return, so the rethrow happens only under a reporter that does. Implementations wrap this in whatever performance accounting they keep. ImmediateJobScheduler, which runs every job on the submitting thread, does not use it: an Error from one of its jobs propagates to that thread undelivered and unreported.
      Parameters:
      executionContext - the execution context to run the job under, or null to run it under the thread's own
      runnable - the job
      description - a description of the job, for the fatal error report
      onError - the consumer to deliver a failure to
    • submit

      JobScheduler.Submission submit(ExecutionContext executionContext, Runnable runnable, LogOutputAppendable description, Consumer<Exception> onError)
      Cause runnable to be executed.

      A scheduler runs every job it accepts: at once, after queuing it, or, when it has no thread free and no queue to hold the job, on the calling thread. A scheduler that cannot take a job at all throws instead, and the job does not run.

      Parameters:
      executionContext - the execution context to run it under
      runnable - the runnable to execute
      description - a description for logging
      onError - a routine to call if an exception occurs while running runnable
      Returns:
      the accepted job, which the submitter may withdraw while no thread has started it
    • getAccumulatedPerformance

      BasePerformanceEntry getAccumulatedPerformance()
      The performance statistics of all runnables that have been completed off-thread, or null if all were executed in the current thread.

      When initializing an operation, the OperationInitializerJobScheduler executes the completion callback as part of another task. Therefore, you must not read the accumulated performance from the completion callback when initializing an operation; as it could miss data from some of the tasks. Furthermore, even though the completion callback identifies the result as ready does not mean that the completion callback has actually completed. To guard against this the OperationInitializerJobScheduler waits for all jobs to be complete before returning the BasePerformanceEntry. Therefore, if you call this from a completion callback, then the operation will hang. ExecutorJobScheduler waits the same way, so the same applies to it.

      It never runs a job itself, so call it only once every job has started: after awaitCompletion(java.util.concurrent.CompletableFuture<T>) or invokeParallel(io.deephaven.engine.context.ExecutionContext, io.deephaven.base.log.LogOutputAppendable, java.util.function.Supplier<CONTEXT_TYPE>, int, int, io.deephaven.engine.table.impl.util.JobScheduler.IterateAction<CONTEXT_TYPE>), which leave no job waiting for a thread. Otherwise it may wait for a job queued behind threads that are themselves waiting, perhaps for a lock the calling thread holds.

    • awaitCompletion

      <T> T awaitCompletion(@NotNull @NotNull CompletableFuture<T> future) throws InterruptedException, ExecutionException
      Wait for future, which this scheduler's jobs complete, and return its result as Future.get() does.

      Each scheduler decides how a caller waits, or whether it may:

      Parameters:
      future - the future to wait for
      Returns:
      the result of future
      Throws:
      InterruptedException - if the calling thread is interrupted while waiting
      ExecutionException - if future completed exceptionally
    • threadCount

      int threadCount()
      How many threads exist in the job scheduler? The job submitters can use this value to determine how many sub-jobs to split work into.
    • iterateParallel

      @FinalDefault default <CONTEXT_TYPE extends JobScheduler.JobThreadContext> void iterateParallel(@Nullable @Nullable ExecutionContext executionContext, @Nullable @Nullable LogOutputAppendable description, @NotNull @NotNull Supplier<CONTEXT_TYPE> taskThreadContextFactory, int start, int count, @NotNull @NotNull JobScheduler.IterateAction<CONTEXT_TYPE> action, @NotNull @NotNull Runnable onComplete, @NotNull @NotNull Runnable cleanup, @NotNull @NotNull Consumer<Exception> onError)
      Provides a mechanism to iterate over a range of values in parallel using the JobScheduler
      Parameters:
      executionContext - the execution context for this task
      description - the description to use for logging
      taskThreadContextFactory - the factory that supplies contexts for the threads handling the sub-tasks
      start - the integer value from which to start iterating
      count - the number of times this task should be called
      action - the task to perform, the current iteration index is provided as a parameter
      onComplete - this will be called when all iterations are complete
      cleanup - called after onComplete successfully returns. If the invocation of the cleanup throws an exception, onError will not be called.
      onError - error handler for the scheduler to use when the iteration fails, as JobScheduler describes, or if onComplete throws an exception
    • iterateParallel

      @FinalDefault default <CONTEXT_TYPE extends JobScheduler.JobThreadContext> void iterateParallel(@Nullable @Nullable ExecutionContext executionContext, @Nullable @Nullable LogOutputAppendable description, @NotNull @NotNull Supplier<CONTEXT_TYPE> taskThreadContextFactory, int start, int count, @NotNull @NotNull JobScheduler.IterateResumeAction<CONTEXT_TYPE> action, @NotNull @NotNull Runnable onComplete, @NotNull @NotNull Runnable cleanup, @NotNull @NotNull Consumer<Exception> onError)
      Provides a mechanism to iterate over a range of values in parallel using the JobScheduler. The advantage to using this over the other method is the resumption callable on action that will trigger the next execution. This allows the next iteration and the completion runnable to be delayed until dependent asynchronous serial or parallel scheduler jobs have completed.
      Parameters:
      executionContext - the execution context for this task
      description - the description to use for logging
      taskThreadContextFactory - the factory that supplies contexts for the tasks
      start - the integer value from which to start iterating
      count - the number of times this task should be called
      action - the task to perform, the current iteration index and a resume Runnable are parameters
      onComplete - this will be called when all iterations are complete
      cleanup - called after onComplete successfully returns. If the invocation of the cleanup throws an exception, onError will not be called.
      onError - error handler for the scheduler to use when the iteration fails, as JobScheduler describes, or if onComplete throws an exception
    • iterateSerial

      @FinalDefault default <CONTEXT_TYPE extends JobScheduler.JobThreadContext> void iterateSerial(@Nullable @Nullable ExecutionContext executionContext, @Nullable @Nullable LogOutputAppendable description, @NotNull @NotNull Supplier<CONTEXT_TYPE> taskThreadContextFactory, int start, int count, @NotNull @NotNull JobScheduler.IterateResumeAction<CONTEXT_TYPE> action, @NotNull @NotNull Runnable onComplete, @NotNull @NotNull Runnable cleanup, @NotNull @NotNull Consumer<Exception> onError)
      Provides a mechanism to iterate over a range of values serially using the JobScheduler. The advantage to using this over a simple iteration is the resumption callable on action that will trigger the next execution. This allows the next iteration and the completion runnable to be delayed until dependent asynchronous serial or parallel scheduler jobs have completed.
      Parameters:
      executionContext - the execution context for this task
      description - the description to use for logging
      taskThreadContextFactory - the factory that supplies contexts for the tasks
      start - the integer value from which to start iterating
      count - the number of times this task should be called
      action - the task to perform, the current iteration index and a resume Runnable are parameters
      onComplete - this will be called when all iterations are complete
      cleanup - called after onComplete successfully returns. If the invocation of the cleanup throws an exception, onError will not be called.
      onError - error handler for the scheduler to use when the iteration fails, as JobScheduler describes, or if onComplete throws an exception
    • invokeParallel

      @FinalDefault default <CONTEXT_TYPE extends JobScheduler.JobThreadContext> void invokeParallel(@Nullable @Nullable ExecutionContext executionContext, @Nullable @Nullable LogOutputAppendable description, @NotNull @NotNull Supplier<CONTEXT_TYPE> taskThreadContextFactory, int start, int count, @NotNull @NotNull JobScheduler.IterateAction<CONTEXT_TYPE> action)
      Iterates over a range of values in parallel as iterateParallel(io.deephaven.engine.context.ExecutionContext, io.deephaven.base.log.LogOutputAppendable, java.util.function.Supplier<CONTEXT_TYPE>, int, int, io.deephaven.engine.table.impl.util.JobScheduler.IterateAction<CONTEXT_TYPE>, java.lang.Runnable, java.lang.Runnable, java.util.function.Consumer<java.lang.Exception>) does, except that the calling thread runs tasks too, and this method returns only once the iteration is over. Where iterateParallel reports its outcome through callbacks and may return first, this holds the calling thread and reports its outcome by returning or throwing; whatever a caller would do in onComplete, cleanup or onError it does after this call, or in a catch or finally around it.

      Participation. Up to min(count, threadCount()) - 1 task invokers are submitted to the scheduler, and one more runs on the calling thread, each with a task context of its own from taskThreadContextFactory, which is called on the calling thread. The caller therefore works even when the scheduler accepts every submission. When the caller runs out of tasks it runs any submitted invoker that the scheduler has not started yet, and withdraws its job; a job the scheduler cannot withdraw does nothing when it starts. So the caller never waits on an invoker that has not started, whatever the scheduler does with a job it cannot start at once, and the scheduler does not wait for the jobs of the invokers the caller ran.

      Return. This returns only once every task has completed and every task context has been closed.

      Failure. A task fails by throwing, as JobScheduler.IterateAction describes, and so does the iteration, as it does when a task context fails to close or the iteration fails to start: no new task starts, and once every task already running has finished, the first failure is thrown, as itself when it is a RuntimeException and wrapped in an UncheckedDeephavenException otherwise. Later failures are attached to it as suppressed. An Error, or any other Throwable that is not an Exception, thrown by a task on a scheduler thread is still reported as fatal and rethrown there, and reaches the caller wrapped, as asDeliverableException(java.lang.Throwable) makes it; one thrown on the calling thread reaches the caller as itself, carrying the later failures, once the other tasks have finished. A submitted invoker that the scheduler runs on the calling thread, as ExecutorJobScheduler does with a job its executor refuses, is still the scheduler's job, and an Error from its tasks is reported as fatal as it would be on a scheduler thread.

      Interruption. The wait cannot be interrupted, because the caller is about to release what the running tasks are using; the interrupt is restored before this returns.

      Nesting and schedulers. The caller waits only on tasks already running on another thread, never on one the scheduler has not started, so a task may itself call invokeParallel, to any depth, on any scheduler and on whatever thread it runs on. The invocation completes as long as every task's own work does and no task waits on other work submitted to the scheduler, such as a callback-form iteration, which the caller cannot run on its behalf. Nor may a task wait on another task of the same invocation: with one thread, or no pool thread free, the caller runs them one after another. The update graph's scheduler accepts jobs only during an update cycle, so with more than one update thread this must be called during an update cycle. Its parallelism comes from the update graph's pool threads. Called from an update thread, as listener code is, the other tasks are dispatched to the remaining pool threads by the update graph's refresh thread. Called from the refresh thread itself, nothing is dispatched while it waits, so it runs every task itself, one after another.

      Parameters:
      executionContext - the execution context the tasks run under, on every thread that runs them, the calling thread's included; null to run them under each thread's own
      description - the description to use for logging
      taskThreadContextFactory - the factory that supplies contexts for the tasks
      start - the integer value from which to start iterating
      count - the number of times this task should be called
      action - the task to perform, the current iteration index is provided as a parameter