我正在寻找获得与请求无关的数据库会话的最佳方法(正确的方法)。
问题如下:我正在构建一个必须访问数据库的Web应用程序。公开的端点接受一个请求,执行第一个工作,然后创建一个线程(将执行艰苦的工作),启动该线程,并使用“作业”的唯一ID答复客户端。同时,线程继续其工作(并且它必须访问数据库),并且客户端可以执行轮询以检查状态。我不是使用专用框架来执行此后台工作,而是仅使用一个简单线程。我只能在任何时候运行一个后台线程,因此我将状态保持为单例。
应用程序是使用应用程序工厂设计https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/
创建的我将Gunicorn用作WSGI服务器,将sqlite用作数据库。
代码的基本结构如下(我正在删除业务逻辑和导入,但是概念仍然存在)
api_jobs.py
@bp.route('/jobs', methods=['POST'])
def create_job():
data = request.get_json(force=True) or {}
name = data['name']
job_controller = JobController() # This is a singleton
job_process = job_controller.start_job(name)
job_process_dict = job_process.to_dict()
return jsonify(job_process_dict)
controller.py
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class JobController(object):
__metaclass__ = Singleton
def __init__(self):
self.job_thread = None
def start_job(self, name):
if self.job_thread is not None:
job_id = self.job_thread.job_id
job_process = JobProcess.query.get(job_id)
if job_process.status != 'end':
raise ValueError('A job process is already ongoing!')
else:
self.job_thread = None
job_process = JobProcess(name)
db.session.add(job_process)
db.session.commit() # At this step I create the ID
self.job_thread = JobThread(db.session, job_process.id)
self.job_thread.start()
return job_process
class JobThread(threading.Thread):
def __init__(self, db_session, job_id):
self.job_id = job_id
self.db_session = db_session
self.session = self.db_session()
def run(self):
self.job_process = self.session.query(JobProcess).get(self.job_id)
self.job_process.status = 'working'
self.session.commit()
i = 0
while True:
sleep(1)
print('working hard')
i = i +1
if i > 10:
break
self.job_process.status = 'end'
self.session.commit()
self.db_session.remove()
models.py
class JobProcess(db.Model):
id = db.Column(db.Integer, primary_key=True)
status = db.Column(db.String(64))
name = db.Column(db.String(64))
def to_dict(self):
data = {
'id': self.id,
'status': self.status,
'name': self.name,
}
return data
根据我的理解,调用self.session = self.db_session()
实际上没有任何作用(由于sqlalchemy使用注册表,如果我没有记错的话,它也是代理),但这是我的最佳尝试发现创建了一个“新的/分离的/有用的”会话。
我签出https://docs.sqlalchemy.org/en/13/orm/contextual.html#using-thread-local-scope-with-web-applications是为了获得独立于请求的数据库会话,但是即使使用建议的创建新会话工厂的方法(sessionmaker + scoped_session),也无法正常工作。
在代码稍作更改的情况下,我所获得的错误是多个,在此配置中,错误是
DetachedInstanceError: Instance <JobProcess at 0x7f875f81c350> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/bhk3)
基本问题仍然存在:是否可以创建一个将驻留在线程内的会话,并且我将负责创建/删除会话?
答案 0 :(得分:0)
遇到DetachedInstanceError的原因是,您试图将会话从主线程传递到作业线程。 Sqlalchemy使用线程本地存储来管理会话,因此无法在两个线程之间共享单个会话。您只需要在作业线程的run方法中创建一个新会话。