/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or authors. * * 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 io.janusproject.kernel.services.jdk.executors; import java.lang.Thread.UncaughtExceptionHandler; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.google.common.util.concurrent.Service; import com.google.inject.Inject; import com.google.inject.Singleton; import io.janusproject.JanusConfig; import io.janusproject.services.AbstractDependentService; import io.janusproject.services.executor.JanusCallable; import io.janusproject.services.executor.JanusRunnable; /** * Platform service that supports the execution resources. * * <p>This service is thread-safe. * * @author $Author: srodriguez$ * @author $Author: ngaud$ * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ @Singleton public class JdkExecutorService extends AbstractDependentService implements io.janusproject.services.executor.ExecutorService { private ScheduledExecutorService schedules; private ExecutorService exec; private ScheduledFuture<?> purgeTask; private UncaughtExceptionHandler uncaughtExceptionHandler; /** * Construct. */ public JdkExecutorService() { // } /** * Change the JRE service for scheduled tasks. * * @param service - the JRE service. */ @Inject void setScheduledExecutorService(ScheduledExecutorService service) { this.schedules = service; } /** * Change the JRE service for scheduled tasks. * * @param service - the JRE service. */ @Inject void setExecutorService(ExecutorService service) { this.exec = service; } /** * Change the default exception handler. * * @param handler the default exception handler. */ @Inject void setUncaughtExceptionHandler(UncaughtExceptionHandler handler) { this.uncaughtExceptionHandler = handler; } @Override public final Class<? extends Service> getServiceType() { return io.janusproject.services.executor.ExecutorService.class; } @Override protected void doStart() { assert this.schedules != null; assert this.exec != null; if (this.uncaughtExceptionHandler != null) { Thread.setDefaultUncaughtExceptionHandler(this.uncaughtExceptionHandler); } // Launch a periodic task that is purging the executor pools. if ((this.schedules instanceof ThreadPoolExecutor) || (this.exec instanceof ThreadPoolExecutor)) { final int delay = JanusConfig.getSystemPropertyAsInteger(JanusConfig.KERNEL_THREAD_PURGE_DELAY_NAME, JanusConfig.KERNEL_THREAD_PURGE_DELAY_VALUE); this.purgeTask = this.schedules.scheduleWithFixedDelay(new Purger(), delay, delay, TimeUnit.SECONDS); } notifyStarted(); } @Override protected void doStop() { if (this.purgeTask != null) { this.purgeTask.cancel(true); this.purgeTask = null; } this.exec.shutdown(); this.schedules.shutdown(); try { final int timeout = JanusConfig.getSystemPropertyAsInteger(JanusConfig.KERNEL_THREAD_TIMEOUT_NAME, JanusConfig.KERNEL_THREAD_TIMEOUT_VALUE); this.schedules.awaitTermination(timeout, TimeUnit.SECONDS); this.exec.awaitTermination(timeout, TimeUnit.SECONDS); } catch (InterruptedException e) { // This error may occur when the thread is killed before this // function is waiting for its termination. } finally { this.schedules.shutdownNow(); this.exec.shutdownNow(); notifyStopped(); } } /** Create a task with the given runnable. * * @param runnable the runnable. * @return the task. */ @SuppressWarnings("static-method") protected Runnable createTask(Runnable runnable) { if (runnable instanceof JanusRunnable) { return runnable; } return new JanusRunnable(runnable); } /** Create a task with the given callable. * * @param <T> the type of the returned value. * @param callable the callable. * @return the task. */ @SuppressWarnings("static-method") protected <T> Callable<T> createTask(Callable<T> callable) { if (callable instanceof JanusCallable<?>) { return callable; } return new JanusCallable<>(callable); } @Override public void execute(Runnable task) { this.exec.execute(createTask(task)); } @Override public int executeMultipleTimesInParallelAndWaitForTermination(Runnable task, int nbExecutions, int runGroupSize) throws InterruptedException { assert runGroupSize >= 1; final Runnable janusTask = createTask(task); if (nbExecutions > 1) { final AtomicInteger errors = new AtomicInteger(); final CountDownLatch doneSignal = new CountDownLatch(nbExecutions); if (runGroupSize > 1) { final int numberOfGroups = nbExecutions / runGroupSize; final int rest = nbExecutions - numberOfGroups * runGroupSize; for (int i = 0; i < numberOfGroups; ++i) { this.exec.execute(() -> { for (int j = 0; j < runGroupSize; ++j) { try { janusTask.run(); } catch (Throwable e) { errors.incrementAndGet(); } finally { doneSignal.countDown(); } } }); } if (rest > 1) { this.exec.execute(() -> { for (int j = 0; j < rest; ++j) { try { janusTask.run(); } catch (Throwable e) { errors.incrementAndGet(); } finally { doneSignal.countDown(); } } }); } } else { for (int i = 0; i < nbExecutions; ++i) { this.exec.execute(() -> { try { janusTask.run(); } catch (Throwable e) { errors.incrementAndGet(); } finally { doneSignal.countDown(); } }); } } // Wait for all creators to complete before continuing doneSignal.await(); return nbExecutions - errors.get(); } if (nbExecutions == 1) { janusTask.run(); return 1; } return 0; } @Override public Future<?> submit(Runnable task) { return this.exec.submit(createTask(task)); } @Override public <T> Future<T> submit(Runnable task, T result) { return this.exec.submit(createTask(task), result); } @Override public <T> Future<T> submit(Callable<T> task) { return this.exec.submit(createTask(task)); } @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { return this.schedules.schedule(createTask(command), delay, unit); } @Override public <T> ScheduledFuture<T> schedule(Callable<T> command, long delay, TimeUnit unit) { return this.schedules.schedule(createTask(command), delay, unit); } @Override public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { return this.schedules.scheduleAtFixedRate(createTask(command), initialDelay, period, unit); } @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { return this.schedules.scheduleWithFixedDelay(createTask(command), initialDelay, delay, unit); } @Override public ExecutorService getExecutorService() { return this.exec; } @Override public void purge() { if (this.exec instanceof ThreadPoolExecutor) { ((ThreadPoolExecutor) this.exec).purge(); } if (this.schedules instanceof ThreadPoolExecutor) { ((ThreadPoolExecutor) this.schedules).purge(); } } /** * Task that is purging the thread pools. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class Purger implements Runnable { private String oldThreadName; /** * Construct. */ Purger() { // } private boolean setName() { if (this.oldThreadName != null) { return false; } final Thread t = Thread.currentThread(); this.oldThreadName = t.getName(); t.setName(toString()); return true; } private boolean restoreName() { if (this.oldThreadName == null) { return false; } final Thread t = Thread.currentThread(); t.setName(this.oldThreadName); this.oldThreadName = null; return true; } @Override public void run() { assert setName(); try { purge(); } finally { assert restoreName(); } } @Override public String toString() { return "Janus Thread Purger"; //$NON-NLS-1$ } } }