/*
* Copyright 2015 Evgeny Dolganov (evgenij.dolganov@gmail.com).
*
* 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 och.service;
import static och.util.ExceptionUtil.*;
import static och.util.Util.*;
import static och.util.concurrent.AsyncListener.*;
import static och.util.concurrent.ExecutorsUtil.*;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import och.api.exception.ExpectedException;
import och.util.concurrent.AsyncListener;
import och.util.model.CallableVoid;
import org.apache.commons.logging.Log;
public class AsyncService implements UncaughtExceptionHandler {
private Log log = getLog(getClass());
private ArrayList<AsyncListener> listeners = new ArrayList<>();
private ArrayList<AsyncListener> scheduleListeners = new ArrayList<>();
private ExecutorService workThreads;
private ScheduledExecutorService scheduledService;
public AsyncService() {
this(10, 2);
}
public AsyncService(int asyncThreads, int scheduleThreads) {
workThreads = newFixedThreadPool("AsyncService", asyncThreads, this);
scheduledService = newScheduledThreadPool("AsyncService-scheduled", scheduleThreads, this);
}
public void addListener(AsyncListener l){
listeners.add(l);
}
public void addScheduleListener(AsyncListener l){
scheduleListeners.add(l);
}
public <T> Future<T> invoke(final Callable<T> task){
Future<T> future = workThreads.submit(new Callable<T>() {
@Override
public T call() throws Exception {
try {
return task.call();
}catch (Throwable t) {
ExpectedException.logError(log, t, "can't invoke async");
throw getExceptionOrThrowError(t);
}
}
});
fireAsyncEvent(listeners, future);
return future;
}
/**
* Good for simple cases
* <pre>
* task --- delay --- task
* </pre>
*/
public void scheduleWithFixedDelay(String commandName, Runnable command, long initialDelayMs, long delayMs){
logScheduleWithFixedDelay(commandName, delayMs);
ScheduledFuture<?> future = scheduledService.scheduleWithFixedDelay(command, initialDelayMs, delayMs, TimeUnit.MILLISECONDS);
fireScheduleEvent(future);
}
/**
* Good for simple cases
* <pre>
* task --- delay --- task
* </pre>
*/
public void tryScheduleWithFixedDelay(String commandName, CallableVoid command, long initialDelayMs, long delayMs){
logScheduleWithFixedDelay(commandName, delayMs);
ScheduledFuture<?> future = scheduledService.scheduleWithFixedDelay(()->{
try {
command.call();
}catch(Throwable t){
log.error("can't scheduleWithFixedDelay", t);
}
}, initialDelayMs, delayMs, TimeUnit.MILLISECONDS);
fireScheduleEvent(future);
}
private void logScheduleWithFixedDelay(String commandName, long delayMs){
log.info("scheduleWithFixedDelay: '"+commandName+"' with delayMs="+delayMs);
if(delayMs < 60_000L) log.warn("very small delay "+delayMs+"ms for '"+commandName+"'");
}
/**
* Good for time periods:
* <pre>
* period --- period --- period --- period
* taaaaaaaaaaask taaaaaaaaaaask
* </pre>
*/
public void scheduleAtFixedRate(String commandName, Runnable command, long initialDelayMs, long periodMs){
logScheduleAtFixedRate(commandName, periodMs);
ScheduledFuture<?> future = scheduledService.scheduleAtFixedRate(command, initialDelayMs, periodMs, TimeUnit.MILLISECONDS);
fireScheduleEvent(future);
}
/**
* Good for time periods:
* <pre>
* period --- period --- period --- period
* taaaaaaaaaaask taaaaaaaaaaask
* </pre>
*/
public void tryScheduleAtFixedRate(String commandName, CallableVoid command, long initialDelayMs, long periodMs){
logScheduleAtFixedRate(commandName, periodMs);
ScheduledFuture<?> future = scheduledService.scheduleAtFixedRate(()->{
try {
command.call();
}catch(Throwable t){
log.error("can't scheduleAtFixedRate", t);
}
}, initialDelayMs, periodMs, TimeUnit.MILLISECONDS);
fireScheduleEvent(future);
}
private void logScheduleAtFixedRate(String commandName, long periodMs){
log.info("scheduleAtFixedRate: '"+commandName+"' with periodMs="+periodMs);
if(periodMs < 60_000L) log.warn("very small period "+periodMs+"ms for '"+commandName+"'");
}
private void fireScheduleEvent(Future<?> future) {
for (AsyncListener l : scheduleListeners) {
try {
l.onFutureEvent(future);
}catch (Throwable t) {
log.error("schedule listener error", t);
}
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
log.error("can't invoke async", e);
}
}