Repository: celery/celery

Issue:
Group of Chains Exception/Error Propagation
## Checklist
-  I'm running Celery 4.0.2

## Steps to reproduce
tasks.py
```
@shared_task
def add(x,y):
    return x+y

@shared_task
def raising_exception():
    raise RuntimeError("BLAH")
```
test.py
```
from celery import chord, group
from my_app.tasks import xsum, raising_exception

tasks = group(add.si(1,2), raising_exception.si() | add.si(1,1) )
result = tasks.apply_async()

try:
    result.get()
except Exception as ex:
    raise ex
```
Run the following:
celery worker -A grizzly_project -c 8 &
python manage.py runserver

then in another terminal, run:
python test.py
## Expected behavior
When the "raising_exception" task fails, result.get() should propagate one task back in the chain from the last task (for the second part of the group) to receive the exception: " RuntimeError: BLAH" thus allowing me to catch the Exception similar to below:
```
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    raise ex
celery.backends.base.RuntimeError: BLAH
```
This is the expected behaviour, according to the back propagation of errors in a chain that was implemented in the closing of Issue [1014](https://github.com/celery/celery/issues/1014).

## Actual behavior
Rather, when the result.get() statement executes, the program hangs. 

When I change the group statement from the above code to this such that the failing task is the last to execute:
```
tasks = group(add.si(1,2), add.si(1,1) | raising_exception.si() )
```
...the result.get() doesn't hang but rather the Exception is caught, as expected 

Also, if i simply make it just a chain (rather than a group of chains) with the failing task first:
```
tasks = raising_exception.si() | add.si(1,1)
```
...result.get() doesn't hang but accurately catches the Exception (granted a wait a second or two before executing result.get())



Discussion:
I would just like to throw my hat into the ring here because I'm encountering the same issue. So long as there are no errors, everything works as expected, but with errors, indefinite hang. 

I'm experiencing this on Celery 4.2.1 w/ Redis as a broker.
Does anyone have a workaround for this?  Without this, there is no way to do exception handling in canvas and we have to catch all exceptions and propagate them in the results instead.
I'm having the same problem given the test provided above using the following setup (master):

```
software -> celery:4.2.0 (windowlicker) kombu:4.2.2-post1 py:3.6.7
            billiard:3.5.0.5 py-amqp:2.4.0
platform -> system:Linux arch:64bit, ELF
            kernel version:4.9.125-linuxkit imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:pyamqp results:redis://redis/

broker_url: 'amqp://guest:********@rabbit:5672//'
result_backend: 'redis://redis/'
```
Same here with celery 4.3.0, I would like to add that this is really a critical issue.
I don't understand how is possible given that it should have already been fixed here https://github.com/celery/celery/commit/e043b82827874ed1c904e47b8dc964729b40442a
could you try that patch locally?
What patch? that commit is already on master

Resolving patch:
diff --git a/celery/result.py b/celery/result.py
--- a/celery/result.py
+++ b/celery/result.py
@@ -205,7 +205,7 @@ def get(self, timeout=None, propagate=True, interval=0.5,
             assert_will_not_block()
         _on_interval = promise()
         if follow_parents and propagate and self.parent:
-            on_interval = promise(self._maybe_reraise_parent_error, weak=True)
+            _on_interval = promise(self._maybe_reraise_parent_error, weak=True)
             self._maybe_reraise_parent_error()
         if on_interval:
             _on_interval.then(on_interval)


Test patch:
diff --git a/t/integration/test_canvas.py b/t/integration/test_canvas.py
--- a/t/integration/test_canvas.py
+++ b/t/integration/test_canvas.py
@@ -55,6 +55,27 @@ def test_group_results_in_chain(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == [4, 5]
 
+
+    def test_chain_on_error(self, manager):
+        from celery import states
+        from .tasks import ExpectedException
+        import time
+
+        if not manager.app.conf.result_backend.startswith('redis'):
+            raise pytest.skip('Requires redis result backend.')
+
+        # Run the chord and wait for the error callback to finish.
+        c1 = chain(
+            add.s(1, 2), fail.s(), add.s(3, 4),
+        )
+        res = c1()
+
+        with pytest.raises(ExpectedException):
+            res.get(propagate=True)
+
+        with pytest.raises(ExpectedException):
+            res.parent.get(propagate=True)
+
     @flaky
     def test_chain_inside_group_receives_arguments(self, manager):
         c = (
@@ -562,17 +583,13 @@ def test_chord_on_error(self, manager):
                 chord_error.s()),
         )
         res = c1()
-        try:
+        with pytest.raises(ExpectedException):
             res.wait(propagate=False)
-        except ExpectedException:
-            pass
         # Got to wait for children to populate.
         while not res.children:
             time.sleep(0.1)
-        try:
+        with pytest.raises(ExpectedException):
             res.children[0].children[0].wait(propagate=False)
-        except ExpectedException:
-            pass
 
         # Extract the results of the successful tasks from the chord.
         #