/* * 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.geode.internal.util; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; /** * Wraps an executor with a task queue so that currently executing tasks can be aborted. */ public class AbortableTaskService { /** the executor */ private final Executor exec; /** the queue of executing tasks */ private final Queue<AbortingRunnable> tasks; /** * Provides an executable interface that can be aborted during execution. */ public interface AbortableTask { /** * Invoked to execute the task. The task implementation should periodically check the aborted * flag and take appropriate action. * * @param aborted set to true when the task has been aborted */ void runOrAbort(AtomicBoolean aborted); /** * Invoked when a task is aborted prior to execution. */ void abortBeforeRun(); } public AbortableTaskService(Executor exec) { this.exec = exec; tasks = new ConcurrentLinkedQueue<AbortingRunnable>(); } /** * Executes the task using the embedded executor. * * @param task the task to execute */ public void execute(AbortableTask task) { AbortingRunnable ar = new AbortingRunnable(task); tasks.add(ar); try { exec.execute(ar); } catch (RejectedExecutionException e) { tasks.remove(ar); throw e; } } /** * Aborts all executing tasks. */ public void abortAll() { for (AbortingRunnable ar : tasks) { ar.abort(); } } /** * Waits for all currently executing tasks to complete. */ public void waitForCompletion() { boolean interrupted = false; for (AbortingRunnable ar : tasks) { try { ar.waitForCompletion(); } catch (InterruptedException e) { interrupted = true; } } if (interrupted) { Thread.currentThread().interrupt(); } } /** * Returns true if all tasks are done or aborted. */ public boolean isCompleted() { for (AbortingRunnable ar : tasks) { synchronized (ar) { if (!ar.done) { return false; } } } return true; } private class AbortingRunnable implements Runnable { /** the task to execute */ private final AbortableTask task; /** true if the task is aborted */ private AtomicBoolean aborted; /** true if the task has begun */ private AtomicBoolean hasStarted; /** true if the task is complete */ private boolean done; public AbortingRunnable(AbortableTask task) { this.task = task; aborted = new AtomicBoolean(false); hasStarted = new AtomicBoolean(false); done = false; } private synchronized void waitForCompletion() throws InterruptedException { while (!done) { wait(); } } private synchronized void signalDone() { done = true; notifyAll(); } private void abort() { aborted.set(true); if (hasStarted.compareAndSet(false, true)) { try { task.abortBeforeRun(); } finally { tasks.remove(this); signalDone(); } } } @Override public void run() { if (hasStarted.compareAndSet(false, true)) { try { task.runOrAbort(aborted); } finally { tasks.remove(this); signalDone(); } } } } }