enthought / traits

Observable typed attributes for Python classes
Other
432 stars 85 forks source link

"dictionary changed size during iteration" error from AdaptationManager operations #1616

Open mdickinson opened 2 years ago

mdickinson commented 2 years ago

When iterating through existing adaptation offers for the first time, Traits resolves string-based protocols by performing the appropriate import. That import can execute arbitrary Python code, including code that itself registers adaptation offers. If that occurs, we get a "dictionary changed size during iteration" RuntimeError.

To reproduce, create files file1.py and file2.py in the current directory, with the following contents. For file1.py:

from traits.api import adapt, Adapter, Interface, provides, register_factory

class ISomeInterface(Interface):
    pass

@provides(ISomeInterface)
class MyAdapter(Adapter):
    pass

register_factory(MyAdapter, "file2.SomeFactory", ISomeInterface)
adapt(range(10), ISomeInterface)

For file2.py:

from traits.api import Adapter, Interface, provides, register_factory

class ISomeOtherInterface(Interface):
    pass

@provides(ISomeOtherInterface)
class SliceAdapter(Adapter):
    pass

register_factory(SliceAdapter, slice, ISomeOtherInterface)

class SomeFactory():
    pass

Now executing file1.py gives the following traceback:

(traits) mdickinson@mirzakhani Desktop % python file1.py 
Traceback (most recent call last):
  File "/Users/mdickinson/Desktop/file1.py", line 11, in <module>
    adapt(range(10), ISomeInterface)
  File "/Users/mdickinson/Enthought/ETS/traits/traits/adaptation/adaptation_manager.py", line 413, in adapt
    return manager.adapt(adaptee, to_protocol, default)
  File "/Users/mdickinson/Enthought/ETS/traits/traits/adaptation/adaptation_manager.py", line 133, in adapt
    result = self._adapt(adaptee, to_protocol)
  File "/Users/mdickinson/Enthought/ETS/traits/traits/adaptation/adaptation_manager.py", line 260, in _adapt
    edges = self._get_applicable_offers(current_protocol, path)
  File "/Users/mdickinson/Enthought/ETS/traits/traits/adaptation/adaptation_manager.py", line 321, in _get_applicable_offers
    for from_protocol_name, offers in self._adaptation_offers.items():
RuntimeError: dictionary changed size during iteration

We should fix the code to be more robust against this kind of change.