AppConfig.ready()在Django安装上运行两次(使用Heroku)

时间:2017-04-23 22:59:17

标签: python django heroku

我有一个需要在我的某个Web应用程序后台运行的函数。

我实现了一个自定义AppConfig,如下所示:

class MyAppConfig(AppConfig):
    run_already = False

    def ready(self):
        from .tasks import update_products
        if "manage.py" not in sys.argv and not self.run_already:
            self.run_already = True
            update_products()

但是,此命令正在执行两次(update_products()调用)

As stated in the documentation:

  

在通常的初始化过程中,只调用ready方法   曾经是Django。但在某些极端情况下,特别是在测试中   正在摆弄已安装的应用程序,准备就绪可能会更多   不止一次。在这种情况下,要么写出幂等方法,要么放一个   在AppConfig类上标记以防止重新运行应该的代码   只执行一次。

我觉得我正在按照文档说的去做。是什么给了什么?

5 个答案:

答案 0 :(得分:7)

正如answer所述,如果您正在使用Django上的python manage.py runserver命令运行您的应用,您的应用将运行两次:一次验证您的模型,另一次验证一个运行你的应用程序。

您可以将此选项--noreload更改为runserver命令。

答案 1 :(得分:2)

在heroku上,gunicorn是由一名以上的炮手开始的。将WEB_CONCURRENCY设置为1

heroku config:set WEB_CONCURRENCY=1

(见Basic configuration

答案 2 :(得分:0)

没有旗帜在班级上有效。 Django在两个独立的进程上运行两次。两个独立进程上的类级变量彼此不可见。使用数据库表中的标志,如此代码中所示(SchedulerUtils是由我编写的类,使用方法go()启动backgroud apscheduler调度程序。模型使用表scheduler_schedulerinfo中的一行,因此您必须在此之前插入此行: “INSERT INTO scheduler_schedulerinfo(started)values(0);”):

################################## APPS.PY
import os
from django.apps import AppConfig
from apscheduler.schedulers.background import BlockingScheduler, BackgroundScheduler
from scheduler.utils import SchedulerUtils

class SchedulerConfig(AppConfig):
    name = 'scheduler'

    def ready(self):
        startScheduler = True
        pid = os.getpid()

        #check i'm on heroku
        if (os.environ.get("DYNO")):
            # i'm on heroku, here runs twice
            print("[%s] DYNO ENV exists, i'm on heroku" % pid)
            from scheduler.models import SchedulerInfo
            schedInfo = SchedulerInfo.objects.all().first()
            if (schedInfo.started == 0):
                print("[%s] Scheduler not started, starting.... " % pid)
                startScheduler = True
                # set flag to 1
                SchedulerInfo.objects.all().update(started = 1)
            else:
                print("[%s] Scheduler already running, not starting." % pid)
                startScheduler = False # already running
                # reset to 0 for next time
                SchedulerInfo.objects.all().update(started = 0)

        # PRINT FLAG VALUE
        from scheduler.models import SchedulerInfo
        schedInfo = SchedulerInfo.objects.all().first()
        print("[%s] Value of flag schedulerinfo.started: %d" % (pid, schedInfo.started))

        if (startScheduler):
            su = SchedulerUtils()
            su.go()

##################################### MODELS.PY
from django.db import models

class SchedulerInfo(models.Model):
    started = models.IntegerField(default=0)

答案 3 :(得分:0)

另一个解决方案可能是检查gunicorn pids如下:

import os
from django.apps import AppConfig
import psutil

class SchedulerConfig(AppConfig):
    name = 'scheduler'

    # I want to start ths scheduler only once,
    # if WEB_CONCURRENCY is set and is greater than 1
    # start the scheduler if the pid of this gunicorn is the same of the
    # maximum pid of all gunicorn processes
    def ready(self):
        startScheduler = True

        #check WEB_CONCURRENCY exists and is more than 1
        web_concurrency = os.environ.get("WEB_CONCURRENCY")
        if (web_concurrency):
            mypid = os.getpid()
            print("[%s] WEB_CONCURRENCY exists and is set to %s" % (mypid, web_concurrency))
            gunicorn_workers = int(web_concurrency)
            if (gunicorn_workers > 1):
                maxPid = self.getMaxRunningGunicornPid()
                if (maxPid == mypid):
                    startScheduler = True
                else:
                    startScheduler = False

        if (startScheduler):
            print("[%s] WILL START SCHEDULER", mypid)
        else:
            print("[%s] WILL NOT START SCHEDULER", mypid)

    def getMaxRunningGunicornPid(self):
        running_pids = psutil.pids()
        maxPid = -1
        for pid in running_pids:
            proc = psutil.Process(pid)
            proc_name = proc.name()
            if (proc_name == "gunicorn"):
                if (maxPid < pid):
                    maxPid = pid
        print("Max Gunicorn PID: %s", maxPid)
        return maxPid

答案 4 :(得分:0)

发现AppConfig被触发了两次,这导致调度程序通过这种设置被初始化了两次。而是像这样在url.py中实例化调度程序-

urlpatterns = [
    path('api/v1/', include(router.urls)),
    path('api/v1/login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/v1/login/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
    path('api/v1/', include('rest_registration.api.urls'))
]

scheduler = BackgroundScheduler()
scheduler.add_job(task.run, trigger='cron', hour=settings.TASK_RUNNER_HOURS, minute=settings.TASK_RUNNER_MINUTES, max_instances=1)
scheduler.start()

这样,调度程序仅实例化一次。问题已解决。