| /* |
| * 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.sis.internal.system; |
| |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.DelayQueue; |
| import java.util.concurrent.BlockingQueue; |
| import org.apache.sis.util.logging.Logging; |
| |
| |
| /** |
| * A thread executing short tasks after some (potentially zero nanosecond) delay. |
| * This class should be reserved to internal SIS usage without user's code. |
| * In practice some user code may be indirectly executed through SIS tasks invoking overrideable methods. |
| * But all submitted tasks shall be very quick, since there is only one thread shared by everyone. |
| * |
| * <p>The methods for use in this class are:</p> |
| * <ul> |
| * <li>{@link #schedule(DelayedRunnable)}</li> |
| * </ul> |
| * |
| * <h2>Comparison with {@code java.util.concurrent}</h2> |
| * We tried to use {@link java.util.concurrent.ScheduledThreadPoolExecutor} in a previous SIS version, |
| * but its "fixed-sized pool" design forces us to use only one thread if we do not want to waste resources |
| * (profiling shows that even a single thread has very low activity), which reduces the interest of that class. |
| * Combination of {@code ThreadPoolExecutor} super-class with {@code DelayedQueue} were not successful neither. |
| * |
| * <p>Given that:</p> |
| * <ul> |
| * <li>it seems difficult to configure {@code (Scheduled)ThreadPoolExecutor} in such a way |
| * that two or more threads are created only when really needed,</li> |
| * <li>using those executor services seems an overkill when the pool size is fixed to one thread,</li> |
| * <li>our profiling has show very low activity for that single thread anyway,</li> |
| * <li>we do not need cancellation and shutdown services for house keeping tasks (this is a daemon thread),</li> |
| * </ul> |
| * a more lightweight solution seems acceptable here. Pseudo-benchmarking using the |
| * {@code CacheTest.stress()} tests suggests that the lightweight solution is faster. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 0.7 |
| * |
| * @see <a href="https://issues.apache.org/jira/browse/SIS-76">SIS-76</a> |
| * |
| * @since 0.3 |
| * @module |
| */ |
| public final class DelayedExecutor extends DaemonThread { |
| /** |
| * Schedules the given short task for later execution in a daemon thread. |
| * The task will be executed after the delay specified by {@link DelayedRunnable#getDelay(TimeUnit)} |
| * The task must completes quickly, because we will typically use only one thread for all submitted tasks. |
| * Completion of the task shall not be critical, since the JVM is allowed to shutdown before task completion. |
| * |
| * @param task the task to schedule for later execution. |
| */ |
| public static void schedule(final DelayedRunnable task) { |
| QUEUE.add(task); |
| } |
| |
| /** |
| * List of delayed tasks to execute. |
| */ |
| private static final BlockingQueue<DelayedRunnable> QUEUE = new DelayQueue<>(); |
| |
| /** |
| * Creates the singleton instance of the {@code DelayedExecutor} thread. |
| */ |
| static { |
| synchronized (Threads.class) { |
| final DelayedExecutor thread; |
| Threads.lastCreatedDaemon = thread = new DelayedExecutor(Threads.lastCreatedDaemon); |
| /* |
| * Call to Thread.start() must be outside the constructor |
| * (Reference: Goetz et al.: "Java Concurrency in Practice"). |
| */ |
| thread.start(); |
| } |
| if (Supervisor.ENABLED) { |
| Supervisor.register(); |
| } |
| } |
| |
| /** |
| * Constructs a new thread as a daemon thread. This thread will be sleeping most of the time. |
| * It will run only only a few nanoseconds every time a new {@link DelayedRunnable} is taken. |
| * |
| * <div class="note"><b>Note:</b> |
| * We give to this thread a priority higher than the normal one since this thread shall |
| * execute only tasks to be completed very shortly. Quick execution of those tasks is at |
| * the benefit of the rest of the system, since they make more resources available sooner.</div> |
| */ |
| private DelayedExecutor(final DaemonThread lastCreatedDaemon) { |
| super("DelayedExecutor", lastCreatedDaemon); |
| setPriority(Thread.NORM_PRIORITY + 1); |
| } |
| |
| /** |
| * Loop to be run during the virtual machine lifetime. |
| * Public as an implementation side-effect; <strong>do not invoke explicitly!</strong> |
| */ |
| @Override |
| public final void run() { |
| /* |
| * The reference queue should never be null. However some strange cases have been |
| * observed at shutdown time. If the field become null, assume that a shutdown is |
| * under way and let the thread terminate. |
| */ |
| BlockingQueue<DelayedRunnable> queue; |
| while ((queue = QUEUE) != null) { |
| try { |
| final DelayedRunnable task = queue.take(); |
| if (task != null) { |
| task.run(); |
| continue; |
| } |
| } catch (InterruptedException exception) { |
| /* |
| * Probably the 'killAll' method has been invoked. |
| * We need to test 'isKillRequested()' below. |
| */ |
| } catch (Throwable exception) { |
| Logging.unexpectedException(Logging.getLogger(Loggers.SYSTEM), getClass(), "run", exception); |
| } |
| if (isKillRequested()) { |
| queue.clear(); |
| break; |
| } |
| } |
| // Do not log anything at this point, since the loggers may be shutdown now. |
| } |
| |
| /** |
| * Returns {@code true} if this thread seems to be stalled. This method checks the head |
| * of the queue. If the delay for that head has expired and the head is not removed in |
| * the next 5 seconds, then we will presume that the thread is stalled or dead. |
| * |
| * @return {@inheritDoc} |
| */ |
| @Override |
| protected boolean isStalled() { |
| final DelayedRunnable waiting = QUEUE.peek(); |
| if (waiting != null && waiting.getDelay(TimeUnit.NANOSECONDS) <= 0) try { |
| for (int i=0; i<50; i++) { |
| if (!isAlive()) break; |
| Thread.sleep(100); |
| if (QUEUE.peek() != waiting) { |
| return false; |
| } |
| } |
| return true; |
| } catch (InterruptedException e) { |
| /* |
| * Someone doesn't want to let us wait. Since we didn't had the time to |
| * determine if the thread is stalled, conservatively return 'false'. |
| */ |
| } |
| return false; |
| } |
| } |