From fe35eb30d1b2a0ea774c36b28f40cfa5a78f8259 Mon Sep 17 00:00:00 2001 From: Markus Mohrhard Date: Sat, 11 Jan 2020 02:09:03 +0800 Subject: [PATCH] make iterating over sys.modules threadsafe Despite creating a copy through list(sys.modules.items()) there is a possible race condition if another thread is adding to sys.modules File "x/lib/python3.7/pickle.py", line 774, in save_tuple save(element) File "x/lib/python3.7/pickle.py", line 549, in save self.save_reduce(obj=obj, *rv) File "x/lib/python3.7/pickle.py", line 637, in save_reduce save(func) File "x/lib/python3.7/pickle.py", line 518, in save self.save_global(obj) File "x/lib/python3.7/site-packages/cloudpickle/cloudpickle.py", line 876, in save_global elif not _is_global(obj, name=name): File "x/lib/python3.7/site-packages/cloudpickle/cloudpickle.py", line 174, in _is_global module_name = _whichmodule(obj, name) File "x/lib/python3.7/site-packages/cloudpickle/cloudpickle.py", line 156, in _whichmodule for module_name, module in list(sys.modules.items()): RuntimeError: dictionary changed size during iteration --- CHANGES.md | 4 ++++ cloudpickle/cloudpickle.py | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 94dcc014f..79043afe4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ 1.2.3 ===== +- Fix a bug when a thread imports a module while cloudpickle iterates + over the module list + ([PR #322](https://github.com/cloudpipe/cloudpickle/pull/322)). + 1.2.2 ===== diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index 1d4c85cbe..fc0372be0 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -151,9 +151,10 @@ def _whichmodule(obj, name): module_name = getattr(obj, '__module__', None) if module_name is not None: return module_name - # Protect the iteration by using a list copy of sys.modules against dynamic - # modules that trigger imports of other modules upon calls to getattr. - for module_name, module in list(sys.modules.items()): + # Protect the iteration by using a copy of sys.modules against dynamic + # modules that trigger imports of other modules upon calls to getattr or + # other threads importing at the same time. + for module_name, module in sys.modules.copy().items(): if module_name == '__main__' or module is None: continue try: