【odoo bug排查】psycopg2.InterfaceError: Cursor already closed

在这里插入图片描述

在这里插入图片描述

错误日志


Traceback (most recent call last):
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api.py", line 983, in get
    cache_value = field_cache[record._ids[0]]
KeyError: 3

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/fields.py", line 1132, in __get__
    value = env.cache.get(record, self)
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api.py", line 990, in get
    raise CacheMiss(record, field)
odoo.exceptions.CacheMiss: 'im.auto.test.api(3,).code'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api_server/handler.py", line 110, in handle_request
    result = f(self, *args, **kwargs, context=context)
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/api_server/decorator.py", line 506, in wrapper
    res = function(*args, **kwargs)
  File "/Users/mzwang/Documents/codes/python/im_test_system/addons/im_pressure/api/api_auto_test_api.py", line 109, in ts_api_im_auto_test_api_list
    total, data = auto_test_api_obj.list_auto_test_api_func(
  File "/Users/mzwang/Documents/codes/python/im_test_system/addons/im_pressure/models/im_auto_test_api_model.py", line 216, in list_auto_test_api_func
    return total, [
  File "/Users/mzwang/Documents/codes/python/im_test_system/addons/im_pressure/models/im_auto_test_api_model.py", line 218, in <listcomp>
    "code": model.code,
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/fields.py", line 1158, in __get__
    recs._fetch_field(self)
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/models.py", line 3157, in _fetch_field
    self._read(fnames)
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/models.py", line 3234, in _read
    cr.execute(query_str, params + [sub_ids])
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 312, in execute
    _logger.debug("query: %s", self._format(query, params))
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 303, in _format
    encoding = psycopg2.extensions.encodings[self.connection.encoding]
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 470, in __getattr__
    return getattr(self._obj, name)
  File "/Users/mzwang/Documents/codes/python/im_test_system/odoo/sql_db.py", line 469, in __getattr__
    raise psycopg2.InterfaceError("Cursor already closed")
psycopg2.InterfaceError: Cursor already closed
2024-04-16 09:48:32,253 94676 DEBUG im_test_system_dev odoo.sql_db: SUM from:0:00:00/0 [201] 

突然出现的项目bug,之前做的模块都没有出现这种情况,新增了一个基础接口模块,突然蹦出来,而且报错及其不稳定,有时候可以刷新,有时候连续几次都没有报错。但一定会出现就是了。

解决思路:

1. 看日志定位问题是什么
2. 发现日志有两处error,一一排查,第一个是odoo 缓存,第二个是pg cursor
3. 浪费了一下午的时间发现第一个缓存不是根本原因,只是因为之前就遇到过这个缓存问题,导致第一时间排查此
4. 继续排查第二个问题。
5. 报错信息为:psycopg2.InterfaceError: Cursor already closed 报错现象为单接口请求没问题,前端多个接口一起并发请求就会报错。所以第一时间想到的就是多线程情况下数据库连接异常关闭。
6. 测试定位排除:
	- 优先排除前端问题
	- 更换数据库,将本地数据库替换为测试环境的,依旧出现(排除odoo升级数据库异常)
	- 更换模块查询,没有问题(此时因为没有能发现稳定复现的机制,所以这个并没有什么意义)
	- 替换报错代码,注释掉则不会报错,替换函数内orm操作,发现也会出现异常。于是替换逻辑,仅用 time.sleep 模拟耗时,报错频率增加。(更加怀疑是odoo 本身的问题)
7. 向自己怀疑的目标前进,开始扒源码,看odoo对数据库连接的实现。
8. 打印日志,打印线程号打印cr对象。
9. 发现在出现异常的情况下,都会有数据库连接被释放。尝试在列表页接口添加 time.sleep ,竟然可以稳定复现报错。于是猜测是在并发执行过程中,A请求执行线程并没有完全释放掉cr,后一个B请求就已经拿到了同一个cr,如果B中存在耗时操作,A请求此时释放cr,B耗时操作完成着执行数据库查询,就会发现cr对象已经被释放。出现报错:psycopg2.InterfaceError: Cursor already closed

如何解决

1. 每个线程拿到的cr数据库连接,都应该是不同的才对

使用线程锁(Thread Lock): 在访问 cr 之前,可以使用线程锁来确保一次只有一个线程可以获取到 cr。这样可以防止多个线程同时操作 cr,从而避免数据竞争和不一致性。

为每个线程创建独立的数据库连接和游标: 可以为每个线程创建独立的数据库连接和游标,这样每个线程都可以独立地操作数据库,不会影响其他线程的操作。这种方法可以确保并发性,但也会增加数据库连接和资源的开销。

使用连接池(Connection Pool): 可以使用连接池来管理数据库连接,确保每个线程获取到的连接都是唯一的。连接池可以控制并发访问数据库的数量,以及确保连接的复用和有效性。这样可以避免因为连接过多而导致数据库性能下降,同时也可以减少连接的创建和销毁开销。

使用数据库事务(Database Transaction): 在需要操作数据库的地方,可以使用数据库事务来确保一系列操作的原子性和一致性。这样可以在一次事务中执行多个操作,并确保这些操作要么全部成功,要么全部失败,从而避免数据的不一致性。


使用线程局部存储(Thread Local Storage,TLS):在每个线程中维护一个独立的数据库连接对象。Python 提供了 threading.local() 类来实现线程局部存储,你可以在每个线程中创建一个独立的数据库连接对象,并在处理请求时使用该对象。这样可以确保每个线程使用的是独立的数据库连接,避免了多个线程之间的竞争和冲突。

使用连接池(Connection Pool):连接池是一种管理数据库连接的机制,它会在应用程序启动时创建一定数量的数据库连接,并将它们保存在一个池中。每个线程需要使用数据库连接时,可以从连接池中获取一个连接,并在使用完毕后将其放回连接池。这样可以避免多个线程同时使用同一个连接,提高了数据库连接的复用性和效率。

使用同步机制:在获取数据库连接时使用同步机制(如互斥锁)来确保只有一个线程可以获取连接,其他线程需要等待。这样可以避免多个线程同时获取同一个连接,但会影响性能。

使用数据库连接的上下文管理器:在使用数据库连接时,可以通过上下文管理器来确保连接在使用完毕后被正确关闭。Python 中的 with 语句可以很方便地实现这一点,确保在退出 with 块时自动关闭连接。
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值