Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Raise AttributeError on request.tm if tm inactive #46

Merged
merged 3 commits into from
Sep 6, 2016

Conversation

nickstenning
Copy link
Contributor

This PR addresses the issue raised in #45.

Raising an AttributeError for request.tm if the current request doesn't have an active transaction manager makes it much harder for an application to mistakenly get hold of a reference to a transaction manager which is not committed (or rolled back) at the end of the request.

There should never be any need to get a reference to the transaction manager during a request that has been opted out using an activate_hook. The ability to do so opens up possibilities for subtle bugs where database sessions are registered with a manager that isn't itself hooked up to the request lifecycle, resulting in leaked references to those sessions inside zope.sqlalchemy.

As suggested in #45, this is implemented by adding tm.active to the WSGI environment on the way through the tween, and removing it on the way out.

This will allow the `request.tm` request property to determine whether or
not the transaction manager is active for the current request and thus
whether to return a reference to the manager to the caller.
@nickstenning
Copy link
Contributor Author

I reckon it's probably time to do a bit of cleanup of the core loop in the tween, but I didn't want to confuse the issue by doing any refactoring in this PR. Happy to follow up with a bit of cleanup. Perhaps we can use TransactionManager#attempts rather than reimplementing that here?

nickstenning added a commit to hypothesis/h that referenced this pull request Aug 29, 2016
Opening a database session to load feature flags for these requests is
not only not necessary, it is quite problematic. It causes the leakage
of references to the database session inside `zope.sqlalchemy` because
these requests have been opted out of the transaction manager by an
`activate_hook` (see `h.app`).

By using the database in these requests, we were accidentally
registering a database session with a transaction manager that wasn't
hooked up to the request lifecycle. This meant that zope.sqlalchemy's
internal references to the session weren't properly cleaned up. In a
high traffic environment -- these leaked session references would then
later lead to a session (in an entirely different request) not being
correctly registered with the transaction manager.

I've also opened a pull request against `pyramid_tm` which should
prevent this class of bug in the future:

    Pylons/pyramid_tm#46
seanh pushed a commit to hypothesis/h that referenced this pull request Aug 30, 2016
Opening a database session to load feature flags for these requests is
not only not necessary, it is quite problematic. It causes the leakage
of references to the database session inside `zope.sqlalchemy` because
these requests have been opted out of the transaction manager by an
`activate_hook` (see `h.app`).

By using the database in these requests, we were accidentally
registering a database session with a transaction manager that wasn't
hooked up to the request lifecycle. This meant that zope.sqlalchemy's
internal references to the session weren't properly cleaned up. In a
high traffic environment -- these leaked session references would then
later lead to a session (in an entirely different request) not being
correctly registered with the transaction manager.

I've also opened a pull request against `pyramid_tm` which should
prevent this class of bug in the future:

    Pylons/pyramid_tm#46
@@ -50,6 +50,9 @@ def tm_tween(request):
if not activate(request):
return handler(request)

# Set a flag in the environment to enable the `request.tm` property.
request.environ['tm.active'] = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be a guard against something else setting tm.active similar to the guard against repoze.tm.active as transaction cannot be nested

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Added.

@mmerickel
Copy link
Member

I cannot remember the reason off the top of my head but pyramid_tm used to use .attempts iterator but it was reimplemented here for some reason.

This PR looks good, just address the one comment and add your name to CONTRIBUTORS.txt in the repo.

This makes it much harder for an application to mistakenly get hold of a
reference to the transaction manager during a request for which the
activate_hook returns `False`.

There should never be any need to get a reference to the transaction
manager during a request that has been opted out. The ability to do so
opens up possibilities for subtle bugs where database sessions are
registered with a manager that isn't itself hooked up to the request
lifecycle, resulting in leaked references to those sessions inside
`zope.sqlalchemy`.
@nickstenning
Copy link
Contributor Author

And it looks like we stopped using .attempts() because of a bug in transaction 1.2.0 which appears to have been fixed since 1.3.0.

So it may be that switching back to that will work fine as long as we're ok to require transaction>=1.3.0?

@mmerickel
Copy link
Member

Sure, you're welcome to consider that in a separate PR but I'm not likely to accept it quite yet... you'll see in #42 where going back to attempts might be an issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants