Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Enhancements
* In assignment to messages associated with symbols, the attribute ``Protected`` is not having into account, following the standard in WMA. With this and the above change, Combinatorical 2.0 works as written.
* ``Share[]`` performs an explicit call to the Python garbage collection and returns the amount of memory free.
* Improving the compatibility of ``TeXForm`` and ``MathMLForm`` outputs with WMA. MatML tags around numbers appear as "<mn>" tags instead of "<mtext>", except in the case of ``InputForm`` expressions. In TeXForm some quotes around strings have been removed to conform to WMA. It is not clear whether this is the correct behavior.

* ``TimeConstrained`` now works on pyston.


Documentation
Expand Down
38 changes: 23 additions & 15 deletions mathics/builtin/datentime.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from mathics.core.symbols import Symbol, SymbolList
from mathics.core.systemsymbols import (
SymbolAborted,
SymbolFailed,
SymbolInfinity,
SymbolNull,
SymbolRowBox,
Expand Down Expand Up @@ -1040,14 +1041,21 @@ class Pause(Builtin):

def apply(self, n, evaluation):
"Pause[n_]"
sleeptime = n.to_python()
if not isinstance(sleeptime, (int, float)) or sleeptime < 0:
sleeptime = float(n.to_python())
if not isinstance(sleeptime, float) or sleeptime < 0:
evaluation.message(
"Pause", "numnm", Expression(SymbolPause, from_python(n))
)
return

time.sleep(sleeptime)
# This checks each 10ms if the evaluation
# was stopped.
while sleeptime > 0.01:
sleeptime = sleeptime - 0.01
time.sleep(0.01)
if evaluation.timeout:
return SymbolNull
if sleeptime > 0:
time.sleep(sleeptime)
return SymbolNull


Expand Down Expand Up @@ -1088,7 +1096,7 @@ def evaluate(self, evaluation):
return Expression(SymbolDateObject.evaluate(evaluation))


if sys.platform != "win32" and not hasattr(sys, "pyston_version_info"):
if True: # sys.platform != "win32":

class TimeConstrained(Builtin):
r"""
Expand All @@ -1105,24 +1113,24 @@ class TimeConstrained(Builtin):
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 \Mathics kernel.

"""

# FIXME: these tests sometimes cause SEGVs which probably means
# that TimeConstraint has bugs.

# Consider testing via unit tests.
# >> TimeConstrained[Integrate[Sin[x]^1000000,x],1]
# = $Aborted
>> TimeConstrained[Pause[.5];x,.1]
= $Aborted

# >> TimeConstrained[Integrate[Sin[x]^1000000,x], 1, Integrate[Cos[x],x]]
# = Sin[x]
>> TimeConstrained[Pause[.5];Integrate[Sin[x],x], .1, Integrate[Cos[x],x]]
= Sin[x]

# >> s=TimeConstrained[Integrate[Sin[x] ^ 3, x], a]
# : Number of seconds a is not a positive machine-sized number or Infinity.
# = TimeConstrained[Integrate[Sin[x] ^ 3, x], a]
>> s=TimeConstrained[Integrate[Sin[x] ^ 3, x], a]
: Number of seconds a is not a positive machine-sized number or Infinity.
= TimeConstrained[Integrate[Sin[x] ^ 3, x], a]

# >> a=1; s
# = Cos[x] (-5 + Cos[2 x]) / 6
>> a=1.; s
= Cos[x] (-3 + Cos[x] ^ 2) / 3
"""

attributes = hold_all | protected
messages = {
Expand Down
1 change: 1 addition & 0 deletions mathics/builtin/inout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,7 @@ class General(Builtin):
"invalidargs": "Invalid arguments.",
"notboxes": "`1` is not a valid box structure.",
"pyimport": '`1`[] is not available. Python module "`2`" is not installed.',
"warn": "`1`",
}
summary_text = "general-purpose messages"

Expand Down
38 changes: 35 additions & 3 deletions mathics/core/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,38 @@ def _thread_target(request, queue) -> None:
queue.put((False, exc_info))


def kill_thread(thread) -> bool:
"""
Tries to kill a thread.
If successful, returns True; otherwise, False.
"""
# See https://www.geeksforgeeks.org/python-different-ways-to-kill-a-thread/

from ctypes import pythonapi, py_object, c_long

thread_id = None
# First, look for the thread id
if hasattr(thread, "_thread_id"):
thread_id = thread._thread_id
else:
import threading

for id, thr in threading._active.items():
if thr is thread:
thread_id = id
if thread_id is None:
# The thread does not exists anymore. Our work has been done.
return True

result = pythonapi.PyThreadState_SetAsyncExc(
c_long(thread_id), py_object(SystemExit)
)
if result == 1:
return True
pythonapi.PyThreadState_SetAsyncExc(c_long(thread_id), None)
return False


# MAX_RECURSION_DEPTH gives the maximum value allowed for $RecursionLimit. it's usually set to its
# default settings.DEFAULT_MAX_RECURSION_DEPTH.

Expand Down Expand Up @@ -117,10 +149,10 @@ def run_with_timeout_and_stack(request, timeout, evaluation):
thread.join(timeout)
if thread.is_alive():
evaluation.timeout = True
while thread.is_alive():
pass
evaluation.timeout = False
if not kill_thread(thread):
evaluation.message("General", "warn", "thread couldn't be stopped.")
evaluation.stopped = False
evaluation.timeout = False
raise TimeoutInterrupt()

success, result = queue.get()
Expand Down
70 changes: 55 additions & 15 deletions test/builtin/test_datentime.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,72 @@
# -*- coding: utf-8 -*-
from test.helper import check_evaluation, evaluate
from mathics.core.symbols import Symbol

import pytest
import sys

import time


@pytest.mark.skipif(
sys.platform in ("win32",) or hasattr(sys, "pyston_version_info"),
reason="TimeConstrained needs to be rewritten",
)
def test_timeremaining():
str_expr = "TimeConstrained[1+2; TimeRemaining[], 0.9]"
result = evaluate(str_expr)
assert result is None or 0 < result.to_python() < 9


@pytest.mark.skip(reason="TimeConstrained needs to be rewritten")
def test_timeconstrained1():
#
def test_timeconstrained_assignment_1():
# This test
str_expr1 = "a=1.; TimeConstrained[Do[Pause[.1];a=a+1,{1000}],1]"
result = evaluate(str_expr1)
str_expected = "$Aborted"
expected = evaluate(str_expected)
assert result == expected
time.sleep(1)
assert evaluate("a").to_python() == 10
# if all the operations where instantaneous, then the
# value of ``a`` should be 10. However, in macOS, ``a``
# just reach 3...
assert evaluate("a").to_python() <= 10


def test_timeconstrained_assignment_2():
# This test checks if the assignment is really aborted
# if the RHS exceeds the wall time.
str_expr1 = "a=1.; TimeConstrained[a=(Pause[.2];2.),.1]"
result = evaluate(str_expr1)
str_expected = "$Aborted"
expected = evaluate(str_expected)
assert result == expected
time.sleep(0.2)
assert evaluate("a").to_python() == 1.0


@pytest.mark.skip(
reason="the current implementation fails to work in nested TimeConstrained expressions..."
)
def test_timeconstrained_assignment_nested():
# This test checks if the assignment is really aborted
# if the RHS exceeds the wall time.
str_expr1 = (
"a=1.;TimeConstrained[TimeConstrained[a=(Pause[.1];2.), .3, a=-2], .1,a=-3]"
)
result = evaluate(str_expr1)
str_expected = "-3"
expected = evaluate(str_expected)
assert result == expected
time.sleep(0.5)
assert evaluate("a").to_python() == -3


def test_timeconstrained_sympy():
# This test tries to run a large and onerous calculus that runs
# in sympy (outside the control of Mathics).
# If the behaviour is the right one, the evaluation
# is interrupted before it saturates memory and raise a SIGEV
# exception.
str_expr = "TimeConstrained[Integrate[Sin[x]^1000000, x], 0.9]"
result = evaluate(str_expr)

assert result is None or result == Symbol("$Aborted")


def test_timeremaining():
str_expr = "TimeConstrained[1+2; TimeRemaining[], 0.9]"
result = evaluate(str_expr)
assert result is None or 0 < result.to_python() < 0.9


def test_datelist():
Expand Down Expand Up @@ -55,7 +95,7 @@ def test_datelist():
check_evaluation(str_expr, str_expected)


def test_datestring():
def test_datestring2():
for str_expr, str_expected in (
## Check Leading 0s
# (
Expand Down