issue #477: make CallError serializable on 2.4.

Making CallError inherit from object broke 'raise CallError()'.

Instead use pure-Python pickler on 2.4 (grmbl) and force it to emit
new-style-alike output for what is otherwise a classic class.

Remove needless complexity from _unpickle_call_error() that only worked
for new-style classes.
issue510
David Wilson 7 years ago
parent 4b89dc4813
commit 1f17422598

@ -38,6 +38,7 @@ import errno
import fcntl import fcntl
import itertools import itertools
import logging import logging
import pickle as py_pickle
import os import os
import signal import signal
import socket import socket
@ -267,7 +268,7 @@ class Kwargs(dict):
return (Kwargs, (dict(self),)) return (Kwargs, (dict(self),))
class CallError(Error, object): class CallError(Error):
""" """
Serializable :class:`Error` subclass raised when :meth:`Context.call() Serializable :class:`Error` subclass raised when :meth:`Context.call()
<mitogen.parent.Context.call>` fails. A copy of the traceback from the <mitogen.parent.Context.call>` fails. A copy of the traceback from the
@ -295,9 +296,7 @@ class CallError(Error, object):
def _unpickle_call_error(s): def _unpickle_call_error(s):
if not (type(s) is UnicodeType and len(s) < 10000): if not (type(s) is UnicodeType and len(s) < 10000):
raise TypeError('cannot unpickle CallError: bad input') raise TypeError('cannot unpickle CallError: bad input')
inst = CallError.__new__(CallError) return CallError(s)
Exception.__init__(inst, s)
return inst
class ChannelError(Error): class ChannelError(Error):
@ -554,13 +553,48 @@ def import_module(modname):
return __import__(modname, None, None, ['']) return __import__(modname, None, None, [''])
class Py24Pickler(py_pickle.Pickler):
"""
Exceptions were "classic" classes until Python 2.5. Sadly for 2.4, cPickle
has no sensible override to control how an object is reified. Therefore for
2.4 we use the pure-Python pickler, so CallError can be made to look as it
does on newer Pythons.
This mess will go away once proper serialization exists.
"""
@classmethod
def dumps(cls, obj, protocol):
bio = BytesIO()
self = cls(bio, protocol=protocol)
self.dump(obj)
return bio.getvalue()
def save_exc_inst(self, obj):
if isinstance(obj, CallError):
func, args = obj.__reduce__()
self.save(func)
self.save(args)
self.write(py_pickle.REDUCE)
else:
py_pickle.Pickler.save_inst(self, obj)
dispatch = py_pickle.Pickler.dispatch.copy()
dispatch[py_pickle.InstanceType] = save_exc_inst
if PY3: if PY3:
# In 3.x Unpickler is a class exposing find_class as an overridable, but it # In 3.x Unpickler is a class exposing find_class as an overridable, but it
# cannot be overridden without subclassing. # cannot be overridden without subclassing.
class _Unpickler(pickle.Unpickler): class _Unpickler(pickle.Unpickler):
def find_class(self, module, func): def find_class(self, module, func):
return self.find_global(module, func) return self.find_global(module, func)
pickle__dumps = pickle.dumps
elif sys.version_info < (2, 5):
# On Python 2.4, we must use a pure-Python pickler.
pickle__dumps = Py24Pickler.dumps
_Unpickler = pickle.Unpickler
else: else:
pickle__dumps = pickle.dumps
# In 2.x Unpickler is a function exposing a writeable find_global # In 2.x Unpickler is a function exposing a writeable find_global
# attribute. # attribute.
_Unpickler = pickle.Unpickler _Unpickler = pickle.Unpickler
@ -633,7 +667,7 @@ class Message(object):
"""Return the class implementing `module_name.class_name` or raise """Return the class implementing `module_name.class_name` or raise
`StreamError` if the module is not whitelisted.""" `StreamError` if the module is not whitelisted."""
if module == __name__: if module == __name__:
if func == '_unpickle_call_error': if func == '_unpickle_call_error' or func == 'CallError':
return _unpickle_call_error return _unpickle_call_error
elif func == '_unpickle_sender': elif func == '_unpickle_sender':
return self._unpickle_sender return self._unpickle_sender
@ -680,10 +714,10 @@ class Message(object):
""" """
self = cls(**kwargs) self = cls(**kwargs)
try: try:
self.data = pickle.dumps(obj, protocol=2) self.data = pickle__dumps(obj, protocol=2)
except pickle.PicklingError: except pickle.PicklingError:
e = sys.exc_info()[1] e = sys.exc_info()[1]
self.data = pickle.dumps(CallError(e), protocol=2) self.data = pickle__dumps(CallError(e), protocol=2)
return self return self
def reply(self, msg, router=None, **kwargs): def reply(self, msg, router=None, **kwargs):

Loading…
Cancel
Save