Repository: celery/celery

Issue:
CELERY_RESULT_SERIALIZER = 'json' breaks Exception marshaling
Setting `CELERY_RESULT_SERIALIZER = json` and raising an exception in the worker leads to this:

```
/path/to/lib/python2.7/site-packages/celery/result.py in get(self, timeout, propagate, interval, no_ack, follow_parents, EXCEPTION_STATES, PROPAGATE_STATES)
    173             status = meta['status']
    174             if status in PROPAGATE_STATES and propagate:
--> 175                 raise meta['result']
    176             return meta['result']
    177     wait = get  # deprecated alias to :meth:`get`.

TypeError: exceptions must be old-style classes or derived from BaseException, not dict
```

where the contents of `meta['result']` are (in my case):

```
{u'exc_message': u'unknown keys: nam', u'exc_type': u'ValueError'}
```

so it _looks_ like celery could convert the dict to a real exception before raising, but it does not currently.  Changing back to `pickle` works as expected.

bug can be reproduced with the following:

``` python
# jsonresults.py
from celery.app.base import Celery

CELERY_RESULT_SERIALIZER = 'json'
CELERY_RESULT_BACKEND = 'amqp'

app = Celery(config_source=__name__)

@app.task
def hello():
    raise ValueError('go away')
```

worker:

```
# python -m celery --app=jsonresults:app worker
```

caller:

``` python
import jsonresults
jsonresults.hello.delay().get()
```



Discussion:
This is biting me as well. Any news?


Resolving patch:
diff --git a/celery/backends/amqp.py b/celery/backends/amqp.py
--- a/celery/backends/amqp.py
+++ b/celery/backends/amqp.py
@@ -195,7 +195,7 @@ def drain_events(self, connection, consumer,
 
         def callback(meta, message):
             if meta['status'] in states.READY_STATES:
-                results[meta['task_id']] = meta
+                results[meta['task_id']] = self.meta_from_decoded(meta)
 
         consumer.callbacks[:] = [callback]
         time_start = now()


Test patch:
diff --git a/celery/tests/backends/test_amqp.py b/celery/tests/backends/test_amqp.py
--- a/celery/tests/backends/test_amqp.py
+++ b/celery/tests/backends/test_amqp.py
@@ -13,6 +13,7 @@
 from celery.backends.amqp import AMQPBackend
 from celery.exceptions import TimeoutError
 from celery.five import Empty, Queue, range
+from celery.result import AsyncResult
 from celery.utils import uuid
 
 from celery.tests.case import (
@@ -246,10 +247,20 @@ def test_wait_for(self):
         with self.assertRaises(TimeoutError):
             b.wait_for(tid, timeout=0.01, cache=False)
 
-    def test_drain_events_remaining_timeouts(self):
+    def test_drain_events_decodes_exceptions_in_meta(self):
+        tid = uuid()
+        b = self.create_backend(serializer="json")
+        b.store_result(tid, RuntimeError("aap"), states.FAILURE)
+        result = AsyncResult(tid, backend=b)
 
-        class Connection(object):
+        with self.assertRaises(Exception) as cm:
+            result.get()
 
+        self.assertEqual(cm.exception.__class__.__name__, "RuntimeError")
+        self.assertEqual(str(cm.exception), "aap")
+
+    def test_drain_events_remaining_timeouts(self):
+        class Connection(object):
             def drain_events(self, timeout=None):
                 pass