001package arez; 002 003import arez.spy.Priority; 004import arez.spy.TaskCompleteEvent; 005import arez.spy.TaskInfo; 006import arez.spy.TaskStartEvent; 007import grim.annotations.OmitSymbol; 008import java.util.Objects; 009import javax.annotation.Nonnull; 010import javax.annotation.Nullable; 011import static org.realityforge.braincheck.Guards.*; 012 013/** 014 * A task represents an executable element that can be run by the task executor. 015 */ 016public final class Task 017 extends Node 018{ 019 /** 020 * The code to invoke when task is executed. 021 */ 022 @Nonnull 023 private final SafeProcedure _work; 024 /** 025 * State of the task. 026 */ 027 private int _flags; 028 /** 029 * Cached info object associated with element. 030 * This should be null if {@link Arez#areSpiesEnabled()} is false; 031 */ 032 @OmitSymbol( unless = "arez.enable_spies" ) 033 @Nullable 034 private TaskInfo _info; 035 036 Task( @Nullable final ArezContext context, 037 @Nullable final String name, 038 @Nonnull final SafeProcedure work, 039 final int flags ) 040 { 041 super( context, name ); 042 if ( Arez.shouldCheckApiInvariants() ) 043 { 044 apiInvariant( () -> ( ~Flags.CONFIG_FLAGS_MASK & flags ) == 0, 045 () -> "Arez-0224: Task named '" + name + "' passed invalid flags: " + 046 ( ~Flags.CONFIG_FLAGS_MASK & flags ) ); 047 } 048 049 _work = Objects.requireNonNull( work ); 050 _flags = flags | Flags.STATE_IDLE | Flags.runType( flags ) | Flags.priority( flags ); 051 if ( Arez.areRegistriesEnabled() && 0 == ( _flags & Flags.NO_REGISTER_TASK ) ) 052 { 053 getContext().registerTask( this ); 054 } 055 } 056 057 /** 058 * Re-schedule this task if it is idle and trigger the scheduler if it is not active. 059 */ 060 public void schedule() 061 { 062 if ( isIdle() ) 063 { 064 queueTask(); 065 } 066 getContext().triggerScheduler(); 067 } 068 069 int getFlags() 070 { 071 return _flags; 072 } 073 074 void queueTask() 075 { 076 getContext().getTaskQueue().queueTask( this ); 077 } 078 079 void initialSchedule() 080 { 081 queueTask(); 082 triggerSchedulerInitiallyUnlessRunLater(); 083 } 084 085 void triggerSchedulerInitiallyUnlessRunLater() 086 { 087 // If we have not explicitly supplied the RUN_LATER flag then assume it is a run now and 088 // trigger the scheduler 089 if ( 0 == ( _flags & Flags.RUN_LATER ) ) 090 { 091 getContext().triggerScheduler(); 092 } 093 } 094 095 /** 096 * Return the priority of the task. 097 * This is only meaningful when TaskQueue observes priority. 098 * 099 * @return the priority of the task. 100 */ 101 int getPriorityIndex() 102 { 103 return Flags.getPriorityIndex( _flags ); 104 } 105 106 /** 107 * Return the priority enum for task. 108 * 109 * @return the priority. 110 */ 111 @Nonnull 112 Priority getPriority() 113 { 114 return Priority.values()[ getPriorityIndex() ]; 115 } 116 117 /** 118 * Return the task. 119 * 120 * @return the task. 121 */ 122 @Nonnull 123 SafeProcedure getWork() 124 { 125 return _work; 126 } 127 128 /** 129 * Execute the work associated with the task. 130 */ 131 void executeTask() 132 { 133 // It is possible that the task was executed outside the executor and 134 // may no longer need to be executed. This particularly true when executing tasks 135 // using the "idle until urgent" strategy. 136 if ( isQueued() ) 137 { 138 markAsIdle(); 139 140 if ( 0 == ( _flags & Flags.NO_WRAP_TASK ) ) 141 { 142 runTask(); 143 } 144 else 145 { 146 // It is expected that the task/observers currently catch error 147 // and handle internally. Thus no need to catch errors here. 148 _work.call(); 149 } 150 151 // If this task has been marked as a task to dispose on completion then do so 152 if ( 0 != ( _flags & Flags.DISPOSE_ON_COMPLETE ) ) 153 { 154 dispose(); 155 } 156 } 157 } 158 159 /** 160 * Actually execute the task, capture errors and send spy events. 161 */ 162 private void runTask() 163 { 164 long startedAt = 0L; 165 if ( willPropagateSpyEvents() ) 166 { 167 startedAt = System.currentTimeMillis(); 168 getSpy().reportSpyEvent( new TaskStartEvent( asInfo() ) ); 169 } 170 Throwable error = null; 171 try 172 { 173 getWork().call(); 174 } 175 catch ( final Throwable t ) 176 { 177 // Should we handle it with a per-task handler or a global error handler? 178 error = t; 179 } 180 if ( willPropagateSpyEvents() ) 181 { 182 final long duration = System.currentTimeMillis() - startedAt; 183 getSpy().reportSpyEvent( new TaskCompleteEvent( asInfo(), error, (int) duration ) ); 184 } 185 } 186 187 @Override 188 public void dispose() 189 { 190 if ( isNotDisposed() ) 191 { 192 _flags = Flags.setState( _flags, Flags.STATE_DISPOSED ); 193 if ( Arez.areRegistriesEnabled() && 0 == ( _flags & Flags.NO_REGISTER_TASK ) ) 194 { 195 getContext().deregisterTask( this ); 196 } 197 } 198 } 199 200 @Override 201 public boolean isDisposed() 202 { 203 return Flags.STATE_DISPOSED == Flags.getState( _flags ); 204 } 205 206 /** 207 * Return the info associated with this class. 208 * 209 * @return the info associated with this class. 210 */ 211 @SuppressWarnings( "ConstantConditions" ) 212 @OmitSymbol( unless = "arez.enable_spies" ) 213 @Nonnull 214 TaskInfo asInfo() 215 { 216 if ( Arez.shouldCheckInvariants() ) 217 { 218 invariant( Arez::areSpiesEnabled, 219 () -> "Arez-0130: Task.asInfo() invoked but Arez.areSpiesEnabled() returned false." ); 220 } 221 if ( Arez.areSpiesEnabled() && null == _info ) 222 { 223 _info = new TaskInfoImpl( this ); 224 } 225 return Arez.areSpiesEnabled() ? _info : null; 226 } 227 228 /** 229 * Mark task as being queued, first verifying that it is not already queued. 230 * This is used so that task will not be able to be queued again until it has run. 231 */ 232 void markAsQueued() 233 { 234 if ( Arez.shouldCheckInvariants() ) 235 { 236 invariant( this::isIdle, 237 () -> "Arez-0128: Attempting to queue task named '" + getName() + "' when task is not idle." ); 238 } 239 _flags = Flags.setState( _flags, Flags.STATE_QUEUED ); 240 } 241 242 /** 243 * Clear the queued flag, first verifying that the task is queued. 244 */ 245 void markAsIdle() 246 { 247 if ( Arez.shouldCheckInvariants() ) 248 { 249 invariant( this::isQueued, 250 () -> "Arez-0129: Attempting to clear queued flag on task named '" + getName() + 251 "' but task is not queued." ); 252 } 253 _flags = Flags.setState( _flags, Flags.STATE_IDLE ); 254 } 255 256 /** 257 * Return true if task is idle or not disposed and not scheduled. 258 * 259 * @return true if task is idle. 260 */ 261 boolean isIdle() 262 { 263 return Flags.STATE_IDLE == Flags.getState( _flags ); 264 } 265 266 /** 267 * Return true if task is already scheduled. 268 * 269 * @return true if task is already scheduled. 270 */ 271 boolean isQueued() 272 { 273 return Flags.STATE_QUEUED == Flags.getState( _flags ); 274 } 275 276 public static final class Flags 277 { 278 /** 279 * Highest priority. 280 * This priority should be used when the task will dispose or release other reactive elements 281 * (and thus remove elements from being scheduled). 282 * 283 * <p>Only one of the PRIORITY_* flags should be applied to a task.</p> 284 * 285 * @see arez.annotations.Priority#HIGHEST 286 * @see Priority#HIGHEST 287 */ 288 public static final int PRIORITY_HIGHEST = 0b001 << 15; 289 /** 290 * High priority. 291 * To reduce the chance that downstream elements will react multiple times within a single 292 * reaction round, this priority should be used when the task may trigger many downstream tasks. 293 * <p>Only one of the PRIORITY_* flags should be applied to a task.</p> 294 * 295 * @see arez.annotations.Priority#HIGH 296 * @see Priority#HIGH 297 */ 298 public static final int PRIORITY_HIGH = 0b010 << 15; 299 /** 300 * Normal priority if no other priority otherwise specified. 301 * 302 * <p>Only one of the PRIORITY_* flags should be applied to a task.</p> 303 * 304 * @see arez.annotations.Priority#NORMAL 305 * @see Priority#NORMAL 306 */ 307 public static final int PRIORITY_NORMAL = 0b011 << 15; 308 /** 309 * Low priority. 310 * Usually used to schedule tasks that reflect state onto non-reactive 311 * application components. i.e. Tasks that are used to build html views, 312 * perform network operations etc. These reactions are often at low priority 313 * to avoid recalculation of dependencies (i.e. {@link ComputableValue}s) triggering 314 * this reaction multiple times within a single reaction round. 315 * 316 * <p>Only one of the PRIORITY_* flags should be applied to a task.</p> 317 * 318 * @see arez.annotations.Priority#LOW 319 * @see Priority#LOW 320 */ 321 public static final int PRIORITY_LOW = 0b100 << 15; 322 /** 323 * Lowest priority. Use this priority if the task is a {@link ComputableValue} that 324 * may be unobserved when a {@link #PRIORITY_LOW} observer reacts. This is used to avoid 325 * recomputing state that is likely to either be unobserved or recomputed as part of 326 * another observers reaction. 327 * <p>Only one of the PRIORITY_* flags should be applied to a task.</p> 328 * 329 * @see arez.annotations.Priority#LOWEST 330 * @see Priority#LOWEST 331 */ 332 public static final int PRIORITY_LOWEST = 0b101 << 15; 333 /** 334 * Mask used to extract priority bits. 335 */ 336 static final int PRIORITY_MASK = 0b111 << 15; 337 /** 338 * Shift used to extract priority after applying mask. 339 */ 340 private static final int PRIORITY_SHIFT = 15; 341 /** 342 * The number of priority levels. 343 */ 344 static final int PRIORITY_COUNT = 5; 345 /** 346 * The scheduler will be triggered when the task is created to immediately invoke the task. 347 * This should not be specified if {@link #RUN_LATER} is specified. 348 */ 349 public static final int RUN_NOW = 1 << 22; 350 /** 351 * The scheduler will not be triggered when the task is created. The application is responsible 352 * for ensuring thatthe {@link ArezContext#triggerScheduler()} method is invoked at a later time. 353 * This should not be specified if {@link #RUN_NOW} is specified. 354 */ 355 public static final int RUN_LATER = 1 << 21; 356 /** 357 * Mask used to extract run type bits. 358 */ 359 static final int RUN_TYPE_MASK = RUN_NOW | RUN_LATER; 360 /** 361 * The flag that indicates that task should not be wrapped. 362 * The wrapping is responsible for ensuring the task never generates an exception and for generating 363 * the spy events. If wrapping is disabled it is expected that the caller is responsible for integrating 364 * with the spy subsystem and catching exceptions if any. 365 */ 366 public static final int NO_WRAP_TASK = 1 << 20; 367 /** 368 * The flag that specifies that the task should be disposed after it has completed execution. 369 */ 370 public static final int DISPOSE_ON_COMPLETE = 1 << 19; 371 /** 372 * The flag that indicates that task should not be registered in top level registry. 373 * This is used when Observers etc create tasks and do not need them exposed to the spy framework. 374 */ 375 static final int NO_REGISTER_TASK = 1 << 18; 376 /** 377 * Mask containing flags that can be applied to a task. 378 */ 379 static final int CONFIG_FLAGS_MASK = 380 PRIORITY_MASK | RUN_TYPE_MASK | DISPOSE_ON_COMPLETE | NO_REGISTER_TASK | NO_WRAP_TASK; 381 /** 382 * Mask containing flags that can be applied to a task representing an observer. 383 * This omits the flag DISPOSE_ON_COMPLETE as the observer is responsible for disposing the task. 384 */ 385 static final int OBSERVER_TASK_FLAGS_MASK = PRIORITY_MASK | RUN_TYPE_MASK; 386 /** 387 * State when the task has not been scheduled. 388 */ 389 static final int STATE_IDLE = 0; 390 /** 391 * State when the task has been scheduled and should not be re-scheduled until next executed. 392 */ 393 static final int STATE_QUEUED = 1; 394 /** 395 * State when the task has been disposed and should no longer be scheduled. 396 */ 397 static final int STATE_DISPOSED = 2; 398 /** 399 * Invalid state that should never be set. 400 */ 401 static final int STATE_INVALID = 3; 402 /** 403 * Mask used to extract state bits. 404 */ 405 private static final int STATE_MASK = STATE_IDLE | STATE_QUEUED | STATE_DISPOSED; 406 /** 407 * Mask containing flags that are used to track runtime state. 408 */ 409 static final int RUNTIME_FLAGS_MASK = STATE_MASK; 410 411 /** 412 * Return true if flags contains valid priority. 413 * 414 * @param flags the flags. 415 * @return true if flags contains priority. 416 */ 417 static boolean isStateValid( final int flags ) 418 { 419 assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants(); 420 return STATE_INVALID != ( STATE_MASK & flags ); 421 } 422 423 static int setState( final int flags, final int state ) 424 { 425 return ( ~STATE_MASK & flags ) | state; 426 } 427 428 static int getState( final int flags ) 429 { 430 return STATE_MASK & flags; 431 } 432 433 /** 434 * Return true if flags contains a valid react type. 435 * 436 * @param flags the flags. 437 * @return true if flags contains react type. 438 */ 439 static boolean isRunTypeValid( final int flags ) 440 { 441 assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants(); 442 return RUN_NOW == ( flags & RUN_NOW ) ^ RUN_LATER == ( flags & RUN_LATER ); 443 } 444 445 /** 446 * Return the RUN_NOW flag if run type not specified. 447 * 448 * @param flags the flags. 449 * @return the default run type if run type unspecified else 0. 450 */ 451 static int runType( final int flags ) 452 { 453 return runType( flags, RUN_NOW ); 454 } 455 456 /** 457 * Return the default run type flag if run type not specified. 458 * 459 * @param flags the flags. 460 * @param defaultFlag the default flag. 461 * @return the default run type if run type unspecified else 0. 462 */ 463 static int runType( final int flags, final int defaultFlag ) 464 { 465 return 0 == ( flags & RUN_TYPE_MASK ) ? defaultFlag : 0; 466 } 467 468 /** 469 * Return true if flags contains valid priority. 470 * 471 * @param flags the flags. 472 * @return true if flags contains priority. 473 */ 474 static boolean isPriorityValid( final int flags ) 475 { 476 assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants(); 477 final int priorityIndex = getPriorityIndex( flags ); 478 return priorityIndex <= 4 && priorityIndex >= 0; 479 } 480 481 /** 482 * Extract and return the priority flag. 483 * This method will not attempt to check priority value is valid. 484 * 485 * @param flags the flags. 486 * @return the priority. 487 */ 488 static int getPriority( final int flags ) 489 { 490 return flags & PRIORITY_MASK; 491 } 492 493 /** 494 * Extract and return the priority value ranging from the highest priority 0 and lowest priority 4. 495 * This method assumes that flags has valid priority and will not attempt to re-check. 496 * 497 * @param flags the flags. 498 * @return the priority. 499 */ 500 static int getPriorityIndex( final int flags ) 501 { 502 return ( getPriority( flags ) >> PRIORITY_SHIFT ) - 1; 503 } 504 505 static int priority( final int flags ) 506 { 507 return 0 != getPriority( flags ) ? 0 : PRIORITY_NORMAL; 508 } 509 510 private Flags() 511 { 512 } 513 } 514}