private ScheduledFuture<?> scheduleCancelationTask(final ExecutionContext executionContext,
      final long timeLimitMillis) {

    // Make ThreadLocal isCanceled available to closure, which will run in a separate thread
    final AtomicBoolean queryCanceledThreadLocal =
        ExecutionContext.isCanceled.get();

    /*
     * This is where the GoF "State" design pattern comes home to roost.
     *
     * memoryState.schedule() is going to either schedule or throw an exception depending on what
     * state we are _currently_ in. Remember the switching of that state (reference) happens
     * in a separate thread, up in the setLowMemory() method, generally called by the
     * HeapMemoryMonitor.
     *
     * The first line of the lambda/closure, when it _eventually_ runs (in yet another thread--
     * a thread from the executor), will access what is _then_ the current state, through
     * memoryState, to createCancelationException().
     */
    return memoryState.schedule(() -> {
      final CacheRuntimeException exception = memoryState
          .createCancellationException(timeLimitMillis, executionContext);

      executionContext.setQueryCanceledException(exception);
      queryCanceledThreadLocal.set(true);

    }, timeLimitMillis, TimeUnit.MILLISECONDS, executor, executionContext);
  }
Run all cancelation tasks. Leave the executor's task queue empty.
geode-core/src/main/java/org/apache/geode/cache/query/internal/QueryMonitor.java