【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执行初始化操作同样可以解决以上问题

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Flask 中,使用 `before_first_request` 装饰器定义的函数中创建的变量是全局变量,可以在应用程序的任何地方使用。但是需要注意的是,这些变量在多线程环境下可能会出现线程安全问题,需要进行适当的线程安全处理。 具体来说,如果你在 `before_first_request` 函数中创建的变量需要被其他请求调用,可以将其保存在 Flask 应用实例对象的一个属性中,然后在其他请求中使用这个属性。例如: ```python from flask import Flask, request app = Flask(__name__) @app.before_first_request def init_global_variable(): # 定义一个全局变量并保存在 Flask 应用实例对象的属性中 app.some_variable = 'Hello, World!' @app.route('/') def index(): # 在请求中使用保存在 Flask 应用实例对象中的全局变量 return app.some_variable @app.route('/set') def set_variable(): # 在请求中修改保存在 Flask 应用实例对象中的全局变量 app.some_variable = request.args.get('variable') return 'OK' ``` 在上面的代码中,`init_global_variable` 函数在整个 Flask 项目启动前运行一次,并在其中创建了一个全局变量 `app.some_variable`。在 `index` 视图函数中,可以直接使用这个全局变量,并返回它的值。在 `set_variable` 视图函数中,可以修改这个全局变量的值。 需要注意的是,在多线程环境下,对于一些需要读写的全局变量,可能需要进行适当的线程安全处理,例如使用锁来保证数据的一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sysu_lluozh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值