blob: f5e291167e47e8ddea53b45d368427ed734a0aa6 [file] [log] [blame]
/*
* 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;
}
}