arez.Task Maven / Gradle / Ivy
package arez;
import arez.spy.Priority;
import arez.spy.TaskCompleteEvent;
import arez.spy.TaskInfo;
import arez.spy.TaskStartEvent;
import grim.annotations.OmitSymbol;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static org.realityforge.braincheck.Guards.*;
/**
* A task represents an executable element that can be run by the task executor.
*/
public final class Task
extends Node
{
/**
* The code to invoke when task is executed.
*/
@Nonnull
private final SafeProcedure _work;
/**
* State of the task.
*/
private int _flags;
/**
* Cached info object associated with element.
* This should be null if {@link Arez#areSpiesEnabled()} is false;
*/
@OmitSymbol( unless = "arez.enable_spies" )
@Nullable
private TaskInfo _info;
Task( @Nullable final ArezContext context,
@Nullable final String name,
@Nonnull final SafeProcedure work,
final int flags )
{
super( context, name );
if ( Arez.shouldCheckApiInvariants() )
{
apiInvariant( () -> ( ~Flags.CONFIG_FLAGS_MASK & flags ) == 0,
() -> "Arez-0224: Task named '" + name + "' passed invalid flags: " +
( ~Flags.CONFIG_FLAGS_MASK & flags ) );
}
_work = Objects.requireNonNull( work );
_flags = flags | Flags.STATE_IDLE | Flags.runType( flags ) | Flags.priority( flags );
if ( Arez.areRegistriesEnabled() && 0 == ( _flags & Flags.NO_REGISTER_TASK ) )
{
getContext().registerTask( this );
}
}
/**
* Re-schedule this task if it is idle and trigger the scheduler if it is not active.
*/
public void schedule()
{
if ( isIdle() )
{
queueTask();
}
getContext().triggerScheduler();
}
int getFlags()
{
return _flags;
}
void queueTask()
{
getContext().getTaskQueue().queueTask( this );
}
void initialSchedule()
{
queueTask();
triggerSchedulerInitiallyUnlessRunLater();
}
void triggerSchedulerInitiallyUnlessRunLater()
{
// If we have not explicitly supplied the RUN_LATER flag then assume it is a run now and
// trigger the scheduler
if ( 0 == ( _flags & Flags.RUN_LATER ) )
{
getContext().triggerScheduler();
}
}
/**
* Return the priority of the task.
* This is only meaningful when TaskQueue observes priority.
*
* @return the priority of the task.
*/
int getPriorityIndex()
{
return Flags.getPriorityIndex( _flags );
}
/**
* Return the priority enum for task.
*
* @return the priority.
*/
@Nonnull
Priority getPriority()
{
return Priority.values()[ getPriorityIndex() ];
}
/**
* Return the task.
*
* @return the task.
*/
@Nonnull
SafeProcedure getWork()
{
return _work;
}
/**
* Execute the work associated with the task.
*/
void executeTask()
{
// It is possible that the task was executed outside the executor and
// may no longer need to be executed. This particularly true when executing tasks
// using the "idle until urgent" strategy.
if ( isQueued() )
{
markAsIdle();
if ( 0 == ( _flags & Flags.NO_WRAP_TASK ) )
{
runTask();
}
else
{
// It is expected that the task/observers currently catch error
// and handle internally. Thus no need to catch errors here.
_work.call();
}
// If this task has been marked as a task to dispose on completion then do so
if ( 0 != ( _flags & Flags.DISPOSE_ON_COMPLETE ) )
{
dispose();
}
}
}
/**
* Actually execute the task, capture errors and send spy events.
*/
private void runTask()
{
long startedAt = 0L;
if ( willPropagateSpyEvents() )
{
startedAt = System.currentTimeMillis();
getSpy().reportSpyEvent( new TaskStartEvent( asInfo() ) );
}
Throwable error = null;
try
{
getWork().call();
}
catch ( final Throwable t )
{
// Should we handle it with a per-task handler or a global error handler?
error = t;
}
if ( willPropagateSpyEvents() )
{
final long duration = System.currentTimeMillis() - startedAt;
getSpy().reportSpyEvent( new TaskCompleteEvent( asInfo(), error, (int) duration ) );
}
}
@Override
public void dispose()
{
if ( isNotDisposed() )
{
_flags = Flags.setState( _flags, Flags.STATE_DISPOSED );
if ( Arez.areRegistriesEnabled() && 0 == ( _flags & Flags.NO_REGISTER_TASK ) )
{
getContext().deregisterTask( this );
}
}
}
@Override
public boolean isDisposed()
{
return Flags.STATE_DISPOSED == Flags.getState( _flags );
}
/**
* Return the info associated with this class.
*
* @return the info associated with this class.
*/
@SuppressWarnings( "ConstantConditions" )
@OmitSymbol( unless = "arez.enable_spies" )
@Nonnull
TaskInfo asInfo()
{
if ( Arez.shouldCheckInvariants() )
{
invariant( Arez::areSpiesEnabled,
() -> "Arez-0130: Task.asInfo() invoked but Arez.areSpiesEnabled() returned false." );
}
if ( Arez.areSpiesEnabled() && null == _info )
{
_info = new TaskInfoImpl( this );
}
return Arez.areSpiesEnabled() ? _info : null;
}
/**
* Mark task as being queued, first verifying that it is not already queued.
* This is used so that task will not be able to be queued again until it has run.
*/
void markAsQueued()
{
if ( Arez.shouldCheckInvariants() )
{
invariant( this::isIdle,
() -> "Arez-0128: Attempting to queue task named '" + getName() + "' when task is not idle." );
}
_flags = Flags.setState( _flags, Flags.STATE_QUEUED );
}
/**
* Clear the queued flag, first verifying that the task is queued.
*/
void markAsIdle()
{
if ( Arez.shouldCheckInvariants() )
{
invariant( this::isQueued,
() -> "Arez-0129: Attempting to clear queued flag on task named '" + getName() +
"' but task is not queued." );
}
_flags = Flags.setState( _flags, Flags.STATE_IDLE );
}
/**
* Return true if task is idle or not disposed and not scheduled.
*
* @return true if task is idle.
*/
boolean isIdle()
{
return Flags.STATE_IDLE == Flags.getState( _flags );
}
/**
* Return true if task is already scheduled.
*
* @return true if task is already scheduled.
*/
boolean isQueued()
{
return Flags.STATE_QUEUED == Flags.getState( _flags );
}
public static final class Flags
{
/**
* Highest priority.
* This priority should be used when the task will dispose or release other reactive elements
* (and thus remove elements from being scheduled).
*
* Only one of the PRIORITY_* flags should be applied to a task.
*
* @see arez.annotations.Priority#HIGHEST
* @see Priority#HIGHEST
*/
public static final int PRIORITY_HIGHEST = 0b001 << 15;
/**
* High priority.
* To reduce the chance that downstream elements will react multiple times within a single
* reaction round, this priority should be used when the task may trigger many downstream tasks.
* Only one of the PRIORITY_* flags should be applied to a task.
*
* @see arez.annotations.Priority#HIGH
* @see Priority#HIGH
*/
public static final int PRIORITY_HIGH = 0b010 << 15;
/**
* Normal priority if no other priority otherwise specified.
*
* Only one of the PRIORITY_* flags should be applied to a task.
*
* @see arez.annotations.Priority#NORMAL
* @see Priority#NORMAL
*/
public static final int PRIORITY_NORMAL = 0b011 << 15;
/**
* Low priority.
* Usually used to schedule tasks that reflect state onto non-reactive
* application components. i.e. Tasks that are used to build html views,
* perform network operations etc. These reactions are often at low priority
* to avoid recalculation of dependencies (i.e. {@link ComputableValue}s) triggering
* this reaction multiple times within a single reaction round.
*
* Only one of the PRIORITY_* flags should be applied to a task.
*
* @see arez.annotations.Priority#LOW
* @see Priority#LOW
*/
public static final int PRIORITY_LOW = 0b100 << 15;
/**
* Lowest priority. Use this priority if the task is a {@link ComputableValue} that
* may be unobserved when a {@link #PRIORITY_LOW} observer reacts. This is used to avoid
* recomputing state that is likely to either be unobserved or recomputed as part of
* another observers reaction.
* Only one of the PRIORITY_* flags should be applied to a task.
*
* @see arez.annotations.Priority#LOWEST
* @see Priority#LOWEST
*/
public static final int PRIORITY_LOWEST = 0b101 << 15;
/**
* Mask used to extract priority bits.
*/
static final int PRIORITY_MASK = 0b111 << 15;
/**
* Shift used to extract priority after applying mask.
*/
private static final int PRIORITY_SHIFT = 15;
/**
* The number of priority levels.
*/
static final int PRIORITY_COUNT = 5;
/**
* The scheduler will be triggered when the task is created to immediately invoke the task.
* This should not be specified if {@link #RUN_LATER} is specified.
*/
public static final int RUN_NOW = 1 << 22;
/**
* The scheduler will not be triggered when the task is created. The application is responsible
* for ensuring thatthe {@link ArezContext#triggerScheduler()} method is invoked at a later time.
* This should not be specified if {@link #RUN_NOW} is specified.
*/
public static final int RUN_LATER = 1 << 21;
/**
* Mask used to extract run type bits.
*/
static final int RUN_TYPE_MASK = RUN_NOW | RUN_LATER;
/**
* The flag that indicates that task should not be wrapped.
* The wrapping is responsible for ensuring the task never generates an exception and for generating
* the spy events. If wrapping is disabled it is expected that the caller is responsible for integrating
* with the spy subsystem and catching exceptions if any.
*/
public static final int NO_WRAP_TASK = 1 << 20;
/**
* The flag that specifies that the task should be disposed after it has completed execution.
*/
public static final int DISPOSE_ON_COMPLETE = 1 << 19;
/**
* The flag that indicates that task should not be registered in top level registry.
* This is used when Observers etc create tasks and do not need them exposed to the spy framework.
*/
static final int NO_REGISTER_TASK = 1 << 18;
/**
* Mask containing flags that can be applied to a task.
*/
static final int CONFIG_FLAGS_MASK =
PRIORITY_MASK | RUN_TYPE_MASK | DISPOSE_ON_COMPLETE | NO_REGISTER_TASK | NO_WRAP_TASK;
/**
* Mask containing flags that can be applied to a task representing an observer.
* This omits DISPOSE_ON_COMPLETE as observer is responsible for disposing task
*/
static final int OBSERVER_TASK_FLAGS_MASK = PRIORITY_MASK | RUN_TYPE_MASK;
/**
* State when the task has not been scheduled.
*/
static final int STATE_IDLE = 0;
/**
* State when the task has been scheduled and should not be re-scheduled until next executed.
*/
static final int STATE_QUEUED = 1;
/**
* State when the task has been disposed and should no longer be scheduled.
*/
static final int STATE_DISPOSED = 2;
/**
* Invalid state that should never be set.
*/
static final int STATE_INVALID = 3;
/**
* Mask used to extract state bits.
*/
private static final int STATE_MASK = STATE_IDLE | STATE_QUEUED | STATE_DISPOSED;
/**
* Mask containing flags that are used to track runtime state.
*/
static final int RUNTIME_FLAGS_MASK = STATE_MASK;
/**
* Return true if flags contains valid priority.
*
* @param flags the flags.
* @return true if flags contains priority.
*/
static boolean isStateValid( final int flags )
{
assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants();
return STATE_INVALID != ( STATE_MASK & flags );
}
static int setState( final int flags, final int state )
{
return ( ~STATE_MASK & flags ) | state;
}
static int getState( final int flags )
{
return STATE_MASK & flags;
}
/**
* Return true if flags contains a valid react type.
*
* @param flags the flags.
* @return true if flags contains react type.
*/
static boolean isRunTypeValid( final int flags )
{
assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants();
return RUN_NOW == ( flags & RUN_NOW ) ^ RUN_LATER == ( flags & RUN_LATER );
}
/**
* Return the RUN_NOW flag if run type not specified.
*
* @param flags the flags.
* @return the default run type if run type unspecified else 0.
*/
static int runType( final int flags )
{
return runType( flags, RUN_NOW );
}
/**
* Return the default run type flag if run type not specified.
*
* @param flags the flags.
* @param defaultFlag the default flag.
* @return the default run type if run type unspecified else 0.
*/
static int runType( final int flags, final int defaultFlag )
{
return 0 == ( flags & RUN_TYPE_MASK ) ? defaultFlag : 0;
}
/**
* Return true if flags contains valid priority.
*
* @param flags the flags.
* @return true if flags contains priority.
*/
static boolean isPriorityValid( final int flags )
{
assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants();
final int priorityIndex = getPriorityIndex( flags );
return priorityIndex <= 4 && priorityIndex >= 0;
}
/**
* Extract and return the priority flag.
* This method will not attempt to check priority value is valid.
*
* @param flags the flags.
* @return the priority.
*/
static int getPriority( final int flags )
{
return flags & PRIORITY_MASK;
}
/**
* Extract and return the priority value ranging from the highest priority 0 and lowest priority 4.
* This method assumes that flags has valid priority and will not attempt to re-check.
*
* @param flags the flags.
* @return the priority.
*/
static int getPriorityIndex( final int flags )
{
return ( getPriority( flags ) >> PRIORITY_SHIFT ) - 1;
}
static int priority( final int flags )
{
return 0 != getPriority( flags ) ? 0 : PRIORITY_NORMAL;
}
private Flags()
{
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy