Skip to content

Commit e1020ed

Browse files
Release Managervbraun
Release Manager
authored andcommitted
Trac #24986: Inconsistent mpz_t state after interrupted sig_realloc()
=== TODO === ||☐||Use `sig_occurred()` to check whether an exception from Cysignals is currently being handled while in `Integer.tp_dealloc`. If so, assume that the state of the object's mpz struct may not be consistent, so do not call `mpz_clear` on it and do not place it back in the free pool.|| ||☐||Don't forget to re-enable the test that was disabled in #25137 in order to test that this is fixed.|| ---- As discussed on sage-devel, I'm fairly consistently (roughly 9 times out of 10) getting the following failure on Cygwin: {{{ sage -t --warn-long 164.8 src/sage/structure/coerce_actions.pyx ********************************************************************** File "src/sage/structure/coerce_actions.pyx", line 786, in sage.structure.coerce_actions.IntegerMulAction._repr_name_ Failed example: IntegerMulAction(ZZ, GF5) Expected: Left Integer Multiplication by Integer Ring on Finite Field of size 5 Got: Left Integer Multiplication by Integer Ring on Finite Field of size 1 ********************************************************************** 1 item had failures: 1 of 4 in sage.structure.coerce_actions.IntegerMulAction._repr_name_ [143 tests, 1 failure, 3.30 s] ---------------------------------------------------------------------- sage -t --warn-long 164.8 src/sage/structure/coerce_actions.pyx # 1 doctest failed }}} Obviously Sage doesn't even allow creation of an order 1 field. In fact, I traced the cause of this to a ''specific'' line in `FiniteFieldFactory.create_key_and_extra_args` where, by chance, an `Integer` with a value of `1` is constructed (using `fast_tp_new`) whose `(mp_limb*)(Integer.value._mp_d)` member is assigned the same address as the `_mp_d` of the `Integer` that happens to hold the field's order. The result is that the `order` is then set to `1` as well. This happens ''after'' the check that `order>1` so creation of the field still succeeds. Clearly there is a subtle bug either in `fast_tp_new`, or in the memory allocator itself. URL: https://trac.sagemath.org/24986 Reported by: embray Ticket author(s): Jeroen Demeyer Reviewer(s): Erik Bray
2 parents d0a072e + 521bac9 commit e1020ed

File tree

3 files changed

+43
-26
lines changed

3 files changed

+43
-26
lines changed

src/sage/doctest/forker.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,16 @@ def compiler(example):
673673
raise
674674
except BaseException:
675675
exception = sys.exc_info()
676+
# On Python 2, the exception lives in sys.exc_info() as
677+
# long we are in the same stack frame. To ensure that
678+
# sig_occurred() works correctly, we need to clear the
679+
# exception. This is not an issue on Python 3, where the
680+
# exception is cleared as soon as we are outside of the
681+
# "except" clause.
682+
try:
683+
sys.exc_clear()
684+
except AttributeError:
685+
pass # Python 3
676686
finally:
677687
if self.debugger is not None:
678688
self.debugger.set_continue() # ==== Example Finished ====
@@ -699,8 +709,7 @@ def compiler(example):
699709

700710
# The example raised an exception: check if it was expected.
701711
else:
702-
exc_info = exception
703-
exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
712+
exc_msg = traceback.format_exception_only(*exception[:2])[-1]
704713

705714
if six.PY3 and example.exc_msg is not None:
706715
# On Python 3 the exception repr often includes the
@@ -709,7 +718,7 @@ def compiler(example):
709718
# normalize Python 3 exceptions to match tests written to
710719
# Python 2
711720
# See https://trac.sagemath.org/ticket/24271
712-
exc_cls = exc_info[0]
721+
exc_cls = exception[0]
713722
exc_name = exc_cls.__name__
714723
if exc_cls.__module__:
715724
exc_fullname = (exc_cls.__module__ + '.' +
@@ -736,7 +745,7 @@ def compiler(example):
736745
break
737746

738747
if not quiet:
739-
got += doctest._exception_traceback(exc_info)
748+
got += doctest._exception_traceback(exception)
740749

741750
# If `example.exc_msg` is None, then we weren't expecting
742751
# an exception.
@@ -770,7 +779,7 @@ def compiler(example):
770779
elif outcome is BOOM:
771780
if not quiet:
772781
self.report_unexpected_exception(out, test, example,
773-
exc_info)
782+
exception)
774783
failures += 1
775784
else:
776785
assert False, ("unknown outcome", outcome)

src/sage/rings/integer.pyx

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,10 @@ cimport cython
144144
from libc.math cimport (ldexp, sqrt as sqrt_double, log as log_c,
145145
ceil as ceil_c, isnan)
146146
from libc.string cimport memcpy
147-
cdef extern from "<limits.h>":
148-
const long LONG_MAX # Work around https://github.com/cython/cython/pull/2016
147+
from libc.limits cimport LONG_MAX
149148

150149
from cysignals.memory cimport check_allocarray, check_malloc, sig_free
151-
from cysignals.signals cimport sig_on, sig_off, sig_check
150+
from cysignals.signals cimport sig_on, sig_off, sig_check, sig_occurred
152151

153152
import operator
154153
import sys
@@ -7305,39 +7304,41 @@ cdef PyObject* fast_tp_new(type t, args, kwds) except NULL:
73057304

73067305
return new
73077306

7308-
cdef void fast_tp_dealloc(PyObject* o):
73097307

7308+
cdef void fast_tp_dealloc(PyObject* o):
73107309
# If there is room in the pool for a used integer object,
73117310
# then put it in rather than deallocating it.
7312-
73137311
global integer_pool, integer_pool_count
73147312

73157313
cdef mpz_ptr o_mpz = <mpz_ptr>((<Integer>o).value)
73167314

7317-
if integer_pool_count < integer_pool_size:
7315+
# If we are recovering from an interrupt, throw the mpz_t away
7316+
# without recycling or freeing it because it might be in an
7317+
# inconsistent state (see Trac #24986).
7318+
if sig_occurred() is NULL:
7319+
if integer_pool_count < integer_pool_size:
7320+
# Here we free any extra memory used by the mpz_t by
7321+
# setting it to a single limb.
7322+
if o_mpz._mp_alloc > 10:
7323+
_mpz_realloc(o_mpz, 1)
73187324

7319-
# Here we free any extra memory used by the mpz_t by
7320-
# setting it to a single limb.
7321-
if o_mpz._mp_alloc > 10:
7322-
_mpz_realloc(o_mpz, 1)
7325+
# It's cheap to zero out an integer, so do it here.
7326+
o_mpz._mp_size = 0
73237327

7324-
# It's cheap to zero out an integer, so do it here.
7325-
o_mpz._mp_size = 0
7328+
# And add it to the pool.
7329+
integer_pool[integer_pool_count] = o
7330+
integer_pool_count += 1
7331+
return
73267332

7327-
# And add it to the pool.
7328-
integer_pool[integer_pool_count] = o
7329-
integer_pool_count += 1
7330-
return
7331-
7332-
# Again, we move to the mpz_t and clear it. As in fast_tp_new,
7333-
# we free the memory directly.
7334-
sig_free(o_mpz._mp_d)
7333+
# No space in the pool, so just free the mpz_t.
7334+
sig_free(o_mpz._mp_d)
73357335

73367336
# Free the object. This assumes that Py_TPFLAGS_HAVE_GC is not
73377337
# set. If it was set another free function would need to be
73387338
# called.
73397339
PyObject_Free(o)
73407340

7341+
73417342
from sage.misc.allocator cimport hook_tp_functions
73427343
cdef hook_fast_tp_functions():
73437344
"""

src/sage/structure/coerce_actions.pyx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,10 +736,17 @@ cdef class IntegerMulAction(IntegerAction):
736736
737737
Check that large multiplications can be interrupted::
738738
739-
sage: alarm(0.5); (2^(10^7)) * P # not tested; see trac:#24986
739+
sage: alarm(0.5); (2^(10^6)) * P
740740
Traceback (most recent call last):
741741
...
742742
AlarmInterrupt
743+
744+
Verify that cysignals correctly detects that the above
745+
exception has been handled::
746+
747+
sage: from cysignals.tests import print_sig_occurred
748+
sage: print_sig_occurred()
749+
No current exception
743750
"""
744751
cdef int err = 0
745752
cdef long n_long

0 commit comments

Comments
 (0)