Search code examples
pythondjangomultithreadinginfinite-loopindefinite

I am trying to run an endless worker thread (daemon) from within Django


I have a worker thread which only task is to query a list of active users every 10 minutes from the database, and to send them an SMS message if a certain condition is fulfilled (which is checked every minute); also the worker thread does not hinder the main application at all.

So far I managed to get the thread up and running and sending SMS works also just fine. However, for some reasons the thread stops/gets killed after some random time (hours). I run a try: except Exception as e: within a while True, to catch occurring errors. Additionally, I print out a messages saying what error occurred.

Well, I never see any message and the thread is definitely down. Therefore, I suspect Gunicorn or Django to kill my thread sort of gracefully.

I have put log and print statements all over the code but haven't found anything indicating why my thread is getting killed.

My wsgi.py function where I call the function to start my thread

"""
WSGI config for django_web project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_web.settings')

application = get_wsgi_application()

'''
Start background services
Import has to happen after "get_wsgi_application()"; otherwise docker container crashes
'''
try:
    from threading import Thread
    from timesheet.admin import runWorkmateServices

    runWorkmateServices()

except Exception as exp:
    print(exp)

The function which is called from within the wsgi.py. I double check if the thread was started to avoid having two up and running.

def runWorkmateServices(request=None):
    service_name = 'TimeKeeperWorkMateReminderService'

    thread_found = False
    for thread in threading.enumerate():
        if service_name in thread.name:
            thread_found = True
            break  # Leave loop now

    if thread_found:
        print(f'Service has already been started: {service_name}')
        if request:
            messages.add_message(request, messages.ERROR, f'Service has already been started:: {service_name}')
    else:
        Thread(target=WorkMateReminders, args=(), name=service_name, daemon=True).start()
        print(f'Started Service: {service_name}')
        if request:
            messages.add_message(request, messages.SUCCESS, f'Started Service: {service_name}')

The worker thread itself

def WorkMateReminders():
    print('Thread Started: WorkMateReminders')
    timer = 0
    employees = User.objects.none()

    while True:

        try:
            # Update user list every n * sleep time (10 minutes)
            if timer % 10 == 0:
                timer = 0
                # Get active employees
                employees = User.objects.filter(is_active=True, profile__workmate_sms_reminders_activated=True)
                print(f'Employees updated at {datetime.now().date()} - {datetime.now().time()}: {employees}')

            WorkMateCheckClockOffTimes(employees=employees)

            WorkMateClockOnReminder(employees=employees)

            WorkMateEndOfBreakReminder(employees=employees)

            timer += 1  # increment timer

        except Exception as exp:
            print(f'Error: {exp}')

        time.sleep(60 * 1)

My goal is to have this worker thread running for as long as Django is up.


Solution

  • Most WSGI servers spawn workers that are killed/recycled fairly regularly, spawning threads from these workers is not the best solution to your problem. There are several ways to go about this

    Cron

    Create a management command that does what you want and configure cron to run it every 10 minutes

    Celery/Celerybeat

    Set up a celery worker, this is a process that runs asynchronously to your Django application and using celerybeat you can have tasks run at intervals