Repository: celery/celery

Issue:
Null args in backend_cleanup task from beat.
# Checklist
- [x] I have read the relevant section in the
  [contribution guide](http://docs.celeryproject.org/en/latest/contributing.html#other-bugs)
  on reporting bugs.
- [x] I have checked the [issues list](https://github.com/celery/celery/issues?q=is%3Aissue+label%3A%22Issue+Type%3A+Bug+Report%22+-label%3A%22Category%3A+Documentation%22)
  for similar or identical bug reports.
- [x] I have checked the [pull requests list](https://github.com/celery/celery/pulls?q=is%3Apr+label%3A%22PR+Type%3A+Bugfix%22+-label%3A%22Category%3A+Documentation%22)
  for existing proposed fixes.
- [ ] I have checked the [commit log](https://github.com/celery/celery/commits/master)
  to find out if the bug was already fixed in the master branch.
- [x] I have included all related issues and possible duplicate issues
  in this issue (If there are none, check this box anyway).

## Mandatory Debugging Information

- [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).
- [ ] I have verified that the issue exists against the `master` branch of Celery.
- [x] I have included the contents of ``pip freeze`` in the issue.
- [x I have included all the versions of all the external dependencies required
  to reproduce this bug.

## Optional Debugging Information
- [ ] I have tried reproducing the issue on more than one Python version
  and/or implementation.
- [ ] I have tried reproducing the issue on more than one message broker and/or
  result backend.
- [ ] I have tried reproducing the issue on more than one version of the message
  broker and/or result backend.
- [ ] I have tried reproducing the issue on more than one operating system.
- [ ] I have tried reproducing the issue on more than one workers pool.
- [ ] I have tried reproducing the issue with autoscaling, retries,
  ETA/Countdown & rate limits disabled.
- [ ] I have tried reproducing the issue after downgrading
  and/or upgrading Celery and its dependencies.

## Environment & Settings
<!-- Include the contents of celery --version below -->
**Celery version**: 4.4.0rc2 (cliffs)
<!-- Include the output of celery -A proj report below -->

<details>
<summary><b><code>celery report</code> Output:</b></summary>
<p>

software -> celery:4.4.0rc2 (cliffs) kombu:4.6.3 py:3.7.3
            billiard:3.6.0.0 py-amqp:2.5.0
platform -> system:Linux arch:64bit
            kernel version:4.18.0-21-generic imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:pyamqp results:redis://redis:6379/0

broker_url: 'amqp://guest:********@rabbitmq:5672//'
result_backend: 'redis://redis:6379/0'
timezone: 'UTC'
result_extended: True
task_acks_late: True
task_routes: {
    'celery.*': {'queue': 'api_workers'},
    'graphape.tasks.api.*': {'queue': 'api_workers'},
    'graphape.tasks.process.*': {'queue': 'process_workers'}}
JOBTASTIC_CACHE: <jobtastic.cache.base.WrappedCache object at 0x7f152c5ddb70>
redbeat_redis_url: 'redis://redis:6379/0'
beat_schedule: {
 }

</p>
</details>

# Steps to Reproduce

Don't think anything other than celery 4.4.0RC and just having beat running and publishing a "celery.backend_cleanup"-task.
Task in backend (redis) will have args set to null and this code will later try to enumerate from the null value:
https://github.com/celery/celery/blob/master/celery/beat.py#L387

I guess these lines should be patched to deal with args = null?
Or perhaps args is never allowed to be null and fix should instead be already when task is written to backend?
I'm not sure what celery specification says about null args, please advise.

### Python Packages
<!-- Please fill the contents of pip freeze below -->
<details>
<summary><b><code>pip freeze</code> Output:</b></summary>
<p>
alabaster==0.7.12
amqp==2.5.0
argcomplete==1.9.3
argh==0.26.2
asteval==0.9.14
Babel==2.7.0
billiard==3.6.0.0
bleach==3.1.0
boto3==1.9.179
botocore==1.12.179
bumpversion==0.5.3
cachelib==0.1
celery==4.4.0rc2
celery-redbeat==0.13.0
Cerberus==1.3.1
certifi==2019.6.16
cfn-flip==1.2.1
chardet==3.0.4
Click==7.0
coverage==4.5.3
decorator==4.4.0
dnspython==1.16.0
docutils==0.14
dumb-init==1.2.2
durationpy==0.5
Eve==0.9.2
eventlet==0.25.0
Events==0.3
Flask==1.0.3
Flask-SocketIO==4.1.0
future==0.16.0
graphape==0.0.1
greenlet==0.4.15
hjson==3.0.1
idna==2.8
imagesize==1.1.0
itsdangerous==1.1.0
Jinja2==2.10.1
jmespath==0.9.3
jobtastic==2.1.1
kappa==0.6.0
kombu==4.6.3
lambda-packages==0.20.0
livereload==2.6.1
MarkupSafe==1.1.1
mock==3.0.5
monotonic==1.5
networkx==2.3
numpy==1.16.4
packaging==19.0
pathtools==0.1.2
pkginfo==1.5.0.1
placebo==0.9.0
pluginbase==1.0.0
port-for==0.3.1
psutil==5.6.3
Pygments==2.4.2
pymongo==3.8.0
pyparsing==2.4.0
python-dateutil==2.6.1
python-engineio==3.8.1
python-slugify==1.2.4
python-socketio==4.1.0
pytz==2019.1
PyYAML==5.1.1
readme-renderer==24.0
redis==3.2.1
requests==2.22.0
requests-toolbelt==0.9.1
s3transfer==0.2.1
simplejson==3.16.0
six==1.12.0
snowballstemmer==1.2.1
Sphinx==2.0.1
sphinx-autobuild==0.7.1
sphinx-rtd-theme==0.4.3
sphinxcontrib-applehelp==1.0.1
sphinxcontrib-devhelp==1.0.1
sphinxcontrib-htmlhelp==1.0.2
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.2
sphinxcontrib-serializinghtml==1.1.3
tenacity==5.0.4
toml==0.10.0
tornado==6.0.2
tqdm==4.32.1
troposphere==2.4.9
twine==1.13.0
Unidecode==1.1.1
urllib3==1.25.3
uWSGI==2.0.18
vine==1.3.0
watchdog==0.9.0
webencodings==0.5.1
Werkzeug==0.15.4
wsgi-request-logger==0.4.6
zappa==0.48.2

</p>
</details>

### Other Dependencies
<!--
Please provide system dependencies, configuration files
and other dependency information if applicable
-->
<details>
<p>
- celery-redbeat
</p>
</details>

# Expected Behavior
No exception.

# Actual Behavior
Exception:

```
[2019-07-02 04:00:00,081: ERROR/MainProcess] Message Error: Couldn't apply scheduled task celery.backend_cleanup: 'NoneType' object is not iterable
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/celery/beat.py", line 387, in apply_async
    entry_args = [v() if isinstance(v, BeatLazyFunc) else v for v in entry.args]
TypeError: 'NoneType' object is not iterable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/redbeat/schedulers.py", line 427, in maybe_due
    result = self.apply_async(entry, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/celery/beat.py", line 400, in apply_async
    entry, exc=exc)), sys.exc_info()[2])
  File "/usr/local/lib/python3.7/site-packages/vine/five.py", line 194, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.7/site-packages/celery/beat.py", line 387, in apply_async
    entry_args = [v() if isinstance(v, BeatLazyFunc) else v for v in entry.args]
celery.beat.SchedulingError: Couldn't apply scheduled task celery.backend_cleanup: 'NoneType' object is not iterable
```



Discussion:
could you try to patch and test?

Resolving patch:
diff --git a/celery/beat.py b/celery/beat.py
--- a/celery/beat.py
+++ b/celery/beat.py
@@ -384,7 +384,7 @@ def apply_async(self, entry, producer=None, advance=True, **kwargs):
         task = self.app.tasks.get(entry.task)
 
         try:
-            entry_args = [v() if isinstance(v, BeatLazyFunc) else v for v in entry.args]
+            entry_args = [v() if isinstance(v, BeatLazyFunc) else v for v in (entry.args or [])]
             entry_kwargs = {k: v() if isinstance(v, BeatLazyFunc) else v for k, v in entry.kwargs.items()}
             if task:
                 return task.apply_async(entry_args, entry_kwargs,


Test patch:
diff --git a/t/unit/app/test_beat.py b/t/unit/app/test_beat.py
--- a/t/unit/app/test_beat.py
+++ b/t/unit/app/test_beat.py
@@ -188,6 +188,17 @@ def foo():
         scheduler.apply_async(scheduler.Entry(task=foo.name, app=self.app))
         foo.apply_async.assert_called()
 
+    def test_apply_async_with_null_args(self):
+
+        @self.app.task(shared=False)
+        def foo():
+            pass
+        foo.apply_async = Mock(name='foo.apply_async')
+
+        scheduler = mScheduler(app=self.app)
+        scheduler.apply_async(scheduler.Entry(task=foo.name, app=self.app, args=None, kwargs=None))
+        foo.apply_async.assert_called()
+
     def test_should_sync(self):
 
         @self.app.task(shared=False)