Interface EvaluationContext
- All Known Subinterfaces:
ActionContext
Implemented by the engine, which passes it to a language. It's sealed, so a language can't implement it; a
language's unit tests create one with io.github.brantunger.unruly.test.LanguageTestContexts, from the
unruly-engine-test artifact. Because only the engine implements it, a later release can add methods to it
without breaking languages.
Identity: a context equals only itself, however its facts change and whatever another run's facts are, and its
hashCode() reads neither the facts nor, for an ActionContext, the output object. A run passes one
evaluation context to every condition, and a new action context to each action, so the two are different objects. A
Session serves one run at a time and later runs reuse it, and no call to it marks where a run starts or
ends, so state a run leaves in a session is still there for the next run; state for one run belongs in
runScoped(java.lang.Object, java.util.function.Supplier<? extends T>), or, if it holds a resource that must be released, such as a runtime's context or interpreter
opened for the run, in runScopedClosing(java.lang.Object, java.util.function.Supplier<? extends T>), which closes it when the run ends. A map keyed on contexts must not
keep them alive, as a WeakHashMap doesn't.
-
Method Summary
Modifier and TypeMethodDescription@Nullable Instantdeadline()Returns when the run must stop, from the timeout its engine or itsruncall was given: when the run started, on the system clock, plus its timeout.facts()Returns the values of the run's facts by name.booleanReturns whether the run must stop, because its thread has been interrupted or it has passed its deadline.default CancelRegistrationHasactionrun once when the run passes its deadline, unless the registration returned is closed first.default <T> TReturns the value kept underkeyfor this run, making it withinitthe first time any condition or action of the run asks for it.default <T extends AutoCloseable>
TrunScopedClosing(Object key, Supplier<? extends T> init) Returns the value kept underkeyfor this run, asrunScoped(java.lang.Object, java.util.function.Supplier<? extends T>)does, and closes it when the run ends.default InstantReturns when the run started, by the engine'sRulesEngineBuilder.clock(...): the one instant the run read from it, which judged every rule'svalidFromandvalidTo, and which the run's listeners get asRunContext.startedAt().tags()Returns the tags the run was given withRunOptions.withTags(...): the run uses only the rules that carry at least one of them, and skips the others.default DurationtimeLeft()Returns how long the run has left before it must stop, measured as the engine measures it.
-
Method Details
-
facts
Returns the values of the run's facts by name.- Returns:
- A read-only map, whose values can be
null; writing to it throwsUnsupportedOperationException
-
isCancelled
boolean isCancelled()Returns whether the run must stop, because its thread has been interrupted or it has passed its deadline.The engine checks this itself before each condition and each action, and again when each one returns, so a language that evaluates an expression and returns needn't. A language whose expressions can stop part-way polls it, or maps it to its own cancellation, so a long-running expression stops too; one whose runtime can be stopped only from outside registers an action with
onCancel(Runnable)for the deadline. Returning any value is enough: the engine stops the run as soon as the expression returns, whatever it returned. Throwing an exception once the run is cancelled stops the run the same way; anError, or an exception with one anywhere in its cause chain, such as one wrapping what a fact's Java code threw, is still that rule's failure. A language that can't stop inside an expression runs it to its end, and the run stops when it returns.- Returns:
trueif the run must stop
-
deadline
@Nullable Instant deadline()Returns when the run must stop, from the timeout its engine or itsruncall was given: when the run started, on the system clock, plus its timeout. So it compares withInstant.now(), and shows the deadline a person or a log expects.It is for showing. What stops the run is measured with a monotonic clock from when the run started, so a step of the system clock while the run goes on moves
Instant.now(), but not when the run stops. To time a language's own work, usetimeLeft(), which is exact;isCancelled()says whether the run must stop.- Returns:
- The deadline, or
nullif the run has none
-
timeLeft
Returns how long the run has left before it must stop, measured as the engine measures it. A language that gives a call of its own a timeout, such as a script it runs on another thread, gives it this much time; working it out fromdeadline()andInstant.now()instead is off by as much as the system clock has been stepped since the run started.A run without a deadline has
Duration.ofNanos(Long.MAX_VALUE), about 292 years, and a run whose deadline is further away than that has a little less: so the time left always converts withDuration.toNanos()orDuration.toMillis()without overflowing. The time left says nothing about an interrupt:isCancelled()answers for both.- Returns:
- The time left: positive while the deadline is ahead,
Duration.ZEROonce it has passed, and at mostDuration.ofNanos(Long.MAX_VALUE)
-
startedAt
Returns when the run started, by the engine'sRulesEngineBuilder.clock(...): the one instant the run read from it, which judged every rule'svalidFromandvalidTo, and which the run's listeners get asRunContext.startedAt(). A condition that compares against the current time should compare against this, so it agrees with the rules the run selected and with a test's fixed clock. It isn't comparable todeadline(), which is measured with the system clock. A nested run reads the clock again, so its contexts have an instant of their own.- Returns:
- The instant the run started at
-
tags
Returns the tags the run was given withRunOptions.withTags(...): the run uses only the rules that carry at least one of them, and skips the others. They are the run'sRunContext.tags(), the same set, so a language can bind them to a name its expressions read without copying them. A nested run has tags of its own.- Returns:
- The run's tags: in
Stringorder, unmodifiable, and empty if the run uses every rule
-
onCancel
Hasactionrun once when the run passes its deadline, unless the registration returned is closed first. It is for a language whose runtime can be stopped part-way only from outside the expression, by setting a flag or cancelling the runtime, so it needs no timer of its own: the action does that, and the expression stops.The action runs while the expression runs, on a pooled daemon platform thread, or on a virtual thread of its own when the pool can't start one, so it must be thread-safe; if no thread can be started for it, as when the JVM is out of memory, that is logged and the action never runs. If the engine's cancel timer, the thread that waits for the deadline, can't be started, this method throws instead, and nothing is registered. The action's thread inherits none of the run's inheritable thread-locals, and has no context class loader. A pooled thread runs later actions, of any run or engine, and after each resets only its context class loader and interrupt status, so the action must remove any
ThreadLocalvalue or MDC entry it sets, or the next action on that thread sees it. The action should be short, such as setting a flag that the runtime reads: one still running, or not yet started because no thread was free, a second after the timer handed it over is logged at WARN, though a slow action delays no other, of this run or any other. What it throws, or why no thread could be started for it, is logged and ignored: at ERROR if it is, or carries, a fatalError, as defined next, and at WARN otherwise. If the deadline has already passed, the action runs on this thread before this method returns, and a fatalErrorit throws, aVirtualMachineErrorsuch asOutOfMemoryErrorbut not aStackOverflowErrornor anInternalErrorthat wraps one (whatThrowables.isFatal(java.lang.Throwable)tells, leaving out whatThrowables.isOverflow(java.lang.Throwable)reads as an overflow), or an exception that carries one (whatThrowables.fatalError(java.lang.Throwable)finds), is thrown from this method instead. A run without a deadline registers nothing: the registration returned does nothing when closed. Passing the deadline stops the run asisCancelled()describes, and never interrupts its thread, so neither does the action unless it interrupts it itself. An interrupt never runs the action:isCancelled()still answers for both.The deadline is the one
deadline()shows, measured astimeLeft()measures it, so a nested run's is no later than the run it was started from. Every context of a run, the evaluation context and each action context, registers against the same deadline. The run closes the registrations still open when it ends, as it closes the values kept withrunScopedClosing(java.lang.Object, java.util.function.Supplier<? extends T>), and none can be registered after that. Closing doesn't wait for an action that has started, so an action whose deadline passed just as its expression or its run ended may still run just after, even once the run has returned. A language that reuses its runtime therefore gives each expression a stop flag of its own, made fresh for it and registered and closed with it, or checksisCancelled()before it acts on the flag, so a late action can't stop a later expression.- Parameters:
action- What to run when the run passes its deadline- Returns:
- The registration, whose
CancelRegistration.close()stops the action from running if it hasn't started - Throws:
NullPointerException- ifactionisnullIllegalStateException- if the run has ended, or its registrations are being closedVirtualMachineError- the fatal error the action threw, or carried, when the deadline had already passed and the action ran on this thread; or the error, such as anOutOfMemoryError, with which starting the engine's cancel timer thread failed, in which case nothing is registered
-
runScoped
Returns the value kept underkeyfor this run, making it withinitthe first time any condition or action of the run asks for it. So a language that converts the facts before it evaluates an expression, such as withFactProperties.toData(java.lang.Object, int), can convert them once per run rather than once per expression.Every condition and action of one
run()shares the values: the evaluation context and each action context of the run return the same value for a key. A nested run has values of its own, and the next run starts with none. A key the language owns, such as its compiler, keeps its values apart from another language's. The same key must always hold the same type, or the caller gets aClassCastException.The value is made when it is first asked for, and isn't made again during the run, so it doesn't see a change that Java code makes to a fact later in the run. The engine keeps the values until the run returns, and then lets them go without closing them: a resource a language opens for one run belongs in
runScopedClosing(java.lang.Object, java.util.function.Supplier<? extends T>), which closes it when the run ends, and one it keeps from run to run in itsSession. Another thread can ask for a value too, such as a script the language runs on another thread: a request for a key whoseinitis running on another thread throwsIllegalStateExceptionrather than waiting for it. Ifinitthrows, nothing is kept, and the next call for the key calls itsinitagain. Aninitcan ask for other keys, but not for its own: that throwsIllegalStateException, rather than recursing or making the value twice.- Type Parameters:
T- The value's type- Parameters:
key- The key, compared withObject.equals(Object)init- Makes the value the first time the run asks forkey- Returns:
- The value kept under
key, nevernull - Throws:
NullPointerException- ifkeyorinitisnull, orinitreturnsnullIllegalStateException- if theinitofkeyis running, so it asked for its own key or is running on another thread, or the run keeps a value underkeywithrunScopedClosing(java.lang.Object, java.util.function.Supplier<? extends T>)
-
runScopedClosing
Returns the value kept underkeyfor this run, asrunScoped(java.lang.Object, java.util.function.Supplier<? extends T>)does, and closes it when the run ends. It is for a resource a language opens for one run and must release, such as a runtime's context or interpreter, or a connection the run's expressions share.The engine closes the values once the run has ended, however it ends: it returns, fails, passes its deadline or is interrupted. It closes them on the thread that ran it, after the run's last listener call and before
run()returns. The run still holds its copy of the rules then, so no other run uses its sessions until its values are closed, and no session of the copy is closed before them. They are closed in the reverse of the order they were made, so a value whoseinitasked for another is closed before that one, and each is closed whatever the others throw. What aclose()throws is handled as what aSession.close()throws is: it is logged at WARN, and the run's outcome stands, unless it is a fatalError, such as anOutOfMemoryError. Once every value is closed,run()throws that error in place of the result, or of a failure of the run that isn't fatal, which the error carries as a suppressed exception; a run that failed with a fatal error of its own throws its own, carrying the one fromclose(). A nested run closes its own values when it ends.A key is either closing or not: asking with this method for a key that the run keeps a value under with
runScoped(java.lang.Object, java.util.function.Supplier<? extends T>), or the other way round, throwsIllegalStateException. So does asking for a value with this method once the run's values are being closed, or have been, such as from a value'sclose()or through a context kept past its run, as that value would never be closed. This method can be called from another thread while the run ends: the value is then either closed with the run's others, or refused withIllegalStateException, and never left open, unless the run's end fails twice, as it can when the stack or the heap runs out. Theinits of closing values run one at a time: a call on another thread while one runs waits for it, then gets the value made for its key, or, if thatinitthrew, makes its own. The wait has no limit and ignores interrupts, keeping the interrupt status; the run's end waits the same way, and a waiting call is refused once the run begins to end. So aninitthat waits for another thread which itself calls this method hangs both. A language's unit tests close a test context's values withio.github.brantunger.unruly.test.LanguageTestContexts.endRun. If aninitends the run that way itself and returns a value, nothing is kept underkey: the value is closed, and refused with theIllegalStateExceptiona later call gets, which carries what thatclose()threw as a suppressed exception, unless that is or carries a fatalError. The error is thrown in its place, carrying theIllegalStateException. Otherwise the values behave asrunScoped(java.lang.Object, java.util.function.Supplier<? extends T>)describes.- Type Parameters:
T- The value's type- Parameters:
key- The key, compared withObject.equals(Object)init- Makes the value the first time the run asks forkey- Returns:
- The value kept under
key, nevernull - Throws:
NullPointerException- ifkeyorinitisnull, orinitreturnsnullIllegalStateException- if theinitofkeyis running, so it asked for its own key, the run keeps a value underkeywithrunScoped(java.lang.Object, java.util.function.Supplier<? extends T>), or the run's values are being or have been closed, including wheninititself ended the run
-