/*
* Strongback
* Copyright 2015, Strongback and individual contributors by the @authors tag.
* See the COPYRIGHT.txt in the distribution for a full listing of individual
* contributors.
*
* Licensed under the MIT License; you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
* 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.strongback;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.LongConsumer;
import org.strongback.annotation.ThreadSafe;
import org.strongback.components.Clock;
import org.strongback.components.Stoppable;
import org.strongback.util.Metronome;
/**
* An executor that invokes registered {@link Executable}s on a fixed period.
*/
@ThreadSafe
final class ExecutorDriver implements Stoppable {
private final String name;
private final Clock timeSystem;
private final Metronome met;
private final Logger logger;
private final Iterable<Executable> executables;
private final AtomicReference<Thread> thread = new AtomicReference<>();
private final LongConsumer delayInformer;
private volatile boolean running = false;
private volatile CountDownLatch stopped = null;
ExecutorDriver(String name, Iterable<Executable> executables, Clock timeSystem, Metronome metronome, Logger logger, LongConsumer delayInformer ) {
this.name = name;
this.timeSystem = timeSystem;
this.met = metronome;
this.logger = logger;
this.executables = executables;
this.delayInformer = delayInformer != null ? delayInformer : ExecutorDriver::noDelay;
}
/**
* Start the execution of this {@link ExecutorDriver} in a separate thread. During each execution, all registered
* {@link Executable}s will be called in the order they were registered.
* <p>
* Calling this method when already started has no effect.
*
* @see #stop()
*/
public void start() {
thread.getAndUpdate(thread -> {
if (thread == null) {
thread = new Thread(this::run);
thread.setName(name);
// run with a bit higher priority to reduce thread context switches
thread.setPriority(8);
stopped = new CountDownLatch(1);
running = true;
thread.start();
}
return thread;
});
}
/**
* Stop this executor from executing, and block until the thread has completed all work (or until the timeout has occurred).
* <p>
* Calling this method when already stopped has no effect.
*
* @see #start()
*/
@Override
public void stop() {
// Get the latch we'll use to wait for the thread to complete
CountDownLatch latch = stopped;
// Atomically mark the thread as completed and change our reference to it ...
Thread oldThread = thread.getAndUpdate(thread -> {
if (thread != null) running = false;
return null;
});
if (oldThread != null && latch != null) {
// Wait (at most 10 seconds) for the thread to complete ...
try {
latch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.interrupted();
}
}
}
private void run() {
try {
long timeInMillis = 0L;
long lastTimeInMillis = 0L;
while (true) {
timeInMillis = timeSystem.currentTimeInMillis();
delayInformer.accept( timeInMillis - lastTimeInMillis);
for (Executable executable : executables) {
if (!running) return;
try {
executable.execute(timeInMillis);
} catch (Throwable e) {
logger.error(e);
}
}
lastTimeInMillis = timeInMillis;
met.pause();
}
} finally {
CountDownLatch latch = stopped;
if (latch != null) latch.countDown();
}
}
private static void noDelay( long delay ) {
// do nothing
}
}