/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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 org.uberfire.commons.async;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.ejb.Asynchronous;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.TransactionAttribute;
import javax.naming.InitialContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static javax.ejb.TransactionAttributeType.NOT_SUPPORTED;
@Singleton
@Startup
@TransactionAttribute(NOT_SUPPORTED)
public class SimpleAsyncExecutorService implements DisposableExecutor {
private static final Logger LOG = LoggerFactory.getLogger(SimpleAsyncExecutorService.class);
private static final Integer AWAIT_TERMINATION_TIMEOUT = Integer.parseInt(System.getProperty("org.uberfire.watcher.quitetimeout",
"3"));
private static final boolean USE_EXECUTOR_SAFE_MODE = Boolean.parseBoolean(System.getProperty("org.uberfire.async.executor.safemode",
"false"));
private static final Object lock = new Object();
private static final AtomicBoolean isEJB = new AtomicBoolean(false);
private static DisposableExecutor defaultInstance;
private static DisposableExecutor managedInstance;
private static DisposableExecutor unmanagedInstance;
private final ExecutorService executorService;
private final AtomicBoolean hasAlreadyShutdown = new AtomicBoolean(false);
private final Set<Future<?>> jobs = new CopyOnWriteArraySet<Future<?>>();
public SimpleAsyncExecutorService() {
executorService = null;
}
public SimpleAsyncExecutorService(boolean notEJB) {
executorService = Executors.newCachedThreadPool(new DescriptiveThreadFactory());
}
public static DisposableExecutor getDefaultInstance() {
synchronized (lock) {
if (defaultInstance == null) {
DisposableExecutor _executorManager = null;
//Unless overridden, delegate instantiation of the ExecutorService to the container
//See https://issues.jboss.org/browse/UF-244 and https://issues.jboss.org/browse/WFLY-4198
//When running in Hosted Mode (on Wildfly 8.1 at present) the System Property should be set
//to "true"
if (!USE_EXECUTOR_SAFE_MODE) {
try {
_executorManager = InitialContext.doLookup("java:module/SimpleAsyncExecutorService");
isEJB.set(true);
} catch (final Exception e) {
LOG.warn("Unable to instantiate EJB Asynchronous Bean. Falling back to Executors' CachedThreadPool.",
e);
}
} else {
LOG.info("Use of to Executors' CachedThreadPool has been requested; overriding container provisioning.");
}
if (_executorManager == null) {
if (unmanagedInstance == null) {
unmanagedInstance = new SimpleAsyncExecutorService(false);
}
defaultInstance = unmanagedInstance;
} else {
if (managedInstance == null) {
managedInstance = _executorManager;
}
defaultInstance = managedInstance;
}
}
}
return defaultInstance;
}
public static DisposableExecutor getUnmanagedInstance() {
synchronized (lock) {
if (unmanagedInstance == null) {
unmanagedInstance = new SimpleAsyncExecutorService(false);
}
return unmanagedInstance;
}
}
public static void shutdownInstances() {
synchronized (lock) {
if (!isEJB.get() && managedInstance != null) {
managedInstance.dispose();
}
if (unmanagedInstance != null) {
unmanagedInstance.dispose();
}
}
}
//Testing only, to enable re-use in the same JVM for multiple tests
static void recycle() {
defaultInstance = null;
managedInstance = null;
unmanagedInstance = null;
}
@Asynchronous
@Lock(LockType.READ)
@Override
public void execute(final Runnable r) {
if (executorService != null) {
jobs.add(executorService.submit(r));
} else {
r.run();
}
}
@Override
public void dispose() {
if (!hasAlreadyShutdown.getAndSet(true) && executorService != null) {
for (final Future<?> job : jobs) {
if (!job.isCancelled() && !job.isDone()) {
job.cancel(true);
}
}
executorService.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!executorService.awaitTermination(AWAIT_TERMINATION_TIMEOUT,
TimeUnit.SECONDS)) {
executorService.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!executorService.awaitTermination(AWAIT_TERMINATION_TIMEOUT,
TimeUnit.SECONDS)) {
LOG.error("Thread pool did not terminate.");
}
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
executorService.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
executorService.shutdown();
}
}
}