django-rq icon indicating copy to clipboard operation
django-rq copied to clipboard

Use django's autoreload feature

Open ffigiel opened this issue 7 years ago • 6 comments
trafficstars

Autoreloading has been a long-requested RQ feature. Since it seems it's not going to be implemented in a while, I thought that django-rq could use runserver's auto-reloading feature instead.

I looked at how runserver does autoreloading and it's much simpler than I thought. Check it out: https://github.com/django/django/blob/master/django/core/management/commands/runserver.py#L98-L105

Of course this may cause issues with workers being stopped mid-job or some problems across various django versions, but we should at least research this a bit more 😃

I can prepare a PR for this. I assume this would be an opt-in feature, and should display a warning when enabled in production mode

ffigiel avatar Nov 27 '17 10:11 ffigiel

Yeah, a PR would be welcome.

Whether or not workers are stopped mid job depends on how autoreload.main is implemented. If it sends a SIGKILL to the management command, we're golden as RQ will wait until the job is finished before shutting down.

selwin avatar Nov 28 '17 16:11 selwin

I quickly hit a brick wall unfortunately 😞 Looks like we'll need to at least subclass the Worker

worker_1     | Traceback (most recent call last):
worker_1     |   File "/usr/local/lib/python3.6/site-packages/django/utils/autoreload.py", line 228, in wrapper
worker_1     |     fn(*args, **kwargs)
worker_1     |   File "/backend/django-rq/django_rq/management/commands/rqworker.py", line 127, in inner_handle
worker_1     |     w.work(burst=options.get('burst', False))
worker_1     |   File "/usr/local/lib/python3.6/site-packages/rq/worker.py", line 448, in work
worker_1     |     self._install_signal_handlers()
worker_1     |   File "/usr/local/lib/python3.6/site-packages/rq/worker.py", line 363, in _install_signal_handlers
worker_1     |     signal.signal(signal.SIGINT, self.request_stop)
worker_1     |   File "/usr/local/lib/python3.6/signal.py", line 47, in signal
worker_1     |     handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
worker_1     | ValueError: signal only works in main thread

After patching the worker to do nothing in _install_signal_handlers the worker started, but died as soon as I saved a file:

worker_1     | Traceback (most recent call last):
worker_1     |   File "/usr/local/lib/python3.6/site-packages/django/utils/autoreload.py", line 228, in wrapper
worker_1     |     fn(*args, **kwargs)
worker_1     |   File "/backend/django-rq/django_rq/management/commands/rqworker.py", line 132, in inner_handle
worker_1     |     w.work(burst=options.get('burst', False))
worker_1     |   File "/usr/local/lib/python3.6/site-packages/rq/worker.py", line 451, in work
worker_1     |     self.register_birth()
worker_1     |   File "/usr/local/lib/python3.6/site-packages/rq/worker.py", line 260, in register_birth
worker_1     |     raise ValueError(msg.format(self.name))
worker_1     | ValueError: There exists an active worker named '2402' already

Then I added a simple w.register_death() and everything seems to work perfectly 🎉

gif

ffigiel avatar Nov 28 '17 21:11 ffigiel

I implemented a wrapper around the rqworker management command (as another management command) that uses django.utils.autoreload. It runs the rqworker using the subprocess module, writing the worker's pid to a temp file, which is then read to kill the old rqworker when reloading. Works only on Linux / macOS.

https://gist.github.com/honi/81ba1bff622649a4a2ef7ccf695b1fa0

Feedback welcomed!

honi avatar Dec 16 '18 16:12 honi

Thank you @honi That script works great for me Much appreciated

BenEaston avatar Aug 20 '19 16:08 BenEaston

@honi

how do you pass the rest of the arguments ?

i.e.. normal way was

python manage.py rqworker

How do I pass the args to your wrapper?

taewookim avatar May 01 '20 19:05 taewookim

Nvm. I saw your solution here.

taewookim avatar May 01 '20 22:05 taewookim