@Override
  public void schedule(final ReportingTaskNode taskNode, final ScheduleState scheduleState) {
    final Runnable reportingTaskWrapper = new ReportingTaskWrapper(taskNode, scheduleState);
    final long schedulingNanos = taskNode.getSchedulingPeriod(TimeUnit.NANOSECONDS);

    final ScheduledFuture<?> future =
        flowEngine.scheduleWithFixedDelay(
            reportingTaskWrapper, 0L, schedulingNanos, TimeUnit.NANOSECONDS);
    final List<ScheduledFuture<?>> futures = new ArrayList<>(1);
    futures.add(future);
    scheduleState.setFutures(futures);

    logger.info("{} started.", taskNode.getReportingTask());
  }
  @Override
  public final synchronized void start() {
    if (!stopped) {
      throw new IllegalStateException(
          "Heartbeat Monitor cannot be started because it is already started");
    }

    stopped = false;
    logger.info("Heartbeat Monitor started");

    try {
      onStart();
    } catch (final Exception e) {
      logger.error("Failed to start Heartbeat Monitor", e);
    }

    this.future =
        flowEngine.scheduleWithFixedDelay(
            new Runnable() {
              @Override
              public void run() {
                try {
                  monitorHeartbeats();
                } catch (final Exception e) {
                  clusterCoordinator.reportEvent(
                      null,
                      Severity.ERROR,
                      "Failed to process heartbeats from nodes due to " + e.toString());
                  logger.error("Failed to process heartbeats", e);
                }
              }
            },
            heartbeatIntervalMillis,
            heartbeatIntervalMillis,
            TimeUnit.MILLISECONDS);
  }
 @Override
 public void shutdown() {
   flowEngine.shutdown();
 }
  @Override
  public void schedule(final Connectable connectable, final ScheduleState scheduleState) {

    final List<ScheduledFuture<?>> futures = new ArrayList<>();
    for (int i = 0; i < connectable.getMaxConcurrentTasks(); i++) {
      final Callable<Boolean> continuallyRunTask;
      final ProcessContext processContext;

      // Determine the task to run and create it.
      if (connectable.getConnectableType() == ConnectableType.PROCESSOR) {
        final ProcessorNode procNode = (ProcessorNode) connectable;
        final StandardProcessContext standardProcContext =
            new StandardProcessContext(
                procNode, flowController, encryptor, getStateManager(connectable.getIdentifier()));
        final ContinuallyRunProcessorTask runnableTask =
            new ContinuallyRunProcessorTask(
                this, procNode, flowController, contextFactory, scheduleState, standardProcContext);

        continuallyRunTask = runnableTask;
        processContext = standardProcContext;
      } else {
        processContext =
            new ConnectableProcessContext(
                connectable, encryptor, getStateManager(connectable.getIdentifier()));
        continuallyRunTask =
            new ContinuallyRunConnectableTask(
                contextFactory, connectable, scheduleState, processContext);
      }

      final AtomicReference<ScheduledFuture<?>> futureRef = new AtomicReference<>();

      final Runnable yieldDetectionRunnable =
          new Runnable() {
            @Override
            public void run() {
              // Call the continually run task. It will return a boolean indicating whether or not
              // we should yield
              // based on a lack of work for to do for the component.
              final boolean shouldYield;
              try {
                shouldYield = continuallyRunTask.call();
              } catch (final RuntimeException re) {
                throw re;
              } catch (final Exception e) {
                throw new ProcessException(e);
              }

              // If the component is yielded, cancel its future and re-submit it to run again
              // after the yield has expired.
              final long newYieldExpiration = connectable.getYieldExpiration();
              if (newYieldExpiration > System.currentTimeMillis()) {
                final long yieldMillis = newYieldExpiration - System.currentTimeMillis();
                final ScheduledFuture<?> scheduledFuture = futureRef.get();
                if (scheduledFuture == null) {
                  return;
                }

                // If we are able to cancel the future, create a new one and update the
                // ScheduleState so that it has
                // an accurate accounting of which futures are outstanding; we must then also update
                // the futureRef
                // so that we can do this again the next time that the component is yielded.
                if (scheduledFuture.cancel(false)) {
                  final long yieldNanos = TimeUnit.MILLISECONDS.toNanos(yieldMillis);

                  synchronized (scheduleState) {
                    if (scheduleState.isScheduled()) {
                      final ScheduledFuture<?> newFuture =
                          flowEngine.scheduleWithFixedDelay(
                              this,
                              yieldNanos,
                              connectable.getSchedulingPeriod(TimeUnit.NANOSECONDS),
                              TimeUnit.NANOSECONDS);

                      scheduleState.replaceFuture(scheduledFuture, newFuture);
                      futureRef.set(newFuture);
                    }
                  }
                }
              } else if (noWorkYieldNanos > 0L && shouldYield) {
                // Component itself didn't yield but there was no work to do, so the framework will
                // choose
                // to yield the component automatically for a short period of time.
                final ScheduledFuture<?> scheduledFuture = futureRef.get();
                if (scheduledFuture == null) {
                  return;
                }

                // If we are able to cancel the future, create a new one and update the
                // ScheduleState so that it has
                // an accurate accounting of which futures are outstanding; we must then also update
                // the futureRef
                // so that we can do this again the next time that the component is yielded.
                if (scheduledFuture.cancel(false)) {
                  synchronized (scheduleState) {
                    if (scheduleState.isScheduled()) {
                      final ScheduledFuture<?> newFuture =
                          flowEngine.scheduleWithFixedDelay(
                              this,
                              noWorkYieldNanos,
                              connectable.getSchedulingPeriod(TimeUnit.NANOSECONDS),
                              TimeUnit.NANOSECONDS);

                      scheduleState.replaceFuture(scheduledFuture, newFuture);
                      futureRef.set(newFuture);
                    }
                  }
                }
              }
            }
          };

      // Schedule the task to run
      final ScheduledFuture<?> future =
          flowEngine.scheduleWithFixedDelay(
              yieldDetectionRunnable,
              0L,
              connectable.getSchedulingPeriod(TimeUnit.NANOSECONDS),
              TimeUnit.NANOSECONDS);

      // now that we have the future, set the atomic reference so that if the component is yielded
      // we
      // are able to then cancel this future.
      futureRef.set(future);

      // Keep track of the futures so that we can update the ScheduleState.
      futures.add(future);
    }

    scheduleState.setFutures(futures);
    logger.info(
        "Scheduled {} to run with {} threads", connectable, connectable.getMaxConcurrentTasks());
  }