<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      環境

      python 版本3.6.4

      gevent 1.5.0

      gunicorn 20.1.0

      錯誤

      RecursionError: maximum recursion depth exceeded while calling a Python object

      錯誤原因

      根據錯誤棧,出問題的代碼在python官方ssl包ssl.py第465行,具體代碼

      class SSLContext(_SSLContext):
      
          @property
          def options(self):
              return Options(super().options)
      
          @options.setter
          def options(self, value):
              # 這就是拋錯的代碼
              super(SSLContext, SSLContext).options.__set__(self, value)
      

      在對SSLContext實例設置option屬性的時候,會調用到super(SSLContext, SSLContext).options.__set__(self, value)

      問題的原因在于先導入了ssl包,然后才進行了gevent patch,這樣上面這一行代碼中的SSLContext實際上已經被patch成了gevent._ssl3.SSLContext

      gevent._ssl3.SSLContext相關的代碼如下

      class SSLContext(orig_SSLContext):
          @orig_SSLContext.options.setter
          def options(self, value):
              super(orig_SSLContext, orig_SSLContext).options.__set__(self, value)
      

      gevent._ssl3.SSLContext中繼承的orig_SSLContext就是python官方的ssl.SSLContext

      所以整體的邏輯就變成了

      1.super(SSLContext, SSLContext).options.__set__(self, value)

      2.由于已經經過了patch,所以SSLContext實際上是gevent._ssl3.SSLContext,那么super(SSLContext, SSLContext).options.__set__(self, value)實際上是super(gevent._ssl3.SSLContext, gevent._ssl3.SSLContext).options.__set__(self, value)

      3.由于gevent繼承了ssl.SSLContext所以會調用到SSLContext的options.setter方法,這樣就回到了1,在這里開始了無限遞歸

      所以patch時機不對,導致調用SSLContext實際是調用了gevent._ssl.SSLContext

      如果先patch再導入,則自始至終都是gevent._ssl3.SSLContext,調用的代碼變成super(orig_SSLContext, orig_SSLContext).options.__set__(self, value)

      orig_SSLContextssl.SSLContext

      patch時機正確,則直接從gevent._ssl.SSLContext調用

      根本原因

      拋出異常的原因清楚了,我們再來找找為什么會拋出這個異常

      先看gunicorn的啟動順序,為了清晰,我省略了無關的代碼,只列出了和啟動相關的代碼

      gunicorn啟動的入口是WSGIApplication().run()

      WSGIApplication繼承了ApplicationApplication繼承BaseApplicationBaseApplication__init__方法中調用了self.do_load_config()進行配置加載

      首先,進行初始化,在__init__中調用了這個方法

      def do_load_config(self):
          """
      Loads the configuration
      """
      try:
              # 對cfg進行初始化,讀取配置
              self.load_default_config()
              # 加載配置文件
              self.load_config()
          except Exception as e:
              print("\nError: %s" % str(e), file=sys.stderr)
              sys.stderr.flush()
              sys.exit(1)
      

      self.do_load_config()調用self.load_default_config()self.load_config()
      對cfg進行初始化

      接著,調用run方法,WSGIApplication沒有實現run方法,則調用Applicationrun方法

      def run(self):
          if self.cfg.print_config or self.cfg.check_config:
              try:
                  # 在這里加載app
                  self.load()
              except Exception:
                  sys.exit(1)
              sys.exit(0)
      
          # 這里會調用Arbiter的run方法
          super().run()
      

      可以看到調用了self.load()

      接著看load方法

      def load(self):
          if self.cfg.paste is not None:
              return self.load_pasteapp()
          else:
              # 我們目前走這里
              return self.load_wsgiapp()
      

      所以load這里加載了我們的app

      接著,Applicationrun方法最后會調用Arbiterrun方法

      def run(self):
          "Main master loop."
      self.start()
          util._setproctitle("master [%s]" % self.proc_name)
      
          try:
              # 這里處理worker
              self.manage_workers()
              # 省略部分代碼
          except Exception:
              sys.exit(-1)
      

      啟動worker最終會調用spawn_worker

      def spawn_worker(self):
          self.worker_age += 1
          # 在配置中設置的worker class
          worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,
                                     self.app, self.timeout / 2.0,
                                     self.cfg, self.log)
          # 省略部分代碼
          try:
              # 這里初始化,對gevent而言,初始化的時候,才會進行patch
              worker.init_process()
              sys.exit(0)
          except SystemExit:
              raise
      

      worker的init_process方法如下

      def init_process(self):
          # 在這里調用patch
          self.patch()
          hub.reinit()
          super().init_process()
      

      self.patch()的實現

      def patch(self):
          # 在這里進行patch
          monkey.patch_all()
      

      綜上,gunicorn啟動的時候,加載順序為:

      配置文件加載 -> app加載 -> worker初始化

      此外我們還發現,在gunicorn處理config的時候,在gunicorn.config中導入了ssl包,所以在worker初始化之前ssl包已經被導入了,后面的patch又把ssl包patch成了gevent._ssl3,最終導致了上面的問題

      復現

      問題找到,我們先構造一個可以復現的例子

      app.py

      from flask import Flask
      import requests
      
      app = Flask(__name__)
      
      from requests.packages.urllib3.util.ssl_ import create_urllib3_context
      ctx = create_urllib3_context()
      
      
      @app.route("/test")
      def test():
          requests.get("https://www.baidu.com")
          return "test"
      
      
      if __name__ == "__main__":
          app.run(debug=True)
      

      啟動命令

      gunicorn -w 2 --worker-class gevent --preload -b 0.0.0.0:5000 app:app
      

      現在當我們啟動后,調用http://127.0.0.1:5000/test 就會觸發RecursionError

      解決

      既然問題在于ssl包導入之后才進行patch,那么我們前置patch即可,考慮到配置文件加載在加載app之前,如果我們在配置文件加載時patch,則是目前能夠找到的最早的patch時機。

      配置文件gunicorn_config.py

      import gevent.monkey
      gevent.monkey.patch_all()
      
      workers = 8
      

      啟動命令

      gunicorn --config config.py --worker-class gevent --preload -b 0.0.0.0:5000 app:app

      問題解決

      posted on 2022-05-12 22:56  Go_Forward  閱讀(1970)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 成人小说亚洲一区二区三区| av在线播放观看国产| 妺妺窝人体色www看美女| 邯郸市| 在线高清免费不卡全码| 亚洲嫩模一区二区三区| 国产精品一区二区三区四| 欧美交a欧美精品喷水| 亚洲中文字幕无码一久久区| 国产办公室秘书无码精品99| 激情国产一区二区三区四| 国产精品一区在线蜜臀| 高台县| 色欲av亚洲一区无码少妇| 国产成人不卡一区二区| 英山县| 熟女人妻视频| 国产精品亚洲二区在线播放| av深夜免费在线观看| 久久一级精品久熟女人妻| 日本熟妇色xxxxx日本免费看| 好吊妞| 日韩av一区二区高清不卡| 天天躁夜夜躁狠狠喷水| 欧美日韩在线视频| 午夜激情小视频一区二区| 久久羞羞色院精品全部免费| 亚洲第一福利网站在线观看| 中文字幕精品人妻丝袜| 99精品视频九九精品视频| 国产成人亚洲精品青草天美| 亚洲熟女精品一区二区| 国产精品天天看天天狠| 南漳县| 国产在线观看网址不卡一区| 国产精品视频午夜福利| 国产精品无码久久久久| 亚洲久久色成人一二三区| 国产日韩乱码精品一区二区 | 国产精品天天看天天狠| 一区二区福利在线视频|