将烧瓶请求/应用程序上下文复制到另一个进程

Posted

技术标签:

【中文标题】将烧瓶请求/应用程序上下文复制到另一个进程【英文标题】:Copy flask request/app context to another process 【发布时间】:2019-10-02 05:58:57 【问题描述】:

tl;博士

如何序列化 Flask 应用程序或请求上下文,或该上下文的 子集(即任何 可以 成功序列化),以便我可以从另一个进程,而不是一个线程?

加长版

我有一些需要访问 Flask 请求上下文或 App 上下文的函数,我想在后台运行这些函数。

Flask 有一个内置的 @copy_current_request_context 装饰器,用于将函数包装在请求上下文的副本中,因此您可以在不同的线程中运行它:

from threading import Thread
from flask import Flask, request, copy_current_request_context

app = Flask(__name__)

@app.route('/')
def index():
    request.foo = 'bar'
    @copy_current_request_context
    def baz():
        print(request.foo)
    thr = Thread(target=baz)
    thr.start()
    return 'ok'

虽然 Flask 不提供用于复制应用上下文的内置装饰器,但它提供了执行此操作的机制 - Access flask.g inside greenlet 中描述了一种解决方案

我尝试过的

首先,我希望用 Pickle 来解决。这是concurrent.futures.ProcessPoolExecutor 使用的。不幸的是,由于应用上下文中存在线程锁对象,Pickle 失败了:

>>> with app.test_request_context('/'):
...     pickle.dumps(appctx)
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: can't pickle _thread.lock objects

它也不能复制修饰/包装的函数:

>>> with app.test_request_context('/'):
...     bar = copy_current_request_context(pow)
...     pickle.dumps(bar)
...
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
_pickle.PicklingError: Can't pickle <function pow at 0x110bee8c8>: it's not the same object as builtins.pow

接下来我尝试了dill。它通过了上述两个测试,但它不能序列化许多其他可能最终出现在上下文中的东西。应用程序上下文特别容易将扩展链接到其中:SQLAlchemy 就是一个很好的例子。当您使用 SQLAlchemy 并尝试序列化您的应用上下文时,会发生以下情况:

>>> from flask_sqlalchemy import SQLAlchemy
>>> db = SQLAlchemy(app)
>>> with app.test_request_context('/'):
...     from flask.globals import _app_ctx_stack
...     appctx = _app_ctx_stack.top
...     dill.dumps(appctx)
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 294, in dumps
    dump(obj, file, protocol, byref, fmode, recurse)#, strictio)
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 287, in dump
    pik.dump(obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 437, in dump
    self.save(obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 549, in save
    self.save_reduce(obj=obj, *rv)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 662, in save_reduce
    save(state)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 902, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 856, in save_dict
    self._batch_setitems(obj.items())
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 882, in _batch_setitems
    save(v)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 549, in save
    self.save_reduce(obj=obj, *rv)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 662, in save_reduce
    save(state)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 902, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 856, in save_dict
    self._batch_setitems(obj.items())
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 882, in _batch_setitems
    save(v)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 816, in save_list
    self._batch_appends(obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 843, in _batch_appends
    save(tmp[0])
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 1386, in save_function
    obj.__dict__), obj=obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 638, in save_reduce
    save(args)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 786, in save_tuple
    save(element)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 771, in save_tuple
    save(element)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 1129, in save_cell
    pickler.save_reduce(_create_cell, (f,), obj=obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 638, in save_reduce
    save(args)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 771, in save_tuple
    save(element)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 549, in save
    self.save_reduce(obj=obj, *rv)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 662, in save_reduce
    save(state)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 902, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 856, in save_dict
    self._batch_setitems(obj.items())
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 882, in _batch_setitems
    save(v)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 549, in save
    self.save_reduce(obj=obj, *rv)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 662, in save_reduce
    save(state)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 902, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 856, in save_dict
    self._batch_setitems(obj.items())
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 882, in _batch_setitems
    save(v)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 549, in save
    self.save_reduce(obj=obj, *rv)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 662, in save_reduce
    save(state)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 902, in save_module_dict
    StockPickler.save_dict(pickler, obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 856, in save_dict
    self._batch_setitems(obj.items())
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 882, in _batch_setitems
    save(v)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 504, in save
    f(self, obj) # Call unbound method with explicit self
  File "/Users/dchevell/Development/python3/sandbox/env/lib/python3.7/site-packages/dill/_dill.py", line 1330, in save_type
    StockPickler.save_global(pickler, obj)
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 957, in save_global
    (obj, module_name, name)) from None
_pickle.PicklingError: Can't pickle <class 'sqlalchemy.orm.session.SignallingSession'>: it's not found as sqlalchemy.orm.session.SignallingSession

那么:有什么办法可以解决这个问题吗?我的一个想法是“及时”分叉一个新进程 - 即在我想运行我的后台任务之前 - 可能意味着我根本不需要复制上下文,它应该已经有了它分叉发生在上下文设置之后。但是,我不知道如何使用ProcessPoolExecutor 进行这项工作,其中进程是一个长期存在的池。我唯一的想法是以某种方式强制工作进程在每个任务结束时关闭,但我很确定这只会导致池损坏。

【问题讨论】:

这是一个非常巧妙的问题,因此是一个巨大的 +1。你有任何进展吗?我试图在烧瓶中使用带有opencv的futures,但是futures + opencv不能很好地发挥作用,这将我推向了loky,这导致了我当前的问题。 【参考方案1】:

不确定这是否适合您,但有可能使用来自其他进程的 Flask 应用程序上下文。它要求您使用application factory 模式。

# your_project/app.py

def create_app(config_file):
    # Load the config file somehow
    # Create your app instance
    app = Flask(__name__)
    # Initialize your extensions
    db.init_app(app)
    # Mount routes, etc
    app.register_blueprint(...)
    # Return your app
    return app

# your_project/wsgi.py
from .app import create_app

app = create_app("./config/local.cfg")

您定义了一个应用程序工厂并使用它来创建您的应用程序作为wsgi.py 或其他方便位置的入口点,然后您可以从后台脚本访问此应用程序实例,该脚本将在另一个进程中运行。


# your_project/background_script.py
from .wsgi import app

with app.app_context():
    # Do some stuff

顺便说一句,这也是将 Flask 与 Celery 一起使用的一种方式,例如,如 in this question 所述。 希望对您有所帮助。

【讨论】:

以上是关于将烧瓶请求/应用程序上下文复制到另一个进程的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中将字节数组发送到另一个进程

从烧瓶启动分离过程

将绑定到 FBO 的纹理复制到另一个 OpenGL 上下文

烧瓶上的POST请求失败[重复]

在同一进程中和两个进程之间将字符串从一个指针复制到另一个指针

带有芹菜的烧瓶 - 应用程序上下文不可用