【Flask】before_first_request與processes

有小夥伴反饋代碼運行服務概率性出現返回None的問題

併發執行

併發執行一個簡單腳本

def ok(t):
    return "ok"

併發執行均可正常返回ok,無任何異常

日誌信息

查看出現錯誤的日誌信息

Traceback (most recent call last):
  File "/app/runCode/callPy3.py", line 49, in call_func
FileNotFoundError: [Errno 2] No such file or directory:
'/app/box/sleep_time_fb3fd130-ba80-11ea-b4b2-0242ac1e573d.py'

從錯誤日誌信息中可以發現執行的文件沒有找到,看看代碼中這塊的處理邏輯

代碼邏輯

    try:
        # 執行關鍵字函數,並返回結果
        return eval(_call_fun_)
    except Exception as e:
        traceback.print_exc()
    finally:
        try:
            # 刪除文件
            os.remove(file_path)
        except Exception as e:
            traceback.print_exc()

從代碼中可以看出,首先執行關鍵字函數後,再 finally 執行刪除文件的操作,debug斷點試驗均爲該處理順序,那爲何會出現該問題呢?

長時間執行

一開始併發執行的代碼,均爲直接返回,而用戶反饋的有問題代碼是因爲sleep了30s,是否是代碼在sleep時代碼文件被刪除?
故在執行某一個sleep 30秒的代碼時,多次執行立即返回的代碼,此問題復現

刪除文件操作

review所有代碼,發現整個服務執行文件刪除操作的僅僅2個地方:

  1. 上面代碼中提到的運行關鍵字函數後刪除代碼文件
  2. 在預處理時before_first_request初始化刪除所有文件
    既然運行關鍵字函數後刪除代碼文件的操作沒有問題,那麼問題很大概率時因爲before_first_request的請求

before_first_request

查看這段代碼

# @app.before_first_request
def before_first_request():
    # 初始化時操作,僅第一次請求時操作
    try:
    	...
        os.remove(file_path)
    except Exception as e:
        traceback.print_exc(e)

before_first_request在官方以及非官方文檔中均如此描述:

在對應用程序實例的第一個請求之前註冊要運行的函數, 只會執行一次

ok,既然懷疑before_first_request出現了問題,打印日誌並部署運行

執行log信息

# @app.before_first_request
def before_first_request():
    # 初始化時操作,僅第一次請求時操作
    try:
    	app.logger.info("**********************************刪除文件啦啦啦啦啦")
        os.remove(file_path)
    except Exception as e:
        traceback.print_exc(e)

通過日誌發現有如下

INFO:werkzeug:172.30.139.0 - - [30/Jun/2020 16:27:08] "POST /colab/call HTTP/1.1" 200 -
INFO:__main__:**********************************刪除文件啦啦啦啦啦
INFO:__main__:ImmutableMultiDict([('code', 'import time\ndef ok(t):\n    time.sleep(t)\n    return "ok"'), ('fun_call', 'ok(0)'), ('user', 'swapi')])
INFO:werkzeug:172.30.139.0 - - [30/Jun/2020 16:27:08] "POST /colab/call HTTP/1.1" 200 -
INFO:__main__:**********************************刪除文件啦啦啦啦啦
INFO:__main__:ImmutableMultiDict([('code', 'import time\ndef ok(t):\n    time.sleep(t)\n    return "ok"'), ('fun_call', 'ok(0)'), ('user', 'swapi')])
INFO:werkzeug:172.30.139.0 - - [30/Jun/2020 16:27:08] "POST /colab/call HTTP/1.1" 200 -
INFO:__main__:**********************************刪除文件啦啦啦啦啦
INFO:__main__:ImmutableMultiDict([('code', 'import time\ndef ok(t):\n    time.sleep(t)\n    return "ok"'), ('fun_call', 'ok(0)'), ('user', 'swapi')])
INFO:werkzeug:172.30.71.0 - - [30/Jun/2020 16:27:08] "POST /colab/call HTTP/1.1" 200 -
INFO:__main__:**********************************刪除文件啦啦啦啦啦
INFO:__main__:ImmutableMultiDict([('code', 'import time\ndef ok(t):\n    time.sleep(t)\n    return "ok"'), ('fun_call', 'ok(0)'), ('user', 'swapi')])

執行機制

很明顯,before_first_request被多次執行了,啥原因呢?我們再次看看before_first_request的具體實現

    #: A lists of functions that should be called at the beginning of the
    #: first request to this instance.  To register a function here, use
    #: the :meth:`before_first_request` decorator.
    #:
    #: .. versionadded:: 0.8
    self.before_first_request_funcs = []
    
    @setupmethod
    def before_first_request(self, f):
        """Registers a function to be run before the first request to this
        instance of the application.

        .. versionadded:: 0.8
        """
        self.before_first_request_funcs.append(f) 

在初始化後第一次請求時會調用,後續的所有請求都不會在調用該函數,除非重新初始化

多進程執行

而該服務啓動的方式恰好時多進程

app.run(host='0.0.0.0', port=5005, debug=False, processes=20)

將多進程修改爲1個進程或者修改成多線程的方式執行

app.run(host='0.0.0.0', port=5005, debug=False, threaded=True)

此時問題沒有復現

或者不使用 @app.before_first_request 註解的方式,直接init執行初始化操作同樣可以解決以上問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章