You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
unpickling an xmlrpc.client.Fault raises TypeError #71468
Attempting to unpickle an xmlrpc.client.Fault will raise a TypeError:
>>> import xmlrpc.client as xmlrpclib
>>> f = xmlrpclib.Fault(42, 'Test Fault')
>>> import pickle
>>> s = pickle.dumps(f)
>>> pickle.loads(s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() missing 2 required positional arguments: 'faultCode' and 'faultString'
Apparently unpickle relies on BaseException's args attribute when reconstructing an Exception class that inherits from BaseException (Fault inherits from Exception). Unfortunately, Fault implements __init__() and does call the base class constructor, but does not pass its arguments, so Fault.args is always an empty tuple.
Simply passing Fault's arguments to the base class constructor allows it to be unpickled.
I've included a patch for 3.x but the issue is present in 2.x as well (the code and fix are exactly the same except in xmlrpclib.py).
I'm not pickling/unpickling it directly, I'm using multiprocessing to handle queries to my server in worker processes which is using pickle to propagate exceptions raised in the worker to the parent.
I could instead raise a different exception and wrap it in a Fault later (which is what I ended up doing to avoid the issue), but it seems like a case of "to-may-to" vs. "to-mah-to". Either way an Exception should be able to be propagated up to the parent in multiprocessing, whether it's my own or Fault.
But since Fault never was unpickleable, this issue is in gray zone between bug fixing and adding new feature. The latter can be done only in new Python version. I hesitate with classification of this issue.
There are a lot of other exceptions that can't be unpickled (including xmlrpc.client.ProtocolError). This may be a part of larger issue.
As for the patch itself, it solves the issue. But faultCode and faultString are saved twice (this is not a large problem for multiprocessing). Test should test pickling with all possible protocols (see how other pickle tests are written).
UriOkrent commented on Jun 9, 2016
UriOkrentmannequin
MannequinAuthor
More actions
My reading of the docs leads me to lean towards bug since this seems to break the contract of BaseException API in a standard lib module:
"In this example, the default __init__() of Exception has been overridden. The new behavior simply creates the value attribute. This replaces the default behavior of creating the args attribute."
The tutorial goes out of its way to note that the default behavior of creating args has been replaced in this simple example -- i.e., one would expect args to be present.
As for your other comments, should I make a similar change to other inheritors of xmlrpc.client that inherit from xmlrpc.client.Error? Should I instead completely remove Fault's constructor and just make faultCode and faultString properties so the attributes aren't duplicated (that seems like it may be dangerous)?
I will update the test for pickling with the other protocols.
UriOkrent commented on Jun 9, 2016
UriOkrentmannequin
MannequinAuthor
More actions
I updated the patch to improve the tests and made the same change to xmlrpc.client.ProtocolError (which was the only other place in xmlrpc.client). I'll let you decide if this patch is better despite the fact that it suffers from the same data duplication as Fault.
If not, we can go back to just fixing Fault, or come up with a fancier solution entirely that doesn't duplicate data. Though, I hesitate to start getting too fancy for two reasons: 1) this module is old and stable, and 2) these are exception objects, so while the data duplication is ugly, the instances themselves should be short-lived.
Activity
UriOkrent commentedon Jun 9, 2016
Attempting to unpickle an xmlrpc.client.Fault will raise a TypeError:
Apparently unpickle relies on BaseException's args attribute when reconstructing an Exception class that inherits from BaseException (Fault inherits from Exception). Unfortunately, Fault implements __init__() and does call the base class constructor, but does not pass its arguments, so Fault.args is always an empty tuple.
Simply passing Fault's arguments to the base class constructor allows it to be unpickled.
I've included a patch for 3.x but the issue is present in 2.x as well (the code and fix are exactly the same except in xmlrpclib.py).
berkerpeksag commentedon Jun 9, 2016
Looks good to me, thanks.
serhiy-storchaka commentedon Jun 9, 2016
Why you need to pickle Fault?
UriOkrent commentedon Jun 9, 2016
I'm not pickling/unpickling it directly, I'm using multiprocessing to handle queries to my server in worker processes which is using pickle to propagate exceptions raised in the worker to the parent.
I could instead raise a different exception and wrap it in a Fault later (which is what I ended up doing to avoid the issue), but it seems like a case of "to-may-to" vs. "to-mah-to". Either way an Exception should be able to be propagated up to the parent in multiprocessing, whether it's my own or Fault.
serhiy-storchaka commentedon Jun 9, 2016
This looks reasonable.
But since Fault never was unpickleable, this issue is in gray zone between bug fixing and adding new feature. The latter can be done only in new Python version. I hesitate with classification of this issue.
There are a lot of other exceptions that can't be unpickled (including xmlrpc.client.ProtocolError). This may be a part of larger issue.
As for the patch itself, it solves the issue. But faultCode and faultString are saved twice (this is not a large problem for multiprocessing). Test should test pickling with all possible protocols (see how other pickle tests are written).
UriOkrent commentedon Jun 9, 2016
My reading of the docs leads me to lean towards bug since this seems to break the contract of BaseException API in a standard lib module:
https://docs.python.org/3/library/exceptions.html#BaseException says BaseExceptions have args and with_traceback so those probably should be preserved in all standard lib inheritors.
This excerpt from the tutorial also seems to imply the same:
https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions
"In this example, the default __init__() of Exception has been overridden. The new behavior simply creates the value attribute. This replaces the default behavior of creating the args attribute."
The tutorial goes out of its way to note that the default behavior of creating args has been replaced in this simple example -- i.e., one would expect args to be present.
As for your other comments, should I make a similar change to other inheritors of xmlrpc.client that inherit from xmlrpc.client.Error? Should I instead completely remove Fault's constructor and just make faultCode and faultString properties so the attributes aren't duplicated (that seems like it may be dangerous)?
I will update the test for pickling with the other protocols.
UriOkrent commentedon Jun 9, 2016
I updated the patch to improve the tests and made the same change to xmlrpc.client.ProtocolError (which was the only other place in xmlrpc.client). I'll let you decide if this patch is better despite the fact that it suffers from the same data duplication as Fault.
If not, we can go back to just fixing Fault, or come up with a fancier solution entirely that doesn't duplicate data. Though, I hesitate to start getting too fancy for two reasons: 1) this module is old and stable, and 2) these are exception objects, so while the data duplication is ugly, the instances themselves should be short-lived.
iritkatriel commentedon Nov 26, 2021
Reproduced in 3.11.