0

我无法识别transaction.interfaces.NoTransactionPyramid 应用程序中的错误来源。我看不到错误发生时的任何模式,所以对我来说这是非常随机的。

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

这是应用程序中登录尝试的堆栈跟踪。此错误发生在另一次实际成功的登录尝试之后。

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 不知何故中止了它创建的交易?任何线索为什么?


编辑:这是发生这种情况的代码: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

4

1 回答 1

1

我发现解决方案比在 Pyramid 或 SQLAlchemy 中进行修补要简单得多。

仔细调试我的身份验证策略,我发现我的它dbsession. 它存储在使用它的第一个请求中,并且从未发布过。

第一个请求按预期工作,以下一个失败:我的理解是该对象在应用程序运行时仍在内存中,并且在初始事务关闭后。第二个请求有一个新的连接和一个新的事务,但内存中的对象仍然指向前一个,当使用时最终会导致这种情况。

我不明白为什么有时没有发生异常。正如我最初提到的,这似乎是随机的。

我挣扎的另一件事是编写一个测试用例来暴露问题。在我的测试中,这个问题从未发生过,因为我在整个测试会话中拥有(而且我从未见过它以不同的方式完成)一个连接和一个事务,而不是每个请求一个新的连接/事务,所以我没有没有找到真正重现的方法。

请让我知道这是否有意义,以及您是否可以阐明如何在测试用例中暴露该错误。

于 2020-06-16T03:09:31.437 回答