ai.libs.jaicore.interrupt.Interrupter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaicore-basic Show documentation
Show all versions of jaicore-basic Show documentation
Fundamental utils required by many other starlibs projects.
package ai.libs.jaicore.interrupt;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used to conduct managed interrupts, which is essential for organized interrupts.
*
* In AILibs, it is generally forbidden to directly interrupt any thread.
* Instead, the Interrupter should be used, because this enables the interrupted thread to decide how to proceed.
*
* Using the Interrupter has several advantages in debugging:
*
* 1. the Interrupter tells which thread caused the interrupt
* 2. the Interrupter tells the time when the thread was interrupted
* 3. the Interrupter provides a reason for the interrupt
*
* @author fmohr
*
*/
public class Interrupter {
/**
* The interrupter is a singleton and must not be created manually.
*/
private Interrupter() {
super();
}
private static final Logger logger = LoggerFactory.getLogger(Interrupter.class);
private static final Interrupter instance = new Interrupter();
public static Interrupter get() {
return instance;
}
private final Map> blackListedInterruptReasons = new HashMap<>(); // interrupts to avoid when they arrive
private final List openInterrupts = new LinkedList<>();
public synchronized void interruptThread(final Thread t, final Object reason) {
if (this.blackListedInterruptReasons.containsKey(t) && this.blackListedInterruptReasons.get(t).contains(reason)) {
this.blackListedInterruptReasons.get(t).remove(reason);
logger.info("Thread {} is not interrupted, because it has been marked to be avoided for reason {}. Removing the entry from the black list.", t, reason);
return;
}
this.openInterrupts.add(new Interrupt(Thread.currentThread(), t, System.currentTimeMillis(), reason));
logger.info("Interrupting {} on behalf of {} with reason {}", t, Thread.currentThread(), reason);
t.interrupt();
logger.info("Interrupt accomplished. Interrupt flag of {}: {}", t, t.isInterrupted());
}
public boolean hasCurrentThreadBeenInterruptedWithReason(final Object reason) {
return this.hasThreadBeenInterruptedWithReason(Thread.currentThread(), reason);
}
public Optional getInterruptOfCurrentThreadWithReason(final Object reason) {
return this.getInterruptOfThreadWithReason(Thread.currentThread(), reason);
}
public Optional getInterruptOfThreadWithReason(final Thread thread, final Object reason) {
return this.openInterrupts.stream().filter(i -> i.getInterruptedThread() == thread && i.getReasonForInterruption().equals(reason)).findFirst();
}
public void avoidInterrupt(final Thread t, final Object reason) {
if (!this.blackListedInterruptReasons.containsKey(t)) {
this.blackListedInterruptReasons.put(t, new HashSet<>());
}
this.blackListedInterruptReasons.get(t).add(reason);
}
public boolean hasThreadBeenInterruptedWithReason(final Thread thread, final Object reason) {
boolean matches = this.openInterrupts.stream().anyMatch(i -> i.getInterruptedThread() == thread && i.getReasonForInterruption().equals(reason));
if (logger.isDebugEnabled()) {
if (matches) {
logger.debug("Reasons for why thread {} has currently been interrupted: {}. Checked reason {} matched? {}", thread,
this.openInterrupts.stream().filter(t -> t.getInterruptedThread() == thread).map(Interrupt::getReasonForInterruption).collect(Collectors.toList()), reason, matches);
} else {
logger.debug("Thread {} is currently not interrupted. In particular, it is not interrupted with reason {}", thread, reason);
}
}
return matches;
}
public Collection getAllUnresolvedInterrupts() {
return this.openInterrupts;
}
public Collection getAllUnresolvedInterruptsOfThread(final Thread thread) {
return this.openInterrupts.stream().filter(i -> i.getInterruptedThread() == thread).collect(Collectors.toList());
}
public Optional getLatestUnresolvedInterruptOfThread(final Thread thread) {
return this.getAllUnresolvedInterruptsOfThread(thread).stream().sorted((i1, i2) -> Long.compare(i1.getTimestampOfInterruption(), i2.getTimestampOfInterruption())).findFirst();
}
public Optional getLatestUnresolvedInterruptOfCurrentThread() {
return this.getLatestUnresolvedInterruptOfThread(Thread.currentThread());
}
public boolean hasCurrentThreadOpenInterrupts() {
Thread currentThread = Thread.currentThread();
return this.openInterrupts.stream().anyMatch(i -> i.getInterruptedThread() == currentThread);
}
public synchronized void markInterruptOnCurrentThreadAsResolved(final Object reason) throws InterruptedException {
Thread ct = Thread.currentThread();
this.markInterruptAsResolved(ct, reason);
if (this.hasCurrentThreadOpenInterrupts()) {
Thread.interrupted(); // clear flag prior to throwing the InterruptedException
logger.info("Throwing a new InterruptedException after having resolved the current interrupt, because the thread still has open interrupts! The reasons for these are: {}",
this.getAllUnresolvedInterruptsOfThread(ct).stream().map(Interrupt::getReasonForInterruption).collect(Collectors.toList()));
throw new InterruptedException();
}
}
public synchronized void markInterruptAsResolved(final Thread t, final Object reason) {
if (!this.hasThreadBeenInterruptedWithReason(t, reason)) {
throw new IllegalArgumentException("The thread " + t + " has not been interrupted with reason " + reason + ". Reasons for which it has been interrupted: "
+ Interrupter.get().getAllUnresolvedInterruptsOfThread(Thread.currentThread()).stream().map(Interrupt::getReasonForInterruption).collect(Collectors.toList()));
}
logger.debug("Removing interrupt with reason {} from list of open interrupts for thread {}", reason, t);
this.openInterrupts.removeIf(i -> i.getInterruptedThread() == t && i.getReasonForInterruption().equals(reason));
if (this.getAllUnresolvedInterruptsOfThread(t).contains(reason)) {
throw new IllegalStateException("The interrupt should have been resolved, but it has not!");
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy