/*
* 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.openejb.core.timer;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.quartz.SchedulerConfigException;
import org.apache.openejb.quartz.spi.ThreadPool;
import org.apache.openejb.util.ExecutorBuilder;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @version $Rev$ $Date$
*/
@SuppressWarnings("UnusedDeclaration")
public class DefaultTimerThreadPoolAdapter implements ThreadPool {
private static final Logger logger = Logger.getInstance(LogCategory.TIMER, "org.apache.openejb.util.resources");
public static final String OPENEJB_TIMER_POOL_SIZE = "openejb.timer.pool.size";
public static final String OPENEJB_EJB_TIMER_POOL_AWAIT_SECONDS = "openejb.ejb-timer-pool.shutdown.timeout";
private Executor executor;
private String instanceId;
private String instanceName;
/**
* Mock support for property: org.quartz.threadPool.threadCount
*/
private int threadCount = Integer.parseInt(SystemInstance.get().getProperty(OPENEJB_TIMER_POOL_SIZE, "3"));
/**
* Mock support for property: org.quartz.threadPool.threadPriority
*/
private int threadPriority = Thread.NORM_PRIORITY;
private final Object threadAvailableLock = new Object();
private boolean threadPoolExecutorUsed;
// This is to prevent other parts of the code becoming dependent
// on the executor produced for EJB Timers
//
// If we want to share an Executor across the whole system
// for @Asynchronous and @Remote execution we should design
// that specifically and have it explicitly created somewhere
public static final class TimerExecutor {
private final Executor executor;
private final AtomicInteger references = new AtomicInteger(0);
private TimerExecutor(final Executor executor) {
if (executor == null) {
throw new IllegalArgumentException("executor cannot be null");
}
this.executor = executor;
}
public TimerExecutor incr() {
references.incrementAndGet();
return this;
}
public boolean decr() {
return references.decrementAndGet() == 0;
}
}
@Override
public int blockForAvailableThreads() {
if (this.threadPoolExecutorUsed) {
final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) this.executor;
synchronized (this.threadAvailableLock) {
while (threadPoolExecutor.getMaximumPoolSize() - threadPoolExecutor.getActiveCount() < 1 && !threadPoolExecutor.isShutdown()) {
try {
this.threadAvailableLock.wait(500L);
} catch (final InterruptedException ignore) {
// no-op
}
}
return threadPoolExecutor.getMaximumPoolSize() - threadPoolExecutor.getActiveCount();
}
} else {
return 1;
}
}
@Override
public void setInstanceId(final String instanceId) {
this.instanceId = instanceId;
}
@Override
public void setInstanceName(final String instanceName) {
this.instanceName = instanceName;
}
public String getInstanceId() {
return this.instanceId;
}
public String getInstanceName() {
return this.instanceName;
}
@Override
public int getPoolSize() {
if (this.threadPoolExecutorUsed) {
return ((ThreadPoolExecutor) this.executor).getPoolSize();
} else {
return 1;
}
}
@Override
public synchronized void initialize() throws SchedulerConfigException {
final TimerExecutor timerExecutor = SystemInstance.get().getComponent(TimerExecutor.class);
if (timerExecutor != null) {
this.executor = timerExecutor.incr().executor;
} else {
this.executor = new ExecutorBuilder()
.size(threadCount)
.prefix("EjbTimerPool")
.build(SystemInstance.get().getOptions());
final TimerExecutor value = new TimerExecutor(this.executor).incr();
SystemInstance.get().setComponent(TimerExecutor.class, value);
}
this.threadPoolExecutorUsed = this.executor instanceof ThreadPoolExecutor;
if (!this.threadPoolExecutorUsed) {
logger.warning("Unrecognized ThreadPool implementation [" + this.executor.getClass().getName() + "] is used, EJB Timer service may not work correctly");
}
}
@Override
public boolean runInThread(final Runnable runnable) {
try {
this.executor.execute(runnable);
return true;
} catch (final RejectedExecutionException e) {
logger.error("Failed to execute timer task", e);
return false;
}
}
@Override
public synchronized void shutdown(final boolean waitForJobsToComplete) {
if (threadPoolExecutorUsed) {
final SystemInstance systemInstance = SystemInstance.get();
final TimerExecutor te = systemInstance.getComponent(TimerExecutor.class);
if (te != null) {
if (te.executor == executor) {
if (te.decr()) {
doShutdownExecutor(waitForJobsToComplete);
systemInstance.removeComponent(TimerExecutor.class);
} else { // flush jobs, maybe not all dedicated to this threadpool if shared but shouldn't be an issue
final ThreadPoolExecutor tpe = ThreadPoolExecutor.class.cast(executor);
if (waitForJobsToComplete) {
final Collection<Runnable> jobs = new ArrayList<>();
tpe.getQueue().drainTo(jobs);
for (final Runnable r : jobs) {
try {
r.run();
} catch (final Exception e) {
logger.warning(e.getMessage(), e);
}
}
}
}
} else {
doShutdownExecutor(waitForJobsToComplete);
}
} else {
doShutdownExecutor(waitForJobsToComplete);
}
}
}
private void doShutdownExecutor(final boolean waitJobs) {
final ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
tpe.shutdown();
if (waitJobs) {
final int timeout = SystemInstance.get().getOptions().get(OPENEJB_EJB_TIMER_POOL_AWAIT_SECONDS, 5);
try {
tpe.awaitTermination(timeout, TimeUnit.SECONDS);
} catch (final InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
public int getThreadCount() {
return this.threadCount;
}
public void setThreadCount(final int threadCount) {
this.threadCount = threadCount;
}
public int getThreadPriority() {
return this.threadPriority;
}
public void setThreadPriority(final int threadPriority) {
this.threadPriority = threadPriority;
}
}