0

I have a Django application that executes a lengthy task, that is controlled by Ajax on one of the app's pages. The first Ajax call creates an entry in a task table in the db, and then creates a thread that executes that task, by calling a Java app via Py4J. This sends a command to a legacy server and then regularly queries it for status and updates the task table until the task is complete.

This tasks takes about 10/15 minutes and works perfectly via manage.py runserver.

However in deployment, I have installed it behind IIS with wfastcgi, and the task is launched correctly but after a little while, minutes, it seems to stop. The task in the db is not updated and there are no exceptions logged or anything. As if IIS or wfastcgi killed the thread without warning. Note that it is an internal tool that will be used at most by 4/5 people so load is not really an issue.

Is there anything I have missed or that could be done?

The code is as follows:

# Get a PyFJ command
def getMCLCmd():
    gateway = JavaGateway()
    try:
        cmd = gateway.entry_point.getMCLCmd()
        return cmd
    except Py4JNetworkError as e:
        # maybe server not started
        logger.info('Py4J not started, starting it')
        pyserver = py4JVM()
        pyserver.start()

    # try again
    try:
        cmd = gateway.entry_point.getMCLCmd()
        return cmd
    except Py4JNetworkError as e:
        logger.debug('Py4J error' + e)
        return None


class py4JVM(threading.Thread):
   def __init__(self):
        threading.Thread.__init__(self, daemon=True)
        self.error = None

   def run(self):
        try:
            process = subprocess.run(MCL_CMD, shell=True, check=True, stdout=subprocess.PIPE, universal_newlines=True)
            output = process.stdout
            logger.info(f'Py4J output: {output}')
        except subprocess.CalledProcessError as e:
            self.error = f"command return with error (code {e.returncode}): {e.output}"
            logger.error(self.error)


# the thread class
class FirstInitThread(threading.Thread):
    def __init__(self, task: ClientTask):
        self.task = task
        threading.Thread.__init__(self)

    def run(self):
        cmd = getMCLCmd()
        if cmd is None:
            self.task.status = 0
            self.task.status_msg = 'Cannot start Py4J'
            self.task.save()
            logger.error('Cannot start Py4J')
            return

        self.task.status = 1
        try:
            if cmd.sendCommand(self.task.config):
                while cmd.isRunning():
                    time.sleep(1)
                    msg = cmd.getNextMessage()
                    if msg:
                        logger.info(msg)
                        self.task.status_msg = msg
                        self.task.save()
                    else:
                        self.task.status_msg = self.task.status_msg + '.'
                        self.task.save()
                self.task.status = 2
                self.task.save()

        except Py4JJavaError as e:
            self.task.status = 2
            self.task.status_msg = 'Client error'
            logger.error('Client error')
            self.task.save()

        except Exception as e:
            self.task.status = 2
            self.task.status_msg = 'Thread stopped: ' + str(e)
            logger.error(self.task.status_msg )
            self.task.save()

The view to launch the process is

@csrf_exempt
def remoteConfig(request: HttpRequest):
    ... creation of config object
    task = ClientTask()
    task.config = config
    task.status_msg = 'Initialisation'
    task.status = 0
    task.save()

    thread = FirstInitThread(task)
    thread.start()
    return HttpResponse(str(task.pk))

And then to update the HTML page with Ajax we call:

@csrf_exempt
def initProgress(request: HttpRequest):
    if request.method != 'POST':
        return HttpResponse("Invalid")

    # construct the client config
    task_id = request.POST.get('task_id')
    task: Task = Task.objects.get(pk = task_id)
    if task is None:
        return HttpResponse('Invalid Task')
    
    if task.status == 2:
        return HttpResponse('Completed: ' + msg + '')
    
    return HttpResponse(task.status_msg)
langlais
  • 21
  • 3
  • wfastcgi has been deprecated, so this is no longer the correct way to host Python web applications on IIS. You can use HttpPlatformHanlder to host Django web applications. For Python developers, learning HttpPlatformHandler has become very important, because Microsoft no longer recommends FastCGI. Reference tutorial: https://medium.com/@akshay.mewada7/host-flask-and-django-web-application-with-windows-iis-httpplatformhanlder-83e831ce91bc – YurongDai Jun 09 '23 at 06:58
  • I'm not sure that is the best way forward. According to this tutorial, HttpResponseHandler uses manage.py runserver, which django doc explicitly says is not good enough for production. In any case, will that solve my issue? – langlais Jun 13 '23 at 14:41
  • Of course this is just a friendly suggestion. – YurongDai Jun 16 '23 at 09:32

0 Answers0