Uh oh!
There was an error while loading. Please reload this page.
[MRG] Regression on pickling classes from the __main__ module - #132
Conversation
| from subprocess import check_output | ||
| from subprocess import PIPE | ||
| from subprocess import STDOUT | ||
| from subprocess import CalledProcessError |
There was a problem hiding this comment.
Can you import all those names at once? :-)
| f.write(source_code.encode('utf-8')) | ||
| cmd = [sys.executable, source_file] | ||
| pythonpath = "{cwd}/tests:{cwd}".format(cwd=os.getcwd()) |
There was a problem hiding this comment.
Can't you use __file__ or something? getcwd sounds fragile.
Codecov Report
@@ Coverage Diff @@## master #132 +/- ##
==========================================
- Coverage 84.08% 83.76% -0.32%
==========================================
Files 2 2 Lines 534 536 +2 Branches 97 98 +1 ==========================================
Hits 449 449 - Misses 63 64 +1 - Partials 22 23 +1
Continue to review full report at Codecov.
|
ogrisel
commented
Nov 13, 2017
@pitrou I have tried to make a minimal change to |
ogrisel
commented
Nov 13, 2017
The coverage decrease is caused by the new branch that can only be executed in subprocess calls and therefore not accounted in the report. |
pitrou
commented
Nov 13, 2017
I don't understand the fix. How does it work? |
ogrisel
commented
Nov 14, 2017
Apparently instances of |
ogrisel
commented
Nov 14, 2017
I have pushed a simpler fix. |
| dispatched here. | ||
| """ | ||
| if obj.__module__ == "__main__": | ||
| return self.save_dynamic_class(obj) |
There was a problem hiding this comment.
Does this mean save_dynamic_class is also used for functions? If so, could you rename that method and update its docstring?
There was a problem hiding this comment.
I don't think functions get dispatched here. The dispatch entries for save_global are:
dispatch[type] =save_globaldispatch[types.ClassType] =save_globalwhereas function-likes are dispatched via:
dispatch[types.FunctionType] =save_function
...
dispatch[types.MethodType] =save_instancemethod
...
dispatch[types.BuiltinFunctionType] =save_builtin_function
...
dispatch[classmethod] =save_classmethoddispatch[staticmethod] =save_classmethodI believe the name save_global is a holdover from the base Pickler class, which calls self.save_global in some base class methods that we call into via super, so renaming this isn't straightforward.
There was a problem hiding this comment.
I have pushed a new commit to simplify that function even further and all tests still pass. However, I am not sure we are not breaking edge cases in third party libraries and applications.
There was a problem hiding this comment.
I have run the test suite of both loky and joblib against this branch all tests pass as well so I am pretty confident that the changes are fine.
There was a problem hiding this comment.
Can you check whether save_dynamic_class really gets a function as input and, if so, change the name or docstring?
There was a problem hiding this comment.
@pitrou I'm pretty confident save_dynamic_class can never be called with a function as input. It accesses obj.__bases__ unconditionally, which doesn't exist on function objects.
| dispatched here. | ||
| """ | ||
| if obj.__module__ == "__main__": | ||
| return self.save_dynamic_class(obj) |
There was a problem hiding this comment.
Can you check whether save_dynamic_class really gets a function as input and, if so, change the name or docstring?
No it does not. It's only for classes that cannot be looked up as attributes from global modules. In our tests those are:
|
ogrisel
commented
Nov 14, 2017
The name of the PR is misleading: the new non-regression tests call interactively defined functions from the |
pitrou
commented
Nov 15, 2017
Ok, thank you for clarifying :-) |
| _builtin_type, (_BUILTIN_TYPE_NAMES[obj],), obj=obj) | ||
| raise | ||
| return self.save_dynamic_class(obj) |
There was a problem hiding this comment.
Does save_dynamic_class raise a readable exception or do we want to keep the if above?
ogrisel
commented
Nov 15, 2017
Maybe it's good idea to keep the protection: >>>importcloudpickle>>>importio>>>p=cloudpickle.CloudPickler(io.BytesIO)
>>>p.save_dynamic_class(lambdax: x)
FatalPythonerror: Cannotrecoverfromstackoverflow.
Thread0x00007fc32d281700 (mostrecentcallfirst):
File"/home/ogrisel/.virtualenvs/py36/lib/python3.6/site-packages/IPython/core/history.py", line764in_writeout_input_cacheFile"/home/ogrisel/.virtualenvs/py36/lib/python3.6/site-packages/IPython/core/history.py", line780inwriteout_cacheFile"/home/ogrisel/.virtualenvs/py36/lib/python3.6/site-packages/IPython/core/history.py", line58inneeds_sqliteFile"<decorator-gen-23>", line2inwriteout_cacheFile"/home/ogrisel/.virtualenvs/py36/lib/python3.6/site-packages/IPython/core/history.py", line834inrunFile"/home/ogrisel/.virtualenvs/py36/lib/python3.6/site-packages/IPython/core/history.py", line58inneeds_sqliteFile"<decorator-gen-24>", line2inrunFile"/usr/lib/python3.6/threading.py", line916in_bootstrap_innerFile"/usr/lib/python3.6/threading.py", line884in_bootstrapCurrentthread0x00007fc33466b700 (mostrecentcallfirst):
File"/usr/lib/python3.6/pickle.py", line264in_getattributeFile"/usr/lib/python3.6/pickle.py", line918insave_globalFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line635insave_globalFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line375insave_functionFile"/usr/lib/python3.6/pickle.py", line476insaveFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line480insave_dynamic_classFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line642insave_globalFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line375insave_functionFile"/usr/lib/python3.6/pickle.py", line476insaveFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line480insave_dynamic_classFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line642insave_globalFile"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line375insave_function
...
Aborted (coredumped) |
Actually the snippet from the previous comment is misleading, I passed the >>>importcloudpickle>>>importio>>>p=cloudpickle.CloudPickler(io.BytesIO())
>>>p.save_dynamic_class(lambdax: x)
Traceback (mostrecentcalllast):
File"<ipython-input-8-4356e554f31b>", line1, in<module>p.save_dynamic_class(lambdax: x)
File"/home/ogrisel/code/cloudpickle/cloudpickle/cloudpickle.py", line490, insave_dynamic_class# encountered while saving will point to the skeleton class.AttributeError: 'function'objecthasnoattribute'__bases__' |
pitrou
commented
Nov 15, 2017
But what was the previous message? It seems to me that Pickler generally raises PicklingError. |
However, I am not sure we should try to preserve the behavior of the private API on wrong inputs. What really matters is that it should raise a meaningful exception when calling the public API with wrong inputs and calling |
ogrisel
commented
Nov 15, 2017
@robertnishihara@iaroslav-ai could you please try this branch with your code to confirm that it fixes all your problems? |
ogrisel
commented
Nov 15, 2017
@pitrou it's true that the upstream However this case should never happen in cloudpickle as far as I know because cloudpickle has been written precisely to be able to pickle dynamic class and function definitions. I am not sure how to write a test for this. |
This reverts commit d9e02fb.
robertnishihara
commented
Nov 15, 2017
@ogrisel Thanks, I just tried it out and this seems to fix my problem. |
iaroslav-ai
commented
Nov 15, 2017
Yup that seems to solve it. Thanks @ogrisel ! |
…lasses from the __main__ module
This PR aims to fix the regression reported in #131 and that affects 0.4.2 and later. Right now there is is just a non-regression test to reproduce the problem.
I believe the regression was silently introduced by @pitrou's cleanups in #122 although I am not 100% sure yet because this code is quite complex.