We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Build task documentation with sphinx fails (error while formatting arguments)
## Checklist

this has been tested with both version 4.0.2 and master (8c8354f)

## Steps to reproduce

```bash
$ git clone https://github.com/inveniosoftware/invenio-indexer.git
$ cd invenio-indexer/
$ pip install -e .[all]
$ sphinx-build -qnNW docs docs/_build/html
```

You can see that `invenio-indexer` correctly implements the requirements to document a celery task:
- https://github.com/inveniosoftware/invenio-indexer/blob/master/docs/conf.py#L52
- https://github.com/inveniosoftware/invenio-indexer/blob/master/docs/api.rst#celery-tasks

## Expected behavior

It should build the documentation of the tasks. This is **working** in Celery 3.1.25.

## Actual behavior

I get the following error:

```
invenio-indexer/docs/api.rst:54: WARNING: error while formatting arguments for invenio_indexer.tasks.index_record: 'NoneType' object is not callable
```

Am I missing something? Should it work differently than Celery 3?
Request on_timeout should ignore soft time limit exception
When Request.on_timeout receive a soft timeout from billiard, it does the same as if it was receiving a hard time limit exception. This is ran by the controller.

But the task may catch this exception and eg. return (this is what soft timeout are for).

This cause:
1. the result to be saved once as an exception by the controller (on_timeout) and another time with the result returned by the task
2. the task status to be passed to failure and to success on the same manner
3. if the task is participating to a chord, the chord result counter (at least with redis) is incremented twice (instead of once), making the chord to return prematurely and eventually loose tasks…

1, 2 and 3 can leads of course to strange race conditions…

## Steps to reproduce (Illustration)

with the program in test_timeout.py:

```python
import time
import celery


app = celery.Celery('test_timeout')
app.conf.update(
    result_backend="redis://localhost/0",
    broker_url="amqp://celery:celery@localhost:5672/host",
)

@app.task(soft_time_limit=1)
def test():
    try:
        time.sleep(2)
    except Exception:
        return 1

@app.task()
def add(args):
    print("### adding", args)
    return sum(args)

@app.task()
def on_error(context, exception, traceback, **kwargs):
    print("### on_error: ", exception)

if __name__ == "__main__":
    result = celery.chord([test.s().set(link_error=on_error.s()), test.s().set(link_error=on_error.s())])(add.s())
    result.get()
```

start a worker and the program:

```
$ celery -A test_timeout worker -l WARNING
$ python3 test_timeout.py
```

## Expected behavior

add method is called with `[1, 1]` as argument and test_timeout.py return normally

## Actual behavior

The test_timeout.py fails, with
```
celery.backends.base.ChordError: Callback error: ChordError("Dependency 15109e05-da43-449f-9081-85d839ac0ef2 raised SoftTimeLimitExceeded('SoftTimeLimitExceeded(True,)',)",
```
On the worker side, the **on_error is called but the add method as well !**

```
[2017-11-29 23:07:25,538: WARNING/MainProcess] Soft time limit (1s) exceeded for test_timeout.test[15109e05-da43-449f-9081-85d839ac0ef2]
[2017-11-29 23:07:25,546: WARNING/MainProcess] ### on_error:
[2017-11-29 23:07:25,546: WARNING/MainProcess] SoftTimeLimitExceeded(True,)
[2017-11-29 23:07:25,547: WARNING/MainProcess] Soft time limit (1s) exceeded for test_timeout.test[38f3f7f2-4a89-4318-8ee9-36a987f73757]
[2017-11-29 23:07:25,553: ERROR/MainProcess] Chord callback for 'ef6d7a38-d1b4-40ad-b937-ffa84e40bb23' raised: ChordError("Dependency 15109e05-da43-449f-9081-85d839ac0ef2 raised SoftTimeLimitExceeded('SoftTimeLimitExceeded(True,)',)",)
Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/celery/backends/redis.py", line 290, in on_chord_part_return
    callback.delay([unpack(tup, decode) for tup in resl])
  File "/usr/local/lib/python3.4/dist-packages/celery/backends/redis.py", line 290, in <listcomp>
    callback.delay([unpack(tup, decode) for tup in resl])
  File "/usr/local/lib/python3.4/dist-packages/celery/backends/redis.py", line 243, in _unpack_chord_result
    raise ChordError('Dependency {0} raised {1!r}'.format(tid, retval))
celery.exceptions.ChordError: Dependency 15109e05-da43-449f-9081-85d839ac0ef2 raised SoftTimeLimitExceeded('SoftTimeLimitExceeded(True,)',)
[2017-11-29 23:07:25,565: WARNING/MainProcess] ### on_error:
[2017-11-29 23:07:25,565: WARNING/MainProcess] SoftTimeLimitExceeded(True,)
[2017-11-29 23:07:27,262: WARNING/PoolWorker-2] ### adding
[2017-11-29 23:07:27,264: WARNING/PoolWorker-2] [1, 1]
```

Of course, on purpose did I choose to call the test.s() twice, to show that the count in the chord continues. In fact:
- the chord result is incremented twice by the error of soft time limit
- the chord result is again incremented twice by the correct returning of `test` task

## Conclusion

Request.on_timeout  should not process soft time limit exception. 

here is a quick monkey patch (correction of celery is trivial)

```python
def patch_celery_request_on_timeout():
    from celery.worker import request
    orig = request.Request.on_timeout
    def patched_on_timeout(self, soft, timeout):
        if not soft:
            orig(self, soft, timeout)
    request.Request.on_timeout = patched_on_timeout
patch_celery_request_on_timeout()
```



## version info

software -> celery:4.1.0 (latentcall) kombu:4.0.2 py:3.4.3
            billiard:3.5.0.2 py-amqp:2.1.4
platform -> system:Linux arch:64bit, ELF imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:amqp results:redis://10.0.3.253/0


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `celery/contrib/sphinx.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 """Sphinx documentation plugin used to document tasks.
3 
4 Introduction
5 ============
6 
7 Usage
8 -----
9 
10 Add the extension to your :file:`docs/conf.py` configuration module:
11 
12 .. code-block:: python
13 
14     extensions = (...,
15                   'celery.contrib.sphinx')
16 
17 If you'd like to change the prefix for tasks in reference documentation
18 then you can change the ``celery_task_prefix`` configuration value:
19 
20 .. code-block:: python
21 
22     celery_task_prefix = '(task)'  # < default
23 
24 With the extension installed `autodoc` will automatically find
25 task decorated objects and generate the correct (as well as
26 add a ``(task)`` prefix), and you can also refer to the tasks
27 using `:task:proj.tasks.add` syntax.
28 
29 Use ``.. autotask::`` to manually document a task.
30 """
31 from __future__ import absolute_import, unicode_literals
32 from inspect import formatargspec
33 from sphinx.domains.python import PyModulelevel
34 from sphinx.ext.autodoc import FunctionDocumenter
35 from celery.app.task import BaseTask
36 from celery.five import getfullargspec
37 
38 
39 class TaskDocumenter(FunctionDocumenter):
40     """Document task definitions."""
41 
42     objtype = 'task'
43     member_order = 11
44 
45     @classmethod
46     def can_document_member(cls, member, membername, isattr, parent):
47         return isinstance(member, BaseTask) and getattr(member, '__wrapped__')
48 
49     def format_args(self):
50         wrapped = getattr(self.object, '__wrapped__', None)
51         if wrapped is not None:
52             argspec = getfullargspec(wrapped)
53             fmt = formatargspec(*argspec)
54             fmt = fmt.replace('\\', '\\\\')
55             return fmt
56         return ''
57 
58     def document_members(self, all_members=False):
59         pass
60 
61 
62 class TaskDirective(PyModulelevel):
63     """Sphinx task directive."""
64 
65     def get_signature_prefix(self, sig):
66         return self.env.config.celery_task_prefix
67 
68 
69 def setup(app):
70     """Setup Sphinx extension."""
71     app.add_autodocumenter(TaskDocumenter)
72     app.add_directive_to_domain('py', 'task', TaskDirective)
73     app.add_config_value('celery_task_prefix', '(task)', True)
74
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/celery/contrib/sphinx.py b/celery/contrib/sphinx.py
--- a/celery/contrib/sphinx.py
+++ b/celery/contrib/sphinx.py
@@ -29,11 +29,13 @@
 Use ``.. autotask::`` to manually document a task.
 """
 from __future__ import absolute_import, unicode_literals
-from inspect import formatargspec
 from sphinx.domains.python import PyModulelevel
 from sphinx.ext.autodoc import FunctionDocumenter
 from celery.app.task import BaseTask
-from celery.five import getfullargspec
+try:  # pragma: no cover
+    from inspect import formatargspec, getfullargspec
+except ImportError:  # Py2
+    from inspect import formatargspec, getargspec as getfullargspec  # noqa
 
 
 class TaskDocumenter(FunctionDocumenter):

{"golden_diff": "diff --git a/celery/contrib/sphinx.py b/celery/contrib/sphinx.py\n--- a/celery/contrib/sphinx.py\n+++ b/celery/contrib/sphinx.py\n@@ -29,11 +29,13 @@\n Use ``.. autotask::`` to manually document a task.\n \"\"\"\n from __future__ import absolute_import, unicode_literals\n-from inspect import formatargspec\n from sphinx.domains.python import PyModulelevel\n from sphinx.ext.autodoc import FunctionDocumenter\n from celery.app.task import BaseTask\n-from celery.five import getfullargspec\n+try:  # pragma: no cover\n+    from inspect import formatargspec, getfullargspec\n+except ImportError:  # Py2\n+    from inspect import formatargspec, getargspec as getfullargspec  # noqa\n \n \n class TaskDocumenter(FunctionDocumenter):\n", "issue": "Build task documentation with sphinx fails (error while formatting arguments)\n## Checklist\r\n\r\nthis has been tested with both version 4.0.2 and master (8c8354f)\r\n\r\n## Steps to reproduce\r\n\r\n```bash\r\n$ git clone https://github.com/inveniosoftware/invenio-indexer.git\r\n$ cd invenio-indexer/\r\n$ pip install -e .[all]\r\n$ sphinx-build -qnNW docs docs/_build/html\r\n```\r\n\r\nYou can see that `invenio-indexer` correctly implements the requirements to document a celery task:\r\n- https://github.com/inveniosoftware/invenio-indexer/blob/master/docs/conf.py#L52\r\n- https://github.com/inveniosoftware/invenio-indexer/blob/master/docs/api.rst#celery-tasks\r\n\r\n## Expected behavior\r\n\r\nIt should build the documentation of the tasks. This is **working** in Celery 3.1.25.\r\n\r\n## Actual behavior\r\n\r\nI get the following error:\r\n\r\n```\r\ninvenio-indexer/docs/api.rst:54: WARNING: error while formatting arguments for invenio_indexer.tasks.index_record: 'NoneType' object is not callable\r\n```\r\n\r\nAm I missing something? Should it work differently than Celery 3?\nRequest on_timeout should ignore soft time limit exception\nWhen Request.on_timeout receive a soft timeout from billiard, it does the same as if it was receiving a hard time limit exception. This is ran by the controller.\r\n\r\nBut the task may catch this exception and eg. return (this is what soft timeout are for).\r\n\r\nThis cause:\r\n1. the result to be saved once as an exception by the controller (on_timeout) and another time with the result returned by the task\r\n2. the task status to be passed to failure and to success on the same manner\r\n3. if the task is participating to a chord, the chord result counter (at least with redis) is incremented twice (instead of once), making the chord to return prematurely and eventually loose tasks\u2026\r\n\r\n1, 2 and 3 can leads of course to strange race conditions\u2026\r\n\r\n## Steps to reproduce (Illustration)\r\n\r\nwith the program in test_timeout.py:\r\n\r\n```python\r\nimport time\r\nimport celery\r\n\r\n\r\napp = celery.Celery('test_timeout')\r\napp.conf.update(\r\n    result_backend=\"redis://localhost/0\",\r\n    broker_url=\"amqp://celery:celery@localhost:5672/host\",\r\n)\r\n\r\n@app.task(soft_time_limit=1)\r\ndef test():\r\n    try:\r\n        time.sleep(2)\r\n    except Exception:\r\n        return 1\r\n\r\n@app.task()\r\ndef add(args):\r\n    print(\"### adding\", args)\r\n    return sum(args)\r\n\r\n@app.task()\r\ndef on_error(context, exception, traceback, **kwargs):\r\n    print(\"### on_error:\u00a0\", exception)\r\n\r\nif __name__ == \"__main__\":\r\n    result = celery.chord([test.s().set(link_error=on_error.s()), test.s().set(link_error=on_error.s())])(add.s())\r\n    result.get()\r\n```\r\n\r\nstart a worker and the program:\r\n\r\n```\r\n$ celery -A test_timeout worker -l WARNING\r\n$ python3 test_timeout.py\r\n```\r\n\r\n## Expected behavior\r\n\r\nadd method is called with `[1, 1]` as argument and test_timeout.py return normally\r\n\r\n## Actual behavior\r\n\r\nThe test_timeout.py fails, with\r\n```\r\ncelery.backends.base.ChordError: Callback error: ChordError(\"Dependency 15109e05-da43-449f-9081-85d839ac0ef2 raised SoftTimeLimitExceeded('SoftTimeLimitExceeded(True,)',)\",\r\n```\r\nOn the worker side, the **on_error is called but the add method as well !**\r\n\r\n```\r\n[2017-11-29 23:07:25,538: WARNING/MainProcess] Soft time limit (1s) exceeded for test_timeout.test[15109e05-da43-449f-9081-85d839ac0ef2]\r\n[2017-11-29 23:07:25,546: WARNING/MainProcess] ### on_error:\r\n[2017-11-29 23:07:25,546: WARNING/MainProcess] SoftTimeLimitExceeded(True,)\r\n[2017-11-29 23:07:25,547: WARNING/MainProcess] Soft time limit (1s) exceeded for test_timeout.test[38f3f7f2-4a89-4318-8ee9-36a987f73757]\r\n[2017-11-29 23:07:25,553: ERROR/MainProcess] Chord callback for 'ef6d7a38-d1b4-40ad-b937-ffa84e40bb23' raised: ChordError(\"Dependency 15109e05-da43-449f-9081-85d839ac0ef2 raised SoftTimeLimitExceeded('SoftTimeLimitExceeded(True,)',)\",)\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.4/dist-packages/celery/backends/redis.py\", line 290, in on_chord_part_return\r\n    callback.delay([unpack(tup, decode) for tup in resl])\r\n  File \"/usr/local/lib/python3.4/dist-packages/celery/backends/redis.py\", line 290, in <listcomp>\r\n    callback.delay([unpack(tup, decode) for tup in resl])\r\n  File \"/usr/local/lib/python3.4/dist-packages/celery/backends/redis.py\", line 243, in _unpack_chord_result\r\n    raise ChordError('Dependency {0} raised {1!r}'.format(tid, retval))\r\ncelery.exceptions.ChordError: Dependency 15109e05-da43-449f-9081-85d839ac0ef2 raised SoftTimeLimitExceeded('SoftTimeLimitExceeded(True,)',)\r\n[2017-11-29 23:07:25,565: WARNING/MainProcess] ### on_error:\r\n[2017-11-29 23:07:25,565: WARNING/MainProcess] SoftTimeLimitExceeded(True,)\r\n[2017-11-29 23:07:27,262: WARNING/PoolWorker-2] ### adding\r\n[2017-11-29 23:07:27,264: WARNING/PoolWorker-2] [1, 1]\r\n```\r\n\r\nOf course, on purpose did I choose to call the test.s() twice, to show that the count in the chord continues. In fact:\r\n- the chord result is incremented twice by the error of soft time limit\r\n- the chord result is again incremented twice by the correct returning of `test` task\r\n\r\n## Conclusion\r\n\r\nRequest.on_timeout  should not process soft time limit exception. \r\n\r\nhere is a quick monkey patch (correction of celery is trivial)\r\n\r\n```python\r\ndef patch_celery_request_on_timeout():\r\n    from celery.worker import request\r\n    orig = request.Request.on_timeout\r\n    def patched_on_timeout(self, soft, timeout):\r\n        if not soft:\r\n            orig(self, soft, timeout)\r\n    request.Request.on_timeout = patched_on_timeout\r\npatch_celery_request_on_timeout()\r\n```\r\n\r\n\r\n\r\n## version info\r\n\r\nsoftware -> celery:4.1.0 (latentcall) kombu:4.0.2 py:3.4.3\r\n            billiard:3.5.0.2 py-amqp:2.1.4\r\nplatform -> system:Linux arch:64bit, ELF imp:CPython\r\nloader   -> celery.loaders.app.AppLoader\r\nsettings -> transport:amqp results:redis://10.0.3.253/0\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"Sphinx documentation plugin used to document tasks.\n\nIntroduction\n============\n\nUsage\n-----\n\nAdd the extension to your :file:`docs/conf.py` configuration module:\n\n.. code-block:: python\n\n    extensions = (...,\n                  'celery.contrib.sphinx')\n\nIf you'd like to change the prefix for tasks in reference documentation\nthen you can change the ``celery_task_prefix`` configuration value:\n\n.. code-block:: python\n\n    celery_task_prefix = '(task)'  # < default\n\nWith the extension installed `autodoc` will automatically find\ntask decorated objects and generate the correct (as well as\nadd a ``(task)`` prefix), and you can also refer to the tasks\nusing `:task:proj.tasks.add` syntax.\n\nUse ``.. autotask::`` to manually document a task.\n\"\"\"\nfrom __future__ import absolute_import, unicode_literals\nfrom inspect import formatargspec\nfrom sphinx.domains.python import PyModulelevel\nfrom sphinx.ext.autodoc import FunctionDocumenter\nfrom celery.app.task import BaseTask\nfrom celery.five import getfullargspec\n\n\nclass TaskDocumenter(FunctionDocumenter):\n    \"\"\"Document task definitions.\"\"\"\n\n    objtype = 'task'\n    member_order = 11\n\n    @classmethod\n    def can_document_member(cls, member, membername, isattr, parent):\n        return isinstance(member, BaseTask) and getattr(member, '__wrapped__')\n\n    def format_args(self):\n        wrapped = getattr(self.object, '__wrapped__', None)\n        if wrapped is not None:\n            argspec = getfullargspec(wrapped)\n            fmt = formatargspec(*argspec)\n            fmt = fmt.replace('\\\\', '\\\\\\\\')\n            return fmt\n        return ''\n\n    def document_members(self, all_members=False):\n        pass\n\n\nclass TaskDirective(PyModulelevel):\n    \"\"\"Sphinx task directive.\"\"\"\n\n    def get_signature_prefix(self, sig):\n        return self.env.config.celery_task_prefix\n\n\ndef setup(app):\n    \"\"\"Setup Sphinx extension.\"\"\"\n    app.add_autodocumenter(TaskDocumenter)\n    app.add_directive_to_domain('py', 'task', TaskDirective)\n    app.add_config_value('celery_task_prefix', '(task)', True)\n", "path": "celery/contrib/sphinx.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"Sphinx documentation plugin used to document tasks.\n\nIntroduction\n============\n\nUsage\n-----\n\nAdd the extension to your :file:`docs/conf.py` configuration module:\n\n.. code-block:: python\n\n    extensions = (...,\n                  'celery.contrib.sphinx')\n\nIf you'd like to change the prefix for tasks in reference documentation\nthen you can change the ``celery_task_prefix`` configuration value:\n\n.. code-block:: python\n\n    celery_task_prefix = '(task)'  # < default\n\nWith the extension installed `autodoc` will automatically find\ntask decorated objects and generate the correct (as well as\nadd a ``(task)`` prefix), and you can also refer to the tasks\nusing `:task:proj.tasks.add` syntax.\n\nUse ``.. autotask::`` to manually document a task.\n\"\"\"\nfrom __future__ import absolute_import, unicode_literals\nfrom sphinx.domains.python import PyModulelevel\nfrom sphinx.ext.autodoc import FunctionDocumenter\nfrom celery.app.task import BaseTask\ntry:  # pragma: no cover\n    from inspect import formatargspec, getfullargspec\nexcept ImportError:  # Py2\n    from inspect import formatargspec, getargspec as getfullargspec  # noqa\n\n\nclass TaskDocumenter(FunctionDocumenter):\n    \"\"\"Document task definitions.\"\"\"\n\n    objtype = 'task'\n    member_order = 11\n\n    @classmethod\n    def can_document_member(cls, member, membername, isattr, parent):\n        return isinstance(member, BaseTask) and getattr(member, '__wrapped__')\n\n    def format_args(self):\n        wrapped = getattr(self.object, '__wrapped__', None)\n        if wrapped is not None:\n            argspec = getfullargspec(wrapped)\n            fmt = formatargspec(*argspec)\n            fmt = fmt.replace('\\\\', '\\\\\\\\')\n            return fmt\n        return ''\n\n    def document_members(self, all_members=False):\n        pass\n\n\nclass TaskDirective(PyModulelevel):\n    \"\"\"Sphinx task directive.\"\"\"\n\n    def get_signature_prefix(self, sig):\n        return self.env.config.celery_task_prefix\n\n\ndef setup(app):\n    \"\"\"Setup Sphinx extension.\"\"\"\n    app.add_autodocumenter(TaskDocumenter)\n    app.add_directive_to_domain('py', 'task', TaskDirective)\n    app.add_config_value('celery_task_prefix', '(task)', True)\n", "path": "celery/contrib/sphinx.py"}]}