Skip to content

Commit 63d482a

Browse files
committed
Wait by realtime, avoid unexplained slowdown
1 parent 40dfc5f commit 63d482a

File tree

1 file changed

+35
-20
lines changed

1 file changed

+35
-20
lines changed

src/sage/doctest/util.py

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -766,49 +766,59 @@ def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float =
766766
767767
``as data`` is optional, but if it is used, it will contain a few useful values::
768768
769-
sage: data # abs tol 0.2
769+
sage: data # abs tol 0.01
770770
{'alarm_raised': True, 'elapsed': 1.0}
771771
772772
``max_wait_after_interrupt`` can be passed if the function may take longer than usual to be interrupted::
773773
774-
sage: cython('''
775-
....: from libc.time cimport clock_t, clock, CLOCKS_PER_SEC
774+
sage: cython(r'''
775+
....: from posix.time cimport clock_gettime, CLOCK_REALTIME, timespec
776776
....: from cysignals.signals cimport sig_check
777+
....: from time import time as walltime
778+
....:
777779
....: cpdef void uninterruptible_sleep(double seconds):
778-
....: cdef clock_t target = clock() + <clock_t>(CLOCKS_PER_SEC * seconds)
779-
....: while clock() < target:
780-
....: pass
780+
....: cdef timespec start_time, target_time
781+
....: start_walltime = walltime()
782+
....: clock_gettime(CLOCK_REALTIME, &start_time)
783+
....:
784+
....: cdef int floor_seconds = <int>seconds
785+
....: target_time.tv_sec = start_time.tv_sec + floor_seconds
786+
....: target_time.tv_nsec = start_time.tv_nsec + <int>((seconds - floor_seconds) * 1e9)
787+
....: if target_time.tv_nsec >= 1000000000:
788+
....: target_time.tv_nsec -= 1000000000
789+
....: target_time.tv_sec += 1
790+
....:
791+
....: while True:
792+
....: clock_gettime(CLOCK_REALTIME, &start_time)
793+
....: if start_time.tv_sec > target_time.tv_sec or (start_time.tv_sec == target_time.tv_sec and start_time.tv_nsec >= target_time.tv_nsec):
794+
....: break
795+
....:
781796
....: cpdef void check_interrupt_only_occasionally():
782797
....: for i in range(10):
783798
....: uninterruptible_sleep(0.8)
784799
....: sig_check()
785800
....: ''')
786-
sage: with ensure_interruptible_after(1) as data: # not passing max_wait_after_interrupt will raise an error
801+
sage: with ensure_interruptible_after(1): # not passing max_wait_after_interrupt will raise an error
787802
....: check_interrupt_only_occasionally()
788803
Traceback (most recent call last):
789804
...
790-
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 1... seconds
805+
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 1.60... seconds
791806
sage: with ensure_interruptible_after(1, max_wait_after_interrupt=0.9):
792807
....: check_interrupt_only_occasionally()
793808
794809
TESTS::
795810
796-
sage: data['elapsed'] # abs tol 0.3 # 1.6 = 0.8 * 2
797-
1.6
798-
799-
::
800-
801811
sage: with ensure_interruptible_after(2) as data: sleep(1)
802812
Traceback (most recent call last):
803813
...
804-
RuntimeError: Function terminates early after 1... < 2.0000 seconds
805-
sage: data # abs tol 0.2
814+
RuntimeError: Function terminates early after 1.00... < 2.0000 seconds
815+
sage: data # abs tol 0.01
806816
{'alarm_raised': False, 'elapsed': 1.0}
807817
sage: with ensure_interruptible_after(1) as data: raise ValueError
808818
Traceback (most recent call last):
809819
...
810820
ValueError
811-
sage: data # abs tol 0.2
821+
sage: data # abs tol 0.01
812822
{'alarm_raised': False, 'elapsed': 0.0}
813823
814824
::
@@ -817,16 +827,20 @@ def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float =
817827
sage: with ensure_interruptible_after(1) as data: uninterruptible_sleep(2)
818828
Traceback (most recent call last):
819829
...
820-
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2... seconds
821-
sage: data # abs tol 0.2
830+
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2.00... seconds
831+
sage: data # abs tol 0.01
822832
{'alarm_raised': True, 'elapsed': 2.0}
823833
sage: with ensure_interruptible_after(1): uninterruptible_sleep(2); raise RuntimeError
824834
Traceback (most recent call last):
825835
...
826-
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2... seconds
827-
sage: data # abs tol 0.2
836+
RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2.00... seconds
837+
sage: data # abs tol 0.01
828838
{'alarm_raised': True, 'elapsed': 2.0}
829839
"""
840+
seconds = float(seconds)
841+
max_wait_after_interrupt = float(max_wait_after_interrupt)
842+
inaccuracy_tolerance = float(inaccuracy_tolerance)
843+
# use Python float to avoid unexplained slowdown with Sage objects
830844
data = {}
831845
start_time = walltime()
832846
alarm(seconds)
@@ -837,6 +851,7 @@ def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float =
837851
except AlarmInterrupt:
838852
alarm_raised = True
839853
finally:
854+
before_cancel_alarm_elapsed = walltime() - start_time
840855
cancel_alarm()
841856
elapsed = walltime() - start_time
842857
data["elapsed"] = elapsed

0 commit comments

Comments
 (0)