ClientConnectionError exception doesn't serialize propertly
To be able to add ClientConnectError to a queue and get it back.
AttributeError: 'str' object has no attribute 'errno'
A similar issue is reproducible using the following code snippet:
import aiohttp
import pickle
connection_key = aiohttp.client_reqrep.ConnectionKey
ose = OSError(1, 'unittest')
cce = aiohttp.client_exceptions.ClientConnectorError(connection_key, ose)
cce_pickled = pickle.dumps(cce)
pickle.loads(cce_pickled)
Result:
Traceback (most recent call last):
File "/opt/.pycharm_helpers/pydev/pydevd.py", line 1758, in <module>
main()
File "/opt/.pycharm_helpers/pydev/pydevd.py", line 1752, in main
globals = debugger.run(setup['file'], None, None, is_module)
File "/opt/.pycharm_helpers/pydev/pydevd.py", line 1147, in run
pydev_imports.execfile(file, globals, locals) # execute the script
File "/opt/.pycharm_helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/project_neural_mouse/src/ARCHIVE/scratch/scratch_13.py", line 11, in <module>
File "/usr/local/lib/python3.6/dist-packages/aiohttp/client_exceptions.py", line 133, in __init__
super().__init__(os_error.errno, os_error.strerror)
AttributeError: 'str' object has no attribute 'errno'
Python 3.6.8, aiohttp.__version__ == 3.6.0
This issue is posted on StackOverflow here:
I placed a ClientConnectionError exception in a multiprocessing.Queue that was generated by asyncio. I did this to pass an exception generated in asyncio land back to a client in another thread/process.
This exception appears to occurred during the deserialization process reading the exception out of the queue. It looks pretty much impossible to reach otherwise.
Traceback (most recent call last):
File "model_neural_simplified.py", line 318, in <module>
main(**arg_parser())
File "model_neural_simplified.py", line 314, in main
globals()[command](**kwargs)
File "model_neural_simplified.py", line 304, in predict
next_neural_data, next_sample = reader.get_next_result()
File "/project_neural_mouse/src/asyncs3/s3reader.py", line 174, in get_next_result
result = future.result()
File "/usr/lib/python3.6/concurrent/futures/_base.py", line 432, in result
return self.__get_result()
File "/usr/lib/python3.6/concurrent/futures/_base.py", line 384, in __get_result
raise self._exception
File "/usr/lib/python3.6/concurrent/futures/thread.py", line 56, in run
result = self.fn(*self.args, **self.kwargs)
File "model_neural_simplified.py", line 245, in read_sample
f_bytes = s3f.read(read_size)
File "/project_neural_mouse/src/asyncs3/s3reader.py", line 374, in read
size, b = self._issue_request(S3Reader.READ, (self.url, size, self.position))
File "/project_neural_mouse/src/asyncs3/s3reader.py", line 389, in _issue_request
response = self.communication_channels[uuid].get()
File "/usr/lib/python3.6/multiprocessing/queues.py", line 113, in get
return _ForkingPickler.loads(res)
File "/usr/local/lib/python3.6/dist-packages/aiohttp/client_exceptions.py", line 133, in __init__
super().__init__(os_error.errno, os_error.strerror)
AttributeError: 'str' object has no attribute 'errno'
The minimally reproducible example posted above generates a similar error as the one shown here that I experienced in practice.
Pull request is welcome!
I updated the minimum reproducible test case based on some comments over at stack overflow. But in looking at it I'm not clear if this is an aiohttp bug or a pickle bug (I'm no pickle expert). I don't actually see any obvious bug in the aiohttp code. It seems like it should work based on the rules of python as far as I'm familiar.
Before pickling ClientConnectionError._os_error is a PermissionError object (a subclass of OSError), not a string. After pickling it appears that this gets converted to a string instead of back to a PermissionError.
I think the right course of action is to throw it over the fence to the pickle maintainers. Does that seem correct?
I created the issue over at bugs.python.org, we'll see what happens there.
Indeed xtreak at bugs.pythong.org posted a pickle only test case that reproduces this.
import pickle
ose = OSError(1, 'unittest')
class SubOSError(OSError):
def __init__(self, foo, os_error):
super().__init__(os_error.errno, os_error.strerror)
cce = SubOSError(1, ose)
cce_pickled = pickle.dumps(cce)
pickle.loads(cce_pickled)
./python.exe ../backups/bpo38254.py
Traceback (most recent call last):
File "/Users/karthikeyansingaravelan/stuff/python/cpython/../backups/bpo38254.py", line 12, in <module>
pickle.loads(cce_pickled)
File "/Users/karthikeyansingaravelan/stuff/python/cpython/../backups/bpo38254.py", line 8, in __init__
super().__init__(os_error.errno, os_error.strerror)
AttributeError: 'str' object has no attribute 'errno'
Update: The open question on stack overflow received an answer and solution to the problem. It was a subtlety of OSError which implements a custom __reduce__ function for serialization. When the signature of a descendant class changes it must also implement __reduce__ to provide custom serialization.
Ooof, that was tricky. But now that it's understood, and it's fixable in aiohttp, I've closed the bugs.pythong.org issue and I'll see about sending a pull request later this week probably.
I am already working on this issue since yesterday.
There are many exception classes with custom constructor in in aiohttp, and they are organized in complex hierarhies. There is more than one way to solve this issue. Different ways can be more appropriate in every particular case.
args. But it affects also __str__ and __repr__.__reduce__. It is not easy to make it subclass friendly.__reduce__. Needs to implement __getnewargs__ or __getstate__ to ignore internal state of BaseException.Excellent, I'll leave it to you then. Thanks @serhiy-storchaka !
Most helpful comment
I am already working on this issue since yesterday.
There are many exception classes with custom constructor in in
aiohttp, and they are organized in complex hierarhies. There is more than one way to solve this issue. Different ways can be more appropriate in every particular case.args. But it affects also__str__and__repr__.__reduce__. It is not easy to make it subclass friendly.__reduce__. Needs to implement__getnewargs__or__getstate__to ignore internal state ofBaseException.