Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit TimeConstrained to one per eval. #1202

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
50 changes: 33 additions & 17 deletions mathics/builtin/datentime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,8 +1069,7 @@ def evaluate(self, evaluation):
import stopit

class TimeConstrained(Builtin):
"""
<url>:WMA link:https://reference.wolfram.com/language/ref/TimeConstrained.html</url>
"""<url>:WMA link:https://reference.wolfram.com/language/ref/TimeConstrained.html</url>

<dl>
<dt>'TimeConstrained[$expr$, $t$]'
Expand All @@ -1080,13 +1079,23 @@ class TimeConstrained(Builtin):
<dd>'returns $failexpr$ if the time constraint is not met.'
</dl>

Possible issues: for certain time-consuming functions (like simplify)
which are based on sympy or other libraries, it is possible that
the evaluation continues after the timeout. However, at the end of the \
evaluation, the function will return '$Aborted' and the results will not affect
the state of the Mathics3 kernel.
At most one 'TimeConstrained[]' evalution can be performed at a time. \
If a second 'TimeConstrianed[]' is called when another is in progress, \
it will be executed without a time constraint.

In time-consuming library functions, like simplify, evalution \
can continue after the timeout. However, at the end of the \
evaluation, the function will return '$Aborted' and the
results will not affect the state of the Mathics3 kernel.

In Python, a timeout exception can occur at a place where \
Python is not in a position to be able to handle it. This is \
called an "unraisable exception".
"""

# If True, we are already running TimeConstrained[].
is_running_TimeConstrained = False

attributes = A_HOLD_ALL | A_PROTECTED
messages = {
"timc": (
Expand All @@ -1097,33 +1106,40 @@ class TimeConstrained(Builtin):

summary_text = "run a command for at most a specified time"

def eval_2(self, expr, t, evaluation):
def eval(self, expr, t, evaluation: Evaluation):
"TimeConstrained[expr_, t_]"
return self.eval_3(expr, t, SymbolAborted, evaluation)
return self.eval_with_failexpr(expr, t, SymbolAborted, evaluation)

def eval_3(self, expr, t, failexpr, evaluation):
def eval_with_failexpr(self, expr, t, failexpr, evaluation: Evaluation):
"TimeConstrained[expr_, t_, failexpr_]"
t = t.evaluate(evaluation)
if not t.is_numeric(evaluation):
evaluation.message("TimeConstrained", "timc", t)
return

if self.is_running_TimeConstrained:
return expr.evaluate(evaluation)

try:
timeout = float(t.to_python())
evaluation.timeout_queue.append((timeout, datetime.now().timestamp()))
request = lambda: expr.evaluate(evaluation)
done = False
with stopit.ThreadingTimeout(timeout) as to_ctx_mgr:
self.is_running_TimeConstrained = True
assert to_ctx_mgr.state == to_ctx_mgr.EXECUTING
result = request()
done = True
if done:
result = expr.evaluate(evaluation)
self.is_running_TimeConstrained = False
if to_ctx_mgr.state == to_ctx_mgr.EXECUTED:
evaluation.timeout_queue.pop()
return result
except Exception:
to_ctx_mgr.cancel()
self.is_running_TimeConstrained = False
evaluation.timeout_queue.pop()
raise
evaluation.timeout_queue.pop()
return failexpr.evaluate(evaluation)
result = failexpr.evaluate(evaluation)
self.is_running_TimeConstrained = False
return result


class TimeZone(Predefined):
Expand Down Expand Up @@ -1151,7 +1167,7 @@ class TimeZone(Predefined):

summary_text = "gets the default time zone"

def evaluate(self, evaluation) -> Real:
def evaluate(self, evaluation: Evaluation) -> Real:
return self.value


Expand Down
Loading