@@ -762,28 +762,27 @@ def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float =
762762    EXAMPLES:: 
763763
764764        sage: from sage.doctest.util import ensure_interruptible_after 
765-         sage: with ensure_interruptible_after(1) as data: sleep(3r ) 
765+         sage: with ensure_interruptible_after(1) as data: sleep(3 ) 
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.01  
769+         sage: data  # abs tol 1  
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: # needs sage.misc.cython 
774775        sage: cython(r''' 
775-         ....: from posix.time cimport clock_gettime, CLOCK_REALTIME, timespec 
776+         ....: from posix.time cimport clock_gettime, CLOCK_REALTIME, timespec, time_t  
776777        ....: from cysignals.signals cimport sig_check 
777-         ....: from time import time as walltime 
778778        ....: 
779779        ....: cpdef void uninterruptible_sleep(double seconds): 
780780        ....:     cdef timespec start_time, target_time 
781-         ....:     start_walltime = walltime() 
782781        ....:     clock_gettime(CLOCK_REALTIME, &start_time) 
783782        ....: 
784-         ....:     cdef int  floor_seconds = <int >seconds 
783+         ....:     cdef time_t  floor_seconds = <time_t >seconds 
785784        ....:     target_time.tv_sec = start_time.tv_sec + floor_seconds 
786-         ....:     target_time.tv_nsec = start_time.tv_nsec + <int >((seconds - floor_seconds) * 1e9) 
785+         ....:     target_time.tv_nsec = start_time.tv_nsec + <long >((seconds - floor_seconds) * 1e9) 
787786        ....:     if target_time.tv_nsec >= 1000000000: 
788787        ....:         target_time.tv_nsec -= 1000000000 
789788        ....:         target_time.tv_sec += 1 
@@ -808,23 +807,44 @@ def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float =
808807
809808    TESTS:: 
810809
811-         sage: # we use 1r instead of 1 to avoid unexplained slowdown 
812-         sage: with ensure_interruptible_after(2) as data: sleep(1r) 
810+         sage: with ensure_interruptible_after(2) as data: sleep(1) 
813811        Traceback (most recent call last): 
814812        ... 
815-         RuntimeError: Function terminates early after 1.00. .. < 2.0000 seconds 
816-         sage: data  # abs tol 0.01  
813+         RuntimeError: Function terminates early after 1... < 2.0000 seconds 
814+         sage: data  # abs tol 1  
817815        {'alarm_raised': False, 'elapsed': 1.0} 
818-         sage: with ensure_interruptible_after(1) as data: raise ValueError 
819-         Traceback (most recent call last): 
820-         ... 
821-         ValueError 
822-         sage: data  # abs tol 0.01 
823-         {'alarm_raised': False, 'elapsed': 0.0} 
824816
825-     :: 
817+     The test above requires a large tolerance, because both ``time.sleep`` and 
818+     ``from posix.unistd cimport usleep`` may have slowdown on the order of 0.1s on Mac, 
819+     likely because the system is idle and GitHub CI switches the program out, 
820+     and context switch back takes time. So we use busy wait instead:: 
826821
827822        sage: # needs sage.misc.cython 
823+         sage: cython(r''' 
824+         ....: from posix.time cimport clock_gettime, CLOCK_REALTIME, timespec, time_t 
825+         ....: from cysignals.signals cimport sig_check 
826+         ....: 
827+         ....: cpdef void interruptible_sleep(double seconds): 
828+         ....:     cdef timespec start_time, target_time 
829+         ....:     clock_gettime(CLOCK_REALTIME, &start_time) 
830+         ....: 
831+         ....:     cdef time_t floor_seconds = <time_t>seconds 
832+         ....:     target_time.tv_sec = start_time.tv_sec + floor_seconds 
833+         ....:     target_time.tv_nsec = start_time.tv_nsec + <long>((seconds - floor_seconds) * 1e9) 
834+         ....:     if target_time.tv_nsec >= 1000000000: 
835+         ....:         target_time.tv_nsec -= 1000000000 
836+         ....:         target_time.tv_sec += 1 
837+         ....: 
838+         ....:     while True: 
839+         ....:         sig_check() 
840+         ....:         clock_gettime(CLOCK_REALTIME, &start_time) 
841+         ....:         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): 
842+         ....:             break 
843+         ....: ''') 
844+         sage: with ensure_interruptible_after(2) as data: interruptible_sleep(1) 
845+         Traceback (most recent call last): 
846+         ... 
847+         RuntimeError: Function terminates early after 1.00... < 2.0000 seconds 
828848        sage: with ensure_interruptible_after(1) as data: uninterruptible_sleep(2) 
829849        Traceback (most recent call last): 
830850        ... 
@@ -837,6 +857,15 @@ def ensure_interruptible_after(seconds: float, max_wait_after_interrupt: float =
837857        RuntimeError: Function is not interruptible within 1.0000 seconds, only after 2.00... seconds 
838858        sage: data  # abs tol 0.01 
839859        {'alarm_raised': True, 'elapsed': 2.0} 
860+ 
861+     :: 
862+ 
863+         sage: with ensure_interruptible_after(1) as data: raise ValueError 
864+         Traceback (most recent call last): 
865+         ... 
866+         ValueError 
867+         sage: data  # abs tol 0.01 
868+         {'alarm_raised': False, 'elapsed': 0.0} 
840869    """ 
841870    seconds  =  float (seconds )
842871    max_wait_after_interrupt  =  float (max_wait_after_interrupt )
0 commit comments