Repository: celery/celery

Issue:
Request.time_limits is None
I am getting an error in the stack trace below. It looks like this line is referencing the wrong field name: https://github.com/celery/celery/blob/master/celery/worker/request.py#L520. I don't think time_limits exists for this Request. The only place in celery codebase that I see using `create_request_cls` is here https://github.com/celery/celery/blob/47ca2b462f22a8d48ed8d80c2f9bf8b9dc4a4de6/celery/worker/strategy.py#L130. It seems to be creating the Request object from task.Request base. According to the docs http://docs.celeryproject.org/en/latest/userguide/tasks.html#task-request task.Request has a field `timelimit`, not `time_limits`, hence the TypeError.  worker.Request, on the other hand, does have `time_limits` http://docs.celeryproject.org/en/latest/reference/celery.worker.request.html. 

The workaround I did to get my code working is basically to check if the variable is None here https://github.com/celery/celery/blob/master/celery/worker/request.py#L520 :
```
if not self.time_limits:
    time_limit = default_time_limit 
    soft_time_limit = default_soft_time_limit
else:
    time_limit, soft_time_limit = self.time_limits
```

I am not sure if I did something stupid and don't understand how the code is structured. Please point me at why it's not an error, if it is by design.

Stack trace:
```
[2018-07-16 06:09:46,229: CRITICAL/MainProcess] Unrecoverable error: TypeError("'NoneType' object is not iterable",)
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/celery/worker/worker.py", line 205, in start
    self.blueprint.start(self)
  File "/usr/lib/python2.7/site-packages/celery/bootsteps.py", line 119, in start
    step.start(parent)
  File "/usr/lib/python2.7/site-packages/celery/bootsteps.py", line 369, in start
    return self.obj.start()
  File "/usr/lib/python2.7/site-packages/celery/worker/consumer/consumer.py", line 322, in start
    blueprint.start(self)
  File "/usr/lib/python2.7/site-packages/celery/bootsteps.py", line 119, in start
    step.start(parent)
  File "/usr/lib/python2.7/site-packages/celery/worker/consumer/consumer.py", line 598, in start
    c.loop(*c.loop_args())
  File "/usr/lib/python2.7/site-packages/celery/worker/loops.py", line 118, in synloop
    qos.update()
  File "/usr/lib/python2.7/site-packages/kombu/common.py", line 417, in update
    return self.set(self.value)
  File "/usr/lib/python2.7/site-packages/kombu/common.py", line 410, in set
    self.callback(prefetch_count=new_value)
  File "/usr/lib/python2.7/site-packages/celery/worker/consumer/tasks.py", line 47, in set_prefetch_count
    apply_global=qos_global,
  File "/usr/lib/python2.7/site-packages/kombu/messaging.py", line 558, in qos
    apply_global)
  File "/usr/lib/python2.7/site-packages/amqp/channel.py", line 1812, in basic_qos
    wait=spec.Basic.QosOk,
  File "/usr/lib/python2.7/site-packages/amqp/abstract_channel.py", line 59, in send_method
    return self.wait(wait, returns_tuple=returns_tuple)
  File "/usr/lib/python2.7/site-packages/amqp/abstract_channel.py", line 79, in wait
    self.connection.drain_events(timeout=timeout)
  File "/usr/lib/python2.7/site-packages/amqp/connection.py", line 491, in drain_events
    while not self.blocking_read(timeout):
  File "/usr/lib/python2.7/site-packages/amqp/connection.py", line 497, in blocking_read
    return self.on_inbound_frame(frame)
  File "/usr/lib/python2.7/site-packages/amqp/method_framing.py", line 77, in on_frame
    callback(channel, msg.frame_method, msg.frame_args, msg)
  File "/usr/lib/python2.7/site-packages/amqp/connection.py", line 501, in on_inbound_method
    method_sig, payload, content,
  File "/usr/lib/python2.7/site-packages/amqp/abstract_channel.py", line 128, in dispatch_method
    listener(*args)
  File "/usr/lib/python2.7/site-packages/amqp/channel.py", line 1597, in _on_basic_deliver
    fun(msg)
  File "/usr/lib/python2.7/site-packages/kombu/messaging.py", line 624, in _receive_callback
    return on_m(message) if on_m else self.receive(decoded, message)
  File "/usr/lib/python2.7/site-packages/celery/worker/consumer/consumer.py", line 572, in on_task_received
    callbacks,
  File "/usr/lib/python2.7/site-packages/celery/worker/strategy.py", line 200, in task_message_handler
    handle(req)
  File "/usr/lib/python2.7/site-packages/celery/worker/worker.py", line 228, in _process_task
    req.execute_using_pool(self.pool)
  File "/usr/lib/python2.7/site-packages/celery/worker/request.py", line 520, in execute_using_pool
    time_limit, soft_time_limit = self.time_limits
TypeError: 'NoneType' object is not iterable
```
## Checklist
celery v 4.2.0

## Steps to reproduce
It's part of a much larger project, so I don't have a self-contained example yet.

## Expected behavior
return a tuple of (None, None)
## Actual behavior
TypeError


Discussion:
This is indeed a bug and your fix should be correct.
I'm trying to figure out how to write a test for this case so it won't regress.
While the fix may be correct, the real issue is that someone is passing a `timelimt` header which is equal to `None` somewhere.
A fix should be made in the `Request` class constructor.

Resolving patch:
diff --git a/celery/worker/request.py b/celery/worker/request.py
--- a/celery/worker/request.py
+++ b/celery/worker/request.py
@@ -116,8 +116,9 @@ def __init__(self, message, on_ack=noop,
         self.parent_id = headers.get('parent_id')
         if 'shadow' in headers:
             self.name = headers['shadow'] or self.name
-        if 'timelimit' in headers:
-            self.time_limits = headers['timelimit']
+        timelimit = headers.get('timelimit', None)
+        if timelimit:
+            self.time_limits = timelimit
         self.argsrepr = headers.get('argsrepr', '')
         self.kwargsrepr = headers.get('kwargsrepr', '')
         self.on_ack = on_ack


Test patch:
diff --git a/t/unit/worker/test_request.py b/t/unit/worker/test_request.py
--- a/t/unit/worker/test_request.py
+++ b/t/unit/worker/test_request.py
@@ -1008,6 +1008,30 @@ def test_execute_using_pool(self):
         weakref_ref.assert_called_with(self.pool.apply_async())
         assert job._apply_result is weakref_ref()
 
+    def test_execute_using_pool_with_none_timelimit_header(self):
+        from celery.app.trace import trace_task_ret as trace
+        weakref_ref = Mock(name='weakref.ref')
+        job = self.zRequest(id=uuid(),
+                            revoked_tasks=set(),
+                            ref=weakref_ref,
+                            headers={'timelimit': None})
+        job.execute_using_pool(self.pool)
+        self.pool.apply_async.assert_called_with(
+            trace,
+            args=(job.type, job.id, job.request_dict, job.body,
+                  job.content_type, job.content_encoding),
+            accept_callback=job.on_accepted,
+            timeout_callback=job.on_timeout,
+            callback=job.on_success,
+            error_callback=job.on_failure,
+            soft_timeout=self.task.soft_time_limit,
+            timeout=self.task.time_limit,
+            correlation_id=job.id,
+        )
+        assert job._apply_result
+        weakref_ref.assert_called_with(self.pool.apply_async())
+        assert job._apply_result is weakref_ref()
+
     def test_execute_using_pool__defaults_of_hybrid_to_proto2(self):
         weakref_ref = Mock(name='weakref.ref')
         headers = strategy.hybrid_to_proto2('', {'id': uuid(),