[Python-Dev] Re: Cycles in the __context__ chain

2020年6月15日 00:49:19 -0700

Worth noting is that there is an existing loop-breaking mechanism, 
but only for the newest exception being raised. In particular, option (4)
is actually the current behavior if the the most recent exception
participates in a cycle:
 Python 3.9.0b1
 >>> A, B, C, D, E = map(Exception, "ABCDE")
 >>> A.__context__ = B
 >>> B.__context__ = C
 >>> C.__context__ = D
 >>> D.__context__ = E
 >>> try:
 ... raise A
 ... except Exception:
 ... raise C
 ...
 Exception: B
 During handling of the above exception, another exception occurred:
 Traceback (most recent call last):
 File "<stdin>", line 2, in <module>
 Exception: A
 During handling of the above exception, another exception occurred:
 Traceback (most recent call last):
 File "<stdin>", line 4, in <module>
 Exception: C
This cycle-breaking is not due to any magic in the ``PyException_SetContext()``,
which is currently a basic one-liner, but instead comes from
``_PyErr_SetObject`` in errors.c, which has something along the lines of:
 def _PyErr_SetObject(new_exc):
 top = existing_topmost_exc()
 if top is None:
 # no context
 set_top_exception(new_exc)
 return
 # convert new_exc class to instance if applicable.
 ...
 if top is new_exc:
 # already on top
 return
 e = top
 while True:
 context = e.__context__
 if context is None:
 # no loop
 break
 if context is new_exc:
 # unlink the existing exception
 e.__context__ = None
 break
 e = context
 
 new_exc.__context__ = top 
 set_top_exception(new_exc)
The only trouble comes about when there is a "rho-shaped" linked list,
in which we have a cycle not involving the new exception being raised.
For instance,
 Raising A on top of (B -> C -> D -> C -> D -> C -> ...)
 results in an infinite loop.
Two possible fixes would be to either (I) use a magical ``__context__``
setter to ensure that there is never a rho-shaped sequence, or (II)
allow arbitrary ``__context__`` graphs and then correctly handle
rho-shaped sequences in ``_PyErr_SetObject`` (i.e. at raise-time).
Fix type (I) could result in surprising things like:
 >>> A = Exception()
 >>> A.__context__ = A
 >>> A.__context__ is None
 True
so I propose fix type (II). This PR is such a fix:
https://github.com/python/cpython/pull/20539
It basically extends the existing behavior (4) to the rho-shaped case.
It also prevents the cycle-detecting logic from sitting in two places
(both _PyErr_SetObject and PyException_SetContext) and does not make any
visible functionality more magical. The only Python-visible change
should be that the infinite loop is no longer possible.
_______________________________________________
Python-Dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/[email protected]/message/R5J5JVUJX3V4DBKVLUI2SUBRD3TRF6PV/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to