/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ambari.server.topology; import java.util.Calendar; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Callable service implementation for executing tasks asynchronously. * The service repeatedly tries to execute the provided task till it successfully completes, or the provided timeout * interval is exceeded. * * @param <T> the type returned by the task to be executed */ public class AsyncCallableService<T> implements Callable<T> { private static final Logger LOG = LoggerFactory.getLogger(AsyncCallableService.class); // task execution is done on a separate thread provided by this executor private final ScheduledExecutorService executorService; // the task to be executed private final Callable<T> task; // the total time the allowed for the task to be executed (retries will be happen within this timeframe in // milliseconds) private final long timeout; // the delay between two consecutive execution trials in milliseconds private final long delay; private T serviceResult; private final Set<Exception> errors = new HashSet<>(); public AsyncCallableService(Callable<T> task, long timeout, long delay, ScheduledExecutorService executorService) { this.task = task; this.executorService = executorService; this.timeout = timeout; this.delay = delay; } @Override public T call() { long startTimeInMillis = Calendar.getInstance().getTimeInMillis(); LOG.info("Task execution started at: {}", startTimeInMillis); // task execution started on a new thread Future<T> future = executorService.submit(task); while (!taskCompleted(future)) { if (!timeoutExceeded(startTimeInMillis)) { LOG.debug("Retrying task execution in [ {} ] milliseconds.", delay); future = executorService.schedule(task, delay, TimeUnit.MILLISECONDS); } else { LOG.debug("Timout exceeded, cancelling task ... "); // making sure the task gets cancelled! if (!future.isDone()) { boolean cancelled = future.cancel(true); LOG.debug("Task cancelled: {}", cancelled); } else { LOG.debug("Task already done."); } LOG.info("Timeout exceeded, task execution won't be retried!"); // exit the "retry" loop! break; } } LOG.info("Exiting Async task execution with the result: [ {} ]", serviceResult); return serviceResult; } private boolean taskCompleted(Future<T> future) { boolean completed = false; try { LOG.debug("Retrieving task execution result ..."); // should receive task execution result within the configured timeout interval // exceptions thrown from the task are propagated here T taskResult = future.get(timeout, TimeUnit.MILLISECONDS); // task failures are expected to be reportesd as exceptions LOG.debug("Task successfully executed: {}", taskResult); setServiceResult(taskResult); errors.clear(); completed = true; } catch (Exception e) { // Future.isDone always true here! LOG.info("Exception during task execution: ", e); errors.add(e); } return completed; } private boolean timeoutExceeded(long startTimeInMillis) { return timeout < Calendar.getInstance().getTimeInMillis() - startTimeInMillis; } private void setServiceResult(T serviceResult) { this.serviceResult = serviceResult; } public Set<Exception> getErrors() { return errors; } }