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

treq.testing.StubTreq: fix persisting twisted.web.server.Session objects between requests #328

Merged
merged 5 commits into from May 13, 2021

Conversation

bennr01
Copy link
Contributor

@bennr01 bennr01 commented Apr 13, 2021

This PR contains my proposed fix for #327. For details about the issue and this fix, see said issue.

tl;dr of original issue: Previously, a resource wrapped by treq.testing.StubTreq could not properly use request.getSession(), as it would always create a new twisted.web.server.Session instead of returning the same one. This is because treq.testing.StubTreq always create a new instance of twisted.web.server.Site for each request. As sessions are stored within a Site instance, this meant that upon completion of a request, the original session was lost. This PR solves this issue by moving the twisted.web.server.Site instance to an attribute of the agent. Additionally, the new treq.testing.StubTreq.cleanSessions() method provides a simple way of expiring the sessions in order to avoid leaving behind a dirty/unclean reactor. Although I am not sure if this is the best way to do this.

Before this commit, twisted.web.server.Session objects were not persisted properly between requests.
Thus, two requests with the same cookies would result in two different sessions.
See twisted#327 for details.

def cleanSessions(self):
"""
Clean up sessions to prevent leaving behind a dirty reactor.
Copy link
Contributor

Choose a reason for hiding this comment

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

The behavior of leaving a dirty reactor looks like a bug in Twisted to me, actually. The session's delayed call should be registered with the reactor passed to Site.

Copy link
Contributor

Choose a reason for hiding this comment

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

I filed https://twistedmatrix.com/trac/ticket/10177 for this issue.

I think that it could be worked around here by overriding the site's sessionFactory to pass the memory reactor to the Session constructor. Then you wouldn't need this method.

Clean up sessions to prevent leaving behind a dirty reactor.
If you are using :obj:`StubTreq` with :obj:`twisted.web.server.Session`
objects, you most likely have to call this method once you are done,
for example during the tearDown of a unittest TestCase.
Copy link
Contributor

Choose a reason for hiding this comment

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

For stuff like this using self.addCleanup is almost always superior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@twm Are you suggesting replacing this whole method to somehow use said function or to change the documentation?

Copy link
Contributor

Choose a reason for hiding this comment

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

To change the documentation, but it's a moot point if this method is going away.

@twm
Copy link
Contributor

twm commented Apr 14, 2021

Thank you for the well-stated bug report and nicely polished PR! I think that the overall approach of reusing the Site is reasonable.

Would you find the cleanSessions method useful if it weren't necessary?

@bennr01
Copy link
Contributor Author

bennr01 commented Apr 14, 2021

Hi,

Would you find the cleanSessions method useful if it weren't necessary?

It depends, but I'm leaning towards "no". It is useful when using treq.testing.StubTreq for testing twisted.web applications and wanting to reset sessions between requests, but there may be better ways to do that.
During debugging I encountered some weird cookie behavior which I am not sure is intentional (the same cookies being used between requests, even when no cookies parameter was passed to the request or the .clear() was called on the cookies), so maybe it's not necessary even when one wants to have a new session during the same test_* function.

Still, the primarily concern for writing .cleanSessions() was to provide a way to avoid having the user access private (as in 'starting with a underscore') attributes and methods. So if there would be a better way I'd be in favor of dropping .cleanSessions(). It's an explicit call for something that should be (IMO) implicitly cleaned up.

Personally, I also feel like .cleanSessions() is too specific for what it's supposed to do. A more generic method for completely resetting all associated states would be a more clean interface.

Copy link
Contributor

@twm twm left a comment

Choose a reason for hiding this comment

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

Okay, then I'd say remove cleanSessions() entirely.

It is likely that we'll end up making some (all?) of these attributes public as part of #227. In practice tests that use StubTreq end up pretty tightly coupled to the details of these objects so I don't think it's useful to hide them — we couldn't make substantial internal changes without breaking folks' tests (even this PR has some Hyrum's Law risk in that regard, though I think it's acceptable).

Thanks!

@bennr01
Copy link
Contributor Author

bennr01 commented Apr 21, 2021

Hi,

I have now removed .cleanSessions() again. Instead, I've moved the code directly into the test with a note to remove it once ticket 10177 is fixed.

Copy link
Contributor

@twm twm left a comment

Choose a reason for hiding this comment

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

Getting closer! I've tried to clarify my prior feedback.

@@ -88,6 +88,7 @@ def __init__(self, rootResource):
reactor=self._memoryReactor,
endpointFactory=_EndpointFactory(self._memoryReactor))
self._rootResource = rootResource
self._serverFactory = Site(self._rootResource, reactor=self._memoryReactor)
Copy link
Contributor

Choose a reason for hiding this comment

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

I meant that you can cause the session to use the correct reactor by overriding sessionFactory here like this:

Suggested change
self._serverFactory = Site(self._rootResource, reactor=self._memoryReactor)
self._serverFactory = Site(self._rootResource, reactor=self._memoryReactor)
self._serverFactory.sessionFactory = lambda site, uid: Session(site, uid, reactor=self._memoryReactor)

Then you don't need the cleanup code in the test you added, and more importantly you don't need cleanup code in any test that uses RequestTraversalAgent.

# or expiring the sessions

# manually expire the sessions.
for sid in list(stub._agent._serverFactory.sessions.keys()):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that you could do this without poking all these private variables by making _SessionIdTestResource append each session it creates to a public attribute and traversing that attribute here.

@bennr01
Copy link
Contributor Author

bennr01 commented Apr 26, 2021

I've implemented the changes proposed above:

  • treq.testing.RequestTraversalAgent will now modify self._server.sessionFactory to always use the memory reactor (though seeing 10177: Sessions: use the site reactor, not the global one twisted#1587 this may have been uneccessary).
  • treq.test.test_testing._SessionIdTestResource will now keep track of the sessions it created and provide a method for expiring these sessions.

Copy link
Contributor

@twm twm left a comment

Choose a reason for hiding this comment

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

Looks great! Thank you very much.

Sorry I lost track of this... I will merge now, and try to release by the weekend.

Copy link
Contributor

@twm twm left a comment

Choose a reason for hiding this comment

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

Oh no, lint failure. :( Please fix that.

@bennr01
Copy link
Contributor Author

bennr01 commented May 12, 2021

@twm Oops, I forgot that I configured the flake8 I'm using to ignore E501... Should be fixed now.

Copy link
Contributor

@twm twm left a comment

Choose a reason for hiding this comment

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

Looks great! Thank you!

@twm twm merged commit b0c6172 into twisted:master May 13, 2021
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.

None yet

2 participants