Repository: celery/celery

Issue:
link_error tasks not run eagerly when using apply()
## Checklist

- [x] I have included the output of ``celery -A proj report`` in the issue.
      (if you are not able to do this, then at least specify the Celery
       version affected).
- [x] I have verified that the issue exists against the `master` branch of Celery.

Tested using the included docker setup

```
$ celery -A tasks report
No handlers could be found for logger "vagrant"

software -> celery:4.2.0 (windowlicker) kombu:4.2.1 py:2.7.14
            billiard:3.5.0.4 py-amqp:2.3.2
platform -> system:Linux arch:64bit imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:pyamqp results:redis://redis/

broker_url: u'amqp://guest:********@rabbit:5672//'
result_backend: u'redis://redis/'
```


## Steps to reproduce
Add `tasks.py`:

```python
# tasks.py

@app.task
def on_failure():
    print 'from on_failure'

@app.task
def error():
    raise ValueError('oh no!')
```
Run the tasks:
```python
>>> from tasks import error, on_failure
>>> error.apply(link_error=on_failure.si()).get()
```

## Expected behavior
Both `error` and `on_failure` to be executed eagerly.

## Actual behavior
`error` is executed eagerly and `on_failure` is executed on a worker.


Discussion:
please send a pr if you have any intended fix in mind if there isn't any open pr to fix this issue.

Resolving patch:
diff --git a/celery/backends/base.py b/celery/backends/base.py
--- a/celery/backends/base.py
+++ b/celery/backends/base.py
@@ -200,9 +200,15 @@ def _call_task_errbacks(self, request, exc, traceback):
             # need to do so if the errback only takes a single task_id arg.
             task_id = request.id
             root_id = request.root_id or task_id
-            group(old_signature, app=self.app).apply_async(
-                (task_id,), parent_id=task_id, root_id=root_id
-            )
+            g = group(old_signature, app=self.app)
+            if self.app.conf.task_always_eager or request.delivery_info.get('is_eager', False):
+                g.apply(
+                    (task_id,), parent_id=task_id, root_id=root_id
+                )
+            else:
+                g.apply_async(
+                    (task_id,), parent_id=task_id, root_id=root_id
+                )
 
     def mark_as_revoked(self, task_id, reason='',
                         request=None, store_result=True, state=states.REVOKED):
diff --git a/t/integration/conftest.py b/t/integration/conftest.py
--- a/t/integration/conftest.py
+++ b/t/integration/conftest.py
@@ -1,7 +1,6 @@
 from __future__ import absolute_import, unicode_literals
 
 import os
-from functools import wraps
 
 import pytest
 
@@ -18,25 +17,11 @@
 __all__ = (
     'celery_app',
     'celery_session_worker',
-    'flaky',
     'get_active_redis_channels',
     'get_redis_connection',
 )
 
 
-def flaky(fun):
-    @wraps(fun)
-    def _inner(*args, **kwargs):
-        for i in reversed(range(3)):
-            try:
-                return fun(*args, **kwargs)
-            except Exception:
-                if not i:
-                    raise
-    _inner.__wrapped__ = fun
-    return _inner
-
-
 def get_redis_connection():
     from redis import StrictRedis
     return StrictRedis(host=os.environ.get('REDIS_HOST'))
diff --git a/t/integration/tasks.py b/t/integration/tasks.py
--- a/t/integration/tasks.py
+++ b/t/integration/tasks.py
@@ -14,6 +14,7 @@
 
 @shared_task
 def identity(x):
+    """Return the argument."""
     return x
 
 
@@ -106,6 +107,12 @@ def print_unicode(log_message='håå®ƒ valmuefrø', print_message='hiöäüß'
     print(print_message)
 
 
+@shared_task
+def return_exception(e):
+    """Return a tuple containing the exception message and sentinel value."""
+    return e, True
+
+
 @shared_task
 def sleeping(i, **_):
     """Task sleeping for ``i`` seconds, and returning nothing."""
@@ -125,23 +132,22 @@ def collect_ids(self, res, i):
     are :task:`ids`: returns a tuple of::
 
         (previous_result, (root_id, parent_id, i))
-
     """
     return res, (self.request.root_id, self.request.parent_id, i)
 
 
 @shared_task(bind=True, expires=60.0, max_retries=1)
-def retry_once(self):
+def retry_once(self, *args, expires=60.0, max_retries=1, countdown=0.1):
     """Task that fails and is retried. Returns the number of retries."""
     if self.request.retries:
         return self.request.retries
-    raise self.retry(countdown=0.1)
+    raise self.retry(countdown=countdown,
+                     max_retries=max_retries)
 
 
 @shared_task
 def redis_echo(message):
-    """Task that appends the message to a redis list"""
-
+    """Task that appends the message to a redis list."""
     redis_connection = get_redis_connection()
     redis_connection.rpush('redis-echo', message)
 
@@ -192,12 +198,24 @@ def build_chain_inside_task(self):
 
 
 class ExpectedException(Exception):
-    pass
+    """Sentinel exception for tests."""
+
+    def __eq__(self, other):
+        return (
+            other is not None and
+            isinstance(other, ExpectedException) and
+            self.args == other.args
+        )
+
+    def __hash__(self):
+        return hash(self.args)
 
 
 @shared_task
 def fail(*args):
-    raise ExpectedException('Task expected to fail')
+    """Task that simply raises ExpectedException."""
+    args = ("Task expected to fail",) + args
+    raise ExpectedException(*args)
 
 
 @shared_task


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
@@ -4,33 +4,89 @@
 
 import pytest
 
-from celery import chain, chord, group
+from celery import chain, chord, group, signature
 from celery.exceptions import TimeoutError
 from celery.result import AsyncResult, GroupResult, ResultSet
 
-from .conftest import flaky, get_active_redis_channels, get_redis_connection
-from .tasks import (add, add_chord_to_chord, add_replaced, add_to_all,
-                    add_to_all_to_chord, build_chain_inside_task, chord_error,
-                    collect_ids, delayed_sum, delayed_sum_with_soft_guard,
-                    fail, identity, ids, print_unicode, raise_error,
-                    redis_echo, second_order_replace1, tsum, return_priority)
+from .conftest import get_active_redis_channels, get_redis_connection
+from .tasks import (ExpectedException, add, add_chord_to_chord, add_replaced,
+                    add_to_all, add_to_all_to_chord, build_chain_inside_task,
+                    chord_error, collect_ids, delayed_sum,
+                    delayed_sum_with_soft_guard, fail, identity, ids,
+                    print_unicode, raise_error, redis_echo, retry_once,
+                    return_exception, return_priority, second_order_replace1,
+                    tsum)
 
 TIMEOUT = 120
 
 
+class test_link_error:
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
+    def test_link_error_eager(self):
+        exception = ExpectedException("Task expected to fail", "test")
+        result = fail.apply(args=("test", ), link_error=return_exception.s())
+        actual = result.get(timeout=TIMEOUT, propagate=False)
+        assert actual == exception
+
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
+    def test_link_error(self):
+        exception = ExpectedException("Task expected to fail", "test")
+        result = fail.apply(args=("test", ), link_error=return_exception.s())
+        actual = result.get(timeout=TIMEOUT, propagate=False)
+        assert actual == exception
+
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
+    def test_link_error_callback_error_callback_retries_eager(self):
+        exception = ExpectedException("Task expected to fail", "test")
+        result = fail.apply(
+            args=("test", ),
+            link_error=retry_once.s(countdown=None)
+        )
+        assert result.get(timeout=TIMEOUT, propagate=False) == exception
+
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
+    def test_link_error_callback_retries(self):
+        exception = ExpectedException("Task expected to fail", "test")
+        result = fail.apply_async(
+            args=("test", ),
+            link_error=retry_once.s(countdown=None)
+        )
+        assert result.get(timeout=TIMEOUT, propagate=False) == exception
+
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
+    def test_link_error_using_signature_eager(self):
+        fail = signature('t.integration.tasks.fail', args=("test", ))
+        retrun_exception = signature('t.integration.tasks.return_exception')
+
+        fail.link_error(retrun_exception)
+
+        exception = ExpectedException("Task expected to fail", "test")
+        assert (fail.apply().get(timeout=TIMEOUT, propagate=False), True) == (exception, True)
+
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
+    def test_link_error_using_signature(self):
+        fail = signature('t.integration.tasks.fail', args=("test", ))
+        retrun_exception = signature('t.integration.tasks.return_exception')
+
+        fail.link_error(retrun_exception)
+
+        exception = ExpectedException("Task expected to fail", "test")
+        assert (fail.delay().get(timeout=TIMEOUT, propagate=False), True) == (exception, True)
+
+
 class test_chain:
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_simple_chain(self, manager):
         c = add.s(4, 4) | add.s(8) | add.s(16)
         assert c().get(timeout=TIMEOUT) == 32
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_single_chain(self, manager):
         c = chain(add.s(3, 4))()
         assert c.get(timeout=TIMEOUT) == 7
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_complex_chain(self, manager):
         c = (
             add.s(2, 2) | (
@@ -41,7 +97,7 @@ def test_complex_chain(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == [64, 65, 66, 67]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_group_results_in_chain(self, manager):
         # This adds in an explicit test for the special case added in commit
         # 1e3fcaa969de6ad32b52a3ed8e74281e5e5360e6
@@ -73,7 +129,7 @@ def test_chain_on_error(self, manager):
         with pytest.raises(ExpectedException):
             res.parent.get(propagate=True)
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_inside_group_receives_arguments(self, manager):
         c = (
             add.s(5, 6) |
@@ -82,7 +138,7 @@ def test_chain_inside_group_receives_arguments(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == [14, 14]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_eager_chain_inside_task(self, manager):
         from .tasks import chain_add
 
@@ -93,7 +149,7 @@ def test_eager_chain_inside_task(self, manager):
 
         chain_add.app.conf.task_always_eager = prev
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_group_chord_group_chain(self, manager):
         from celery.five import bytes_if_py2
 
@@ -120,7 +176,7 @@ def test_group_chord_group_chain(self, manager):
         assert set(redis_messages[4:]) == after_items
         redis_connection.delete('redis-echo')
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_group_result_not_has_cache(self, manager):
         t1 = identity.si(1)
         t2 = identity.si(2)
@@ -130,7 +186,7 @@ def test_group_result_not_has_cache(self, manager):
         result = task.delay()
         assert result.get(timeout=TIMEOUT) == [1, 2, [3, 4]]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_second_order_replace(self, manager):
         from celery.five import bytes_if_py2
 
@@ -150,7 +206,7 @@ def test_second_order_replace(self, manager):
         expected_messages = [b'In A', b'In B', b'In/Out C', b'Out B', b'Out A']
         assert redis_messages == expected_messages
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_parent_ids(self, manager, num=10):
         assert_ping(manager)
 
@@ -218,7 +274,7 @@ def test_chain_error_handler_with_eta(self, manager):
         result = c.get()
         assert result == 10
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_groupresult_serialization(self, manager):
         """Test GroupResult is correctly serialized
         to save in the result backend"""
@@ -232,7 +288,7 @@ def test_groupresult_serialization(self, manager):
         assert len(result) == 2
         assert isinstance(result[0][1], list)
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_of_task_a_group_and_a_chord(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -247,7 +303,7 @@ def test_chain_of_task_a_group_and_a_chord(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == 8
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_of_chords_as_groups_chained_to_a_task_with_two_tasks(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -264,7 +320,7 @@ def test_chain_of_chords_as_groups_chained_to_a_task_with_two_tasks(self, manage
         res = c()
         assert res.get(timeout=TIMEOUT) == 12
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_of_chords_with_two_tasks(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -280,7 +336,7 @@ def test_chain_of_chords_with_two_tasks(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == 12
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_of_a_chord_and_a_group_with_two_tasks(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -296,7 +352,7 @@ def test_chain_of_a_chord_and_a_group_with_two_tasks(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == [6, 6]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_of_a_chord_and_a_task_and_a_group(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -311,7 +367,7 @@ def test_chain_of_a_chord_and_a_task_and_a_group(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == [6, 6]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_of_a_chord_and_two_tasks_and_a_group(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -327,7 +383,7 @@ def test_chain_of_a_chord_and_two_tasks_and_a_group(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == [7, 7]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_of_a_chord_and_three_tasks_and_a_group(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -347,14 +403,14 @@ def test_chain_of_a_chord_and_three_tasks_and_a_group(self, manager):
 
 class test_result_set:
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_result_set(self, manager):
         assert_ping(manager)
 
         rs = ResultSet([add.delay(1, 1), add.delay(2, 2)])
         assert rs.get(timeout=TIMEOUT) == [2, 4]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_result_set_error(self, manager):
         assert_ping(manager)
 
@@ -366,7 +422,7 @@ def test_result_set_error(self, manager):
 
 
 class test_group:
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_ready_with_exception(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -376,7 +432,7 @@ def test_ready_with_exception(self, manager):
         while not result.ready():
             pass
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_empty_group_result(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -388,7 +444,7 @@ def test_empty_group_result(self, manager):
         task = GroupResult.restore(result.id)
         assert task.results == []
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_parent_ids(self, manager):
         assert_ping(manager)
 
@@ -408,7 +464,7 @@ def test_parent_ids(self, manager):
             assert parent_id == expected_parent_id
             assert value == i + 2
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_nested_group(self, manager):
         assert_ping(manager)
 
@@ -426,7 +482,7 @@ def test_nested_group(self, manager):
 
         assert res.get(timeout=TIMEOUT) == [11, 101, 1001, 2001]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_large_group(self, manager):
         assert_ping(manager)
 
@@ -451,8 +507,7 @@ def assert_ping(manager):
 
 
 class test_chord:
-
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_redis_subscribed_channels_leak(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -493,7 +548,7 @@ def test_redis_subscribed_channels_leak(self, manager):
         assert channels_after_count == initial_channels_count
         assert set(channels_after) == set(initial_channels)
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_replaced_nested_chord(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -513,7 +568,7 @@ def test_replaced_nested_chord(self, manager):
         res1 = c1()
         assert res1.get(timeout=TIMEOUT) == [29, 38]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_add_to_chord(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -522,7 +577,7 @@ def test_add_to_chord(self, manager):
         res = c()
         assert res.get() == [0, 5, 6, 7]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_add_chord_to_chord(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -531,7 +586,7 @@ def test_add_chord_to_chord(self, manager):
         res = c()
         assert res.get() == [0, 5 + 6 + 7]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_eager_chord_inside_task(self, manager):
         from .tasks import chord_add
 
@@ -542,7 +597,7 @@ def test_eager_chord_inside_task(self, manager):
 
         chord_add.app.conf.task_always_eager = prev
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_group_chain(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -554,7 +609,7 @@ def test_group_chain(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == [12, 13, 14, 15]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_nested_group_chain(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -580,7 +635,7 @@ def test_nested_group_chain(self, manager):
         res = c()
         assert res.get(timeout=TIMEOUT) == 11
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_single_task_header(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -609,7 +664,7 @@ def test_empty_header_chord(self, manager):
         res2 = c2()
         assert res2.get(timeout=TIMEOUT) == []
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_nested_chord(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -643,7 +698,7 @@ def test_nested_chord(self, manager):
         res = c()
         assert [[[[3, 3], 4], 5], 6] == res.get(timeout=TIMEOUT)
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_parent_ids(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -658,7 +713,7 @@ def test_parent_ids(self, manager):
         )
         self.assert_parentids_chord(g(), expected_root_id)
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_parent_ids__OR(self, manager):
         if not manager.app.conf.result_backend.startswith('redis'):
             raise pytest.skip('Requires redis result backend.')
@@ -762,7 +817,7 @@ def test_chord_on_error(self, manager):
         assert len([cr for cr in chord_results if cr[2] != states.SUCCESS]
                    ) == 1
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_parallel_chords(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -776,7 +831,7 @@ def test_parallel_chords(self, manager):
 
         assert r.get(timeout=TIMEOUT) == [10, 10]
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chord_in_chords_with_chains(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -807,7 +862,7 @@ def test_chord_in_chords_with_chains(self, manager):
 
         assert r.get(timeout=TIMEOUT) == 4
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_chord_chain_chord(self, manager):
         # test for #2573
         try:
@@ -833,7 +888,7 @@ def test_chain_chord_chain_chord(self, manager):
         res = c.delay()
         assert res.get(timeout=TIMEOUT) == 7
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_large_header(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -844,7 +899,7 @@ def test_large_header(self, manager):
         res = c.delay()
         assert res.get(timeout=TIMEOUT) == 499500
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_chain_to_a_chord_with_large_header(self, manager):
         try:
             manager.app.backend.ensure_chords_allowed()
@@ -855,12 +910,12 @@ def test_chain_to_a_chord_with_large_header(self, manager):
         res = c.delay()
         assert res.get(timeout=TIMEOUT) == 1000
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_priority(self, manager):
         c = chain(return_priority.signature(priority=3))()
         assert c.get(timeout=TIMEOUT) == "Priority: 3"
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=1)
     def test_priority_chain(self, manager):
         c = return_priority.signature(priority=3) | return_priority.signature(priority=5)
         assert c().get(timeout=TIMEOUT) == "Priority: 5"
diff --git a/t/integration/test_tasks.py b/t/integration/test_tasks.py
--- a/t/integration/test_tasks.py
+++ b/t/integration/test_tasks.py
@@ -4,24 +4,24 @@
 
 from celery import group
 
-from .conftest import flaky, get_active_redis_channels
+from .conftest import get_active_redis_channels
 from .tasks import add, add_ignore_result, print_unicode, retry_once, sleeping
 
 
 class test_tasks:
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_task_accepted(self, manager, sleep=1):
         r1 = sleeping.delay(sleep)
         sleeping.delay(sleep)
         manager.assert_accepted([r1.id])
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_task_retried(self):
         res = retry_once.delay()
         assert res.get(timeout=10) == 1  # retried once
 
-    @flaky
+    @pytest.mark.flaky(reruns=5, reruns_delay=2)
     def test_unicode_task(self, manager):
         manager.join(
             group(print_unicode.s() for _ in range(5))(),
diff --git a/t/unit/backends/test_base.py b/t/unit/backends/test_base.py
--- a/t/unit/backends/test_base.py
+++ b/t/unit/backends/test_base.py
@@ -383,10 +383,27 @@ def test_mark_as_done__chord(self):
         b.mark_as_done('id', 10, request=request)
         b.on_chord_part_return.assert_called_with(request, states.SUCCESS, 10)
 
+    def test_mark_as_failure__bound_errback_eager(self):
+        b = BaseBackend(app=self.app)
+        b._store_result = Mock()
+        request = Mock(name='request')
+        request.delivery_info = {
+            'is_eager': True
+        }
+        request.errbacks = [
+            self.bound_errback.subtask(args=[1], immutable=True)]
+        exc = KeyError()
+        group = self.patching('celery.backends.base.group')
+        b.mark_as_failure('id', exc, request=request)
+        group.assert_called_with(request.errbacks, app=self.app)
+        group.return_value.apply.assert_called_with(
+            (request.id, ), parent_id=request.id, root_id=request.root_id)
+
     def test_mark_as_failure__bound_errback(self):
         b = BaseBackend(app=self.app)
         b._store_result = Mock()
         request = Mock(name='request')
+        request.delivery_info = {}
         request.errbacks = [
             self.bound_errback.subtask(args=[1], immutable=True)]
         exc = KeyError()