
com.ea.orbit.concurrent.TaskContext Maven / Gradle / Ivy
The newest version!
package com.ea.orbit.concurrent;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class TaskContext
{
private final static ThreadLocal> contextStacks = new ThreadLocal<>();
private final static WeakHashMap> contextStacksMap = new WeakHashMap<>();
private final static AtomicLong nextId = new AtomicLong(1);
// human friendly id, for debugging
private long id = nextId.getAndIncrement();
private ConcurrentHashMap properties = new ConcurrentHashMap<>();
/**
* Adds this execution context to the top of the context stack for the current thread.
*/
public void push()
{
Deque stack = contextStacks.get();
if (stack == null)
{
// Attention! Do not use a concurrent collection for the stack
// it has been measured that concurrent collections decrease the TaskContext's performance.
stack = new LinkedList<>();
contextStacks.set(stack);
final Thread currentThread = Thread.currentThread();
synchronized (contextStacksMap)
{
// this happens only once per thread, no need to optimize it
contextStacksMap.put(currentThread, stack);
}
}
stack.addLast(this);
}
/**
* Removes the this execution context from the context stack for the current thread.
* This will fail with IllegalStateException if the current context is not at the top of the stack.
*/
public void pop()
{
Deque stack = contextStacks.get();
if (stack == null)
{
throw new IllegalStateException("Invalid execution context stack state: " + stack + " trying to remove: " + this);
}
final TaskContext last = stack.pollLast();
if (last != this)
{
if (last != null)
{
// returning it to the stack
stack.addLast(last);
}
throw new IllegalStateException("Invalid execution context stack state: " + stack + " trying to remove: " + this + " but got: " + last);
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + ":" + id;
}
/**
* Gets the current execution context for this thread from the stack.
*
* @return the current context or null if there is none.
*/
public static TaskContext current()
{
final Deque stack = contextStacks.get();
if (stack == null)
{
return null;
}
return stack.peekLast();
}
/**
* Enables the application to peek into what is being executed in another thread.
* This method is intended for debugging and profiling.
*/
public static TaskContext currentFor(Thread thread)
{
final Deque stack;
synchronized (contextStacksMap)
{
// this should not be called very often, it's for profiling
stack = contextStacksMap.get(thread);
}
// beware: this is peeking in a non synchronized LinkedList
// just peeking is safe enough for profiling.
return (stack != null) ? stack.peek() : null;
}
/**
* @return all threads that have active contexts
*/
public static Set activeThreads()
{
synchronized (contextStacksMap)
{
return new HashSet<>(contextStacksMap.keySet());
}
}
/**
* Wraps a Runnable in such a way the it will push the current execution context before any code gets executed and pop it afterwards
*
* @param w the functional interface to be wrapped
* @return wrapped object if there is a current execution context, or the same object if not.
*/
public static Runnable wrap(Runnable w)
{
TaskContext c = current();
if (c != null)
{
return () -> {
c.push();
try
{
w.run();
}
finally
{
c.pop();
}
};
}
return w;
}
/**
* Wraps a BiConsumer in such a way the it will push the current execution context before any code gets executed and pop it afterwards
*
* @param w the functional interface to be wrapped
* @return wrapped object if there is a current execution context, or the same object if not.
*/
public static BiConsumer wrap(BiConsumer w)
{
TaskContext c = current();
if (c != null)
{
return (t, u) -> {
c.push();
try
{
w.accept(t, u);
}
finally
{
c.pop();
}
};
}
return w;
}
/**
* Wraps a Consumer in such a way the it will push the current execution context before any code gets executed and pop it afterwards
*
* @param w the functional interface to be wrapped
* @return wrapped object if there is a current execution context, or the same object if not.
*/
public static Consumer wrap(Consumer w)
{
TaskContext c = current();
if (c != null)
{
return (t) -> {
c.push();
try
{
w.accept(t);
}
finally
{
c.pop();
}
};
}
return w;
}
/**
* Wraps a Function in such a way the it will push the current execution context before any code gets executed and pop it afterwards
*
* @param w the functional interface to be wrapped
* @return wrapped object if there is a current execution context, or the same object if not.
*/
public static Function wrap(Function w)
{
TaskContext c = current();
if (c != null)
{
return (t) -> {
c.push();
try
{
return w.apply(t);
}
finally
{
c.pop();
}
};
}
return w;
}
/**
* Wraps a Function in such a way the it will push the current execution context before any code gets executed and pop it afterwards
*
* @param w the functional interface to be wrapped
* @return wrapped object if there is a current execution context, or the same object if not.
*/
public static BiFunction wrap(BiFunction w)
{
TaskContext c = current();
if (c != null)
{
return (t, u) -> {
c.push();
try
{
return w.apply(t, u);
}
finally
{
c.pop();
}
};
}
return w;
}
/**
* Wraps a Supplier in such a way the it will push the current execution context before any code gets executed and pop it afterwards
*
* @param w the functional interface to be wrapped
* @return wrapped object if there is a current execution context, or the same object if not.
*/
public static Supplier wrap(Supplier w)
{
TaskContext c = current();
if (c != null)
{
return () -> {
c.push();
try
{
return w.get();
}
finally
{
c.pop();
}
};
}
return w;
}
/**
* Returns the property with the given name registered in the current execution context,
* {@code null} if there is no property by that name.
*
* A property allows orbit extensions to exchange custom information.
*
*
* @param name the name of the property
* @return an {@code Object} or
* {@code null} if no property exists matching the given name.
*/
public Object getProperty(String name)
{
if (properties == null)
{
return null;
}
return properties.get(name);
}
/**
* Binds an object to a given property name in the current execution context.
* If the name specified is already used for a property,
* this method will replace the value of the property with the new value.
*
* A property allows orbit extensions to exchange custom information.
*
*
* A null value will work to remove the property.
*
*
* @param name a {@code String} the name of the property.
* @param value an {@code Object} may be null
*/
public void setProperty(String name, Object value)
{
if (value != null)
{
properties.put(name, value);
}
else
{
properties.remove(name);
}
}
protected Map properties()
{
return properties;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy