金字塔中的随机NoTransaction

问题描述 投票:0回答:1

我不知道该如何识别 transaction.interfaces.NoTransaction 在我的金字塔应用程序中出现错误。我没有看到错误发生的任何模式,所以对我来说,这很随机。

这个应用是一个(半)RESTful API,使用SQLAlchemy和MySQL。我目前运行在一个docker容器中,该容器连接到同一主机操作系统上的一个外部(裸机)MySQL实例。

以下是App内一次登录尝试的堆栈跟踪。这个错误就发生在另一个实际成功的登录尝试之后。

2020-06-15 03:57:18,982 DEBUG [txn.140501728405248:108][waitress-1] new transaction
2020-06-15 03:57:18,984 INFO  [sqlalchemy.engine.base.Engine:730][waitress-1] BEGIN (implicit)
2020-06-15 03:57:18,984 DEBUG [txn.140501728405248:576][waitress-1] abort
2020-06-15 03:57:18,985 ERROR [waitress:357][waitress-1] Exception while serving /auth
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/waitress/channel.py", line 350, in service
    task.service()
  File "/usr/local/lib/python3.8/site-packages/waitress/task.py", line 171, in service
    self.execute()
  File "/usr/local/lib/python3.8/site-packages/waitress/task.py", line 441, in execute
    app_iter = self.channel.server.application(environ, start_response)
  File "/usr/local/lib/python3.8/site-packages/pyramid/router.py", line 270, in __call__
    response = self.execution_policy(environ, self)
  File "/usr/local/lib/python3.8/site-packages/pyramid_retry/__init__.py", line 127, in retry_policy
    response = router.invoke_request(request)
  File "/usr/local/lib/python3.8/site-packages/pyramid/router.py", line 249, in invoke_request
    response = handle_request(request)
  File "/usr/local/lib/python3.8/site-packages/pyramid_tm/__init__.py", line 178, in tm_tween
    reraise(*exc_info)
  File "/usr/local/lib/python3.8/site-packages/pyramid_tm/compat.py", line 36, in reraise
    raise value
  File "/usr/local/lib/python3.8/site-packages/pyramid_tm/__init__.py", line 135, in tm_tween
    userid = request.authenticated_userid
  File "/usr/local/lib/python3.8/site-packages/pyramid/security.py", line 381, in authenticated_userid
    return policy.authenticated_userid(self)
  File "/opt/REDACTED-api/REDACTED_api/auth/policy.py", line 208, in authenticated_userid
    result = self._authenticate(request)
  File "/opt/REDACTED-api/REDACTED_api/auth/policy.py", line 199, in _authenticate
    session = self._get_session_from_token(token)
  File "/opt/REDACTED-api/REDACTED_api/auth/policy.py", line 320, in _get_session_from_token
    session = service.get(session_id)
  File "/opt/REDACTED-api/REDACTED_api/service/__init__.py", line 122, in get
    entity = self.queryset.filter(self.Meta.model.id == entity_id).first()
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3375, in first
    ret = list(self[0:1])
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3149, in __getitem__
    return list(res)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3481, in __iter__
    return self._execute_and_instances(context)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3502, in _execute_and_instances
    conn = self._get_bind_args(
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3517, in _get_bind_args
    return fn(
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3496, in _connection_from_session
    conn = self.session.connection(**kw)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection
    return self._connection_for_bind(
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind
    return self.transaction._connection_for_bind(
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 458, in _connection_for_bind
    self.session.dispatch.after_begin(self.session, self, conn)
  File "/usr/local/lib/python3.8/site-packages/sqlalchemy/event/attr.py", line 322, in __call__
    fn(*args, **kw)
  File "/usr/local/lib/python3.8/site-packages/zope/sqlalchemy/datamanager.py", line 268, in after_begin
    join_transaction(
  File "/usr/local/lib/python3.8/site-packages/zope/sqlalchemy/datamanager.py", line 233, in join_transaction
    DataManager(
  File "/usr/local/lib/python3.8/site-packages/zope/sqlalchemy/datamanager.py", line 89, in __init__
    transaction_manager.get().join(self)
  File "/usr/local/lib/python3.8/site-packages/transaction/_manager.py", line 91, in get
    raise NoTransaction()
transaction.interfaces.NoTransaction

跟踪显示,执行最终到达了我的项目,但只是我的自定义认证策略。而且它在应该查询用户的数据库的地方失败了。

这里让我感兴趣的是堆栈跟踪中的第三行。似乎Waitress以某种方式中止了它创建的事务?有什么原因吗?


EDIT: 这里是发生这种情况的代码。policy.py:320

def _get_session_from_token(self, token) -> UserSession:
    try:
        session_id, session_secret = self.parse_token(token)
    except InvalidToken as e:
        raise SessionNotFound(e)

    service = AuthService(self.dbsession, None)

    try:
        session = service.get(session_id)  # <---- Service Class called here 
    except NoResultsFound:
        raise SessionNotFound("Invalid session found Request headers. "
                              "Session id: %s".format(session_id))

    if not service.check_session(session, session_secret):
        raise SessionNotFound("Session signature does not match")

    now = datetime.now(tz=pytz.UTC)
    if session.validity < now:
        raise SessionNotFound(
            "Current session ID {session_id} is expired".format(
                session_id=session.id
            )
        )

    return session

这里是服务类方法的一个视图

class AuthService(ModelService):
    class Meta:
        model = UserSession
        queryset = Query(UserSession)
        search_fields = []
        order_fields = [UserSession.created_at.desc()]

    # These below are from the generic ModelClass father class
    def __init__(self, dbsession: Session, user_id: str):
        self.user_id = user_id
        self.dbsession = dbsession
        self.Meta.queryset = self.Meta.queryset.with_session(dbsession)
        self.logger = logging.getLogger("REDACTED")

    @property
    def queryset(self):
        return self.Meta.queryset

    def get(self, entity_id) -> Base:
        entity = self.queryset.filter(self.Meta.model.id == entity_id).first()

        if not entity:
            raise NoResultsFound(f"Could not find requested ID {entity_id}")

正如你所看到的,已经有了一些异常处理。我真的不知道还有什么其他的异常,我可以尝试着去捕捉 AuthService.get

python mysql transactions pyramid waitress
1个回答
0
投票

我发现这个解决方案比在Pyramid或SQLAlchemy里修修补补要简单得多。

仔细调试我的身份验证策略,我发现我的它是 粘性参考 dbsession. 它被存储在有史以来第一个使用它的请求上,从未被释放。

第一个请求按预期工作,下面的请求失败了。我的理解是,当应用程序运行时,对象仍然在内存中, 而在初始事务关闭后。第二个请求有一个新的连接,也有一个新的事务,但内存中的对象仍然指向之前的对象,当使用时最终导致了这一点。

我不明白的是,为什么异常没有发生呢?有时. 正如我最初提到的,它看起来是随机的。

另一件我很费劲的事情是在写一个测试用例来暴露这个问题。在我的测试中,这个问题从来没有发生过,因为我在整个测试会话中只有一个连接和一个事务,而不是每个请求都有一个新的连接transaction,所以我没有发现没有办法实际重现。

请告诉我这是否有意义,如果你能阐明如何在测试用例上暴露这个bug,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.