| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed 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 com.android.google.gce.gceservice; |
| |
| import android.util.Log; |
| import com.android.google.gce.gceservice.JobBase; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Set; |
| import java.util.HashSet; |
| |
| public class JobExecutor { |
| private static final int THREAD_POOL_SIZE = 8; |
| private static final String LOG_TAG = "GceExecutor"; |
| private static final int ITERATION_PERIOD_MS = 10 * 1000; |
| private final ScheduledExecutorService mExecutor; |
| private final Set<String> mStartedJobs; |
| |
| public JobExecutor() { |
| mExecutor = Executors.newScheduledThreadPool(THREAD_POOL_SIZE); |
| mStartedJobs = new HashSet<String>(); |
| } |
| |
| /** Schedule job for (periodic) execution. |
| * |
| * First execution is performed at the earliest possibility. |
| * Execution stops when a call to execute() returns a nonpositive number. |
| * |
| * Note: iteration time is |delaySeconds| + total time needed to execute job. |
| */ |
| public void schedule(final JobBase job, final GceFuture<?>... futures) { |
| mExecutor.schedule(new Runnable() { |
| private boolean mDependenciesReady = false; |
| private ArrayList<GceFuture<?>> mFutures = new ArrayList<GceFuture<?>>(); |
| |
| { |
| for (GceFuture<?> future: futures) { |
| mFutures.add(future); |
| } |
| } |
| |
| public void run() { |
| if (!mDependenciesReady) { |
| while (!mFutures.isEmpty()) { |
| ArrayList<GceFuture<?>> stragglers = new ArrayList<GceFuture<?>>(); |
| long endTime = System.currentTimeMillis() + ITERATION_PERIOD_MS; |
| for (GceFuture<?> future : mFutures) { |
| try { |
| // Wait for at most ITERATION_PERIOD_MS. Check all futures, |
| // collect only those that still failed to complete. |
| future.get( |
| Math.max(0, endTime - System.currentTimeMillis()), |
| TimeUnit.MILLISECONDS); |
| } catch (TimeoutException e) { |
| stragglers.add(future); |
| } catch (InterruptedException e) { |
| // In theory we should re-try this one. |
| // In practice this hardly ever happens, so let's just |
| // give it another go in the second loop. |
| stragglers.add(future); |
| } catch (Exception e) { |
| Log.e(LOG_TAG, String.format( |
| "Could not start job %s.", job.getClass().getName()), |
| e); |
| job.onDependencyFailed(e); |
| return; |
| } |
| } |
| |
| mFutures = stragglers; |
| if (!mFutures.isEmpty()) { |
| job.onDependencyStraggling(mFutures); |
| } |
| } |
| mDependenciesReady = true; |
| } |
| |
| try { |
| String jobName = job.getClass().getName(); |
| if (!mStartedJobs.contains(jobName)) { |
| mStartedJobs.add(jobName); |
| } |
| int delaySeconds = job.execute(); |
| if (delaySeconds > 0) { |
| mExecutor.schedule(this, delaySeconds, TimeUnit.SECONDS); |
| } else { |
| mStartedJobs.remove(jobName); |
| } |
| } catch (Exception e) { |
| Log.e(LOG_TAG, String.format("Job %s threw an exception and will not be re-scheduled.", |
| job.getClass().getName()), e); |
| } |
| } |
| }, 0, TimeUnit.SECONDS); |
| } |
| } |