Repository: celery/celery

Issue:
ResultSet incorrectly calls backend.remove_pending_result
The master branch currently defines `ResultSet` with:

    class ResultSet(...):
        ...
        def _on_ready(self):
            self.backend.remove_pending_result(self)
            if self.backend.is_async:
                self._cache = [r.get() for r in self.results]
                self.on_ready()

However a typical implemenation of `backend.remove_pending_result` in `celery.backends.async` will assume that the value has an `id`

        def remove_pending_result(self, result):
            self._remove_pending_result(result.id)
            self.on_result_fulfilled(result)
            return result

Presumably, this call to `self.backend.remove_pending_result(self)` is really concerned with deregistering groups from the backend, in which case it should appear in the `GroupResult._on_ready`

This problem was exposed by #4131.  Prior to that, `_on_ready` was not called (which as a bug).

ResultSet incorrectly calls backend.remove_pending_result
The master branch currently defines `ResultSet` with:

    class ResultSet(...):
        ...
        def _on_ready(self):
            self.backend.remove_pending_result(self)
            if self.backend.is_async:
                self._cache = [r.get() for r in self.results]
                self.on_ready()

However a typical implemenation of `backend.remove_pending_result` in `celery.backends.async` will assume that the value has an `id`

        def remove_pending_result(self, result):
            self._remove_pending_result(result.id)
            self.on_result_fulfilled(result)
            return result

Presumably, this call to `self.backend.remove_pending_result(self)` is really concerned with deregistering groups from the backend, in which case it should appear in the `GroupResult._on_ready`

This problem was exposed by #4131.  Prior to that, `_on_ready` was not called (which as a bug).



Discussion:

Resolving patch:
diff --git a/celery/result.py b/celery/result.py
--- a/celery/result.py
+++ b/celery/result.py
@@ -487,7 +487,6 @@ def add(self, result):
                 self._on_full.add(result)
 
     def _on_ready(self):
-        self.backend.remove_pending_result(self)
         if self.backend.is_async:
             self._cache = [r.get() for r in self.results]
             self.on_ready()
@@ -845,6 +844,10 @@ def __init__(self, id=None, results=None, parent=None, **kwargs):
         self.parent = parent
         ResultSet.__init__(self, results, **kwargs)
 
+    def _on_ready(self):
+        self.backend.remove_pending_result(self)
+        ResultSet._on_ready(self)
+
     def save(self, backend=None):
         """Save group-result for later retrieval using :meth:`restore`.
 


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
@@ -7,7 +7,7 @@
 
 from celery import chain, chord, group
 from celery.exceptions import TimeoutError
-from celery.result import AsyncResult, GroupResult
+from celery.result import AsyncResult, GroupResult, ResultSet
 
 from .conftest import flaky, get_redis_connection
 from .tasks import (add, add_chord_to_chord, add_replaced, add_to_all,
@@ -186,6 +186,16 @@ def test_chain_error_handler_with_eta(self, manager):
         assert result == 10
 
 
+class test_result_set:
+
+    @flaky
+    def test_result_set(self, manager):
+        assert manager.inspect().ping()
+
+        rs = ResultSet([add.delay(1, 1), add.delay(2, 2)])
+        assert rs.get(timeout=TIMEOUT) == [2, 4]
+
+
 class test_group:
 
     @flaky