private void handleFailure(
     final SettableFuture<Response> future,
     final Supplier<ListenableFuture<Response>> code,
     final long deadline,
     final long delay,
     final TimeUnit timeUnit,
     final Throwable t) {
   if (clock.now().getMillis() < deadline) {
     if (delay > 0) {
       executorService.schedule(
           new Runnable() {
             @Override
             public void run() {
               startRetry(future, code, deadline - 1, delay, timeUnit);
             }
           },
           delay,
           timeUnit);
     } else {
       startRetry(future, code, deadline - 1, delay, timeUnit);
     }
   } else {
     future.setException(t);
   }
 }
 public void testNoOpScheduledExecutorShutdown() {
   ListeningScheduledExecutorService executor = TestingExecutors.noOpScheduledExecutor();
   assertFalse(executor.isShutdown());
   assertFalse(executor.isTerminated());
   executor.shutdown();
   assertTrue(executor.isShutdown());
   assertTrue(executor.isTerminated());
 }
 public void testNoOpScheduledExecutorInvokeAll() throws ExecutionException, InterruptedException {
   ListeningScheduledExecutorService executor = TestingExecutors.noOpScheduledExecutor();
   taskDone = false;
   Callable<Boolean> task =
       new Callable<Boolean>() {
         @Override
         public Boolean call() {
           taskDone = true;
           return taskDone;
         }
       };
   List<Future<Boolean>> futureList =
       executor.invokeAll(ImmutableList.of(task), 10, TimeUnit.MILLISECONDS);
   Future<Boolean> future = futureList.get(0);
   assertFalse(taskDone);
   assertTrue(future.isDone());
   try {
     future.get();
     fail();
   } catch (CancellationException e) {
     // pass
   }
 }
  public NamespaceExtractionCacheManager(
      Lifecycle lifecycle,
      final ServiceEmitter serviceEmitter,
      final Map<Class<? extends ExtractionNamespace>, ExtractionNamespaceCacheFactory<?>>
          namespaceFunctionFactoryMap) {
    this.listeningScheduledExecutorService =
        MoreExecutors.listeningDecorator(
            Executors.newScheduledThreadPool(
                1,
                new ThreadFactoryBuilder()
                    .setDaemon(true)
                    .setNameFormat("NamespaceExtractionCacheManager-%d")
                    .setPriority(Thread.MIN_PRIORITY)
                    .build()));
    ExecutorServices.manageLifecycle(lifecycle, listeningScheduledExecutorService);
    this.serviceEmitter = serviceEmitter;
    this.namespaceFunctionFactoryMap = namespaceFunctionFactoryMap;
    listeningScheduledExecutorService.scheduleAtFixedRate(
        new Runnable() {
          long priorTasksStarted = 0L;

          @Override
          public void run() {
            try {
              final long tasks = tasksStarted.get();
              serviceEmitter.emit(
                  ServiceMetricEvent.builder()
                      .build("namespace/deltaTasksStarted", tasks - priorTasksStarted));
              priorTasksStarted = tasks;
              monitor(serviceEmitter);
            } catch (Exception e) {
              log.error(e, "Error emitting namespace stats");
              if (Thread.currentThread().isInterrupted()) {
                throw Throwables.propagate(e);
              }
            }
          }
        },
        1,
        10,
        TimeUnit.MINUTES);
  }
  // For testing purposes this is protected
  protected <T extends ExtractionNamespace> ListenableFuture<?> schedule(
      final String id,
      final T namespace,
      final ExtractionNamespaceCacheFactory<T> factory,
      final Runnable postRunnable,
      final String cacheId) {
    log.debug("Trying to update namespace [%s]", id);
    final NamespaceImplData implDatum = implData.get(id);
    if (implDatum != null) {
      synchronized (implDatum.enabled) {
        if (implDatum.enabled.get()) {
          // We also check at the end of the function, but fail fast here
          throw new IAE(
              "Namespace [%s] already exists! Leaving prior running", namespace.toString());
        }
      }
    }
    final long updateMs = namespace.getPollMs();
    final CountDownLatch startLatch = new CountDownLatch(1);

    final Runnable command =
        new Runnable() {
          @Override
          public void run() {
            try {
              startLatch.await(); // wait for "election" to leadership or cancellation
              if (!Thread.currentThread().isInterrupted()) {
                final Map<String, String> cache = getCacheMap(cacheId);
                final String preVersion = lastVersion.get(id);
                final Callable<String> runnable =
                    factory.getCachePopulator(id, namespace, preVersion, cache);

                tasksStarted.incrementAndGet();
                final String newVersion = runnable.call();
                if (preVersion != null && preVersion.equals(newVersion)) {
                  throw new CancellationException(
                      String.format("Version `%s` already exists", preVersion));
                }
                if (newVersion != null) {
                  lastVersion.put(id, newVersion);
                }
                postRunnable.run();
                log.debug("Namespace [%s] successfully updated", id);
              }
            } catch (Throwable t) {
              delete(cacheId);
              if (t instanceof CancellationException) {
                log.debug(t, "Namespace [%s] cancelled", id);
              } else {
                log.error(t, "Failed update namespace [%s]", namespace);
              }
              if (Thread.currentThread().isInterrupted()) {
                throw Throwables.propagate(t);
              }
            }
          }
        };

    ListenableFuture<?> future;
    try {
      if (updateMs > 0) {
        future =
            listeningScheduledExecutorService.scheduleAtFixedRate(
                command, 0, updateMs, TimeUnit.MILLISECONDS);
      } else {
        future = listeningScheduledExecutorService.schedule(command, 0, TimeUnit.MILLISECONDS);
      }

      final NamespaceImplData me = new NamespaceImplData(future, namespace, id);
      final NamespaceImplData other = implData.putIfAbsent(id, me);
      if (other != null) {
        if (!future.isDone() && !future.cancel(true)) {
          log.warn("Unable to cancel future for namespace[%s] on race loss", id);
        }
        throw new IAE("Namespace [%s] already exists! Leaving prior running", namespace);
      } else {
        if (!me.enabled.compareAndSet(false, true)) {
          log.wtf("How did someone enable this before ME?");
        }
        log.debug("I own namespace [%s]", id);
        return future;
      }
    } finally {
      startLatch.countDown();
    }
  }
 protected boolean waitForServiceToEnd(long time, TimeUnit unit) throws InterruptedException {
   return listeningScheduledExecutorService.awaitTermination(time, unit);
 }
 /*
  * (non-Javadoc)
  *
  * @see com.google.common.util.concurrent.AbstractScheduledService#shutDown()
  */
 @Override
 protected void shutDown() throws Exception {
   service.shutdown();
   super.shutDown();
 }
  /**
   * Use the provided BulkJobFactory to build and submit BulkJob items as ListenableFuture objects
   */
  @ExceptionMetered(name = "BulkJobScheduledService_submitWork_exceptions", group = "scheduler")
  private void submitWork(final JobDescriptor jobDescriptor) {
    List<Job> jobs;

    try {
      jobs = jobFactory.jobsFrom(jobDescriptor);
    } catch (JobNotFoundException e) {
      LOG.error("Could not create jobs", e);
      return;
    }

    for (final Job job : jobs) {

      // job execution needs to be external to both the callback and the task.
      // This way regardless of any error we can
      // mark a job as failed if required
      final JobExecution execution = new JobExecutionImpl(jobDescriptor);

      // We don't care if this is atomic (not worth using a lock object)
      // we just need to prevent NPEs from ever occurring
      final JobListener currentListener = this.jobListener;

      ListenableFuture<Void> future =
          service.submit(
              new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                  capacitySemaphore.acquire();

                  execution.start(maxFailCount);

                  jobAccessor.save(execution);

                  // this job is dead, treat it as such
                  if (execution.getStatus() == Status.DEAD) {
                    return null;
                  }

                  // TODO wrap and throw specifically typed exception for onFailure,
                  // needs jobId
                  job.execute(execution);

                  if (currentListener != null) {
                    currentListener.onSubmit(execution);
                  }

                  return null;
                }
              });

      Futures.addCallback(
          future,
          new FutureCallback<Void>() {
            @Override
            public void onSuccess(Void param) {

              if (execution.getStatus() == Status.IN_PROGRESS) {
                LOG.info("Successful completion of bulkJob {}", execution);
                execution.completed();
              }

              jobAccessor.save(execution);
              capacitySemaphore.release();

              if (currentListener != null) {
                currentListener.onSuccess(execution);
              }
            }

            @Override
            public void onFailure(Throwable throwable) {
              LOG.error("Failed execution for bulkJob", throwable);
              // mark it as failed
              if (execution.getStatus() == Status.IN_PROGRESS) {
                execution.failed();
              }

              jobAccessor.save(execution);
              capacitySemaphore.release();

              if (currentListener != null) {
                currentListener.onFailure(execution);
              }
            }
          });
    }
  }