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

9032 HostnameEndpoint Memory Reactor #740

Merged
merged 33 commits into from
May 23, 2017

Conversation

markrwilliams
Copy link
Member

http://twistedmatrix.com/trac/ticket/9032

This aims to fix a release blocker.

markrwilliams and others added 14 commits February 24, 2017 16:51
…reactor

adapt the reactor instead of the application
…Endpoint.

Add deprecation warning.

Fix a bug in _SimpleHostnameResolver._deliver.

Tests for _SimpleHostnameResolver and the revived HostnameEndpoint
behavior.
This reduces the overall diff against trunk and eliminates an
opportunity for twistedchecker to complain about missing docstrings.
The refactoring allows the deprecation warning to sit in
_StandardEndpoint.__init__, which is one stack frame closer to
Agent.__init__.  It also allows for docstrings that link to a comment
in 9032.
@codecov
Copy link

codecov bot commented Mar 11, 2017

Codecov Report

Merging #740 into trunk will increase coverage by 0.01%.
The diff coverage is 99.42%.

@@            Coverage Diff             @@
##            trunk     #740      +/-   ##
==========================================
+ Coverage   89.99%      90%   +0.01%     
==========================================
  Files         841      841              
  Lines      146544   146710     +166     
  Branches    12848    12856       +8     
==========================================
+ Hits       131884   132049     +165     
  Misses      12291    12291              
- Partials     2369     2370       +1

@markrwilliams
Copy link
Member Author

markrwilliams commented Mar 11, 2017

There's an unrelated OpenSSL issue that's causing the Windows and macOS builders to fail.

EDIT #743 is an attempt at fixing those.

Copy link
Contributor

@tomprince tomprince left a comment

Choose a reason for hiding this comment

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

This looks like a good improvement. There are a couple of small changes, and I didn't have time to look at the tests.

reactor.__class__,
Version("Twisted", "NEXT", 0, 0),
format=("Passing HostnameEndpoint %(fqpn)s"
" was deprecated in %(version)s"),
Copy link
Contributor

Choose a reason for hiding this comment

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

It isn't clear to me that this should be calling out passing a specific type as being deprecated. I think it would be better to simply say passing a reactor that doesn't implement IReactorPluggableNameResolver is deprecated. Particularly since the fix might be to make the reactor implement the new interface, rather than changing the reactor being passed.

Copy link
Member Author

Choose a reason for hiding this comment

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

The log message should include the interface required, but I'd like it to also include the FQPN of the reactor to make debugging tests easier.

Copy link
Contributor

Choose a reason for hiding this comment

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

Including the FQPN of the reactor seems fine. But I'd go with something like:

Passing a reactor that doesn't implement IReactorPluggableNameResolver to HostnameEndpoint was deprecated <...>. (%(fqpn)s was passed, which doesn't currently implement <...>

that makes it clear that a possible fix is making the reactor implement the given interface.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess it already does mention the interface required of the reactor.

@@ -1425,7 +1425,7 @@ def test_deprecatedDuckPolicy(self):
WebClientContextFactory} to do it.
"""
def warnMe():
client.Agent(MemoryReactorClock(),
client.Agent(deterministicResolvingReactor(MemoryReactorClock()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you file a ticket for either making this public, or extending MemoryReactorClock with this functionality?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

twisted.testing HERE WE COME

@@ -0,0 +1 @@
twisted.internet.endpoints.HostnameEndpoint and twisted.web.client.Agent work again with reactors that do not provide IReactorPluggableNameResolver. This undoes the changes that broke downstream users such as treq.testing. Note that passing reactors that do not provide IReactorPluggableNameResolver to either is deprecated.
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 this would be better split into two news files. One for the bugfix, and another one for the deprecation. Also, I don't think it makes sense to call out that this is undoing a previous change: the new behavior is actually different than the old and the point isn't to undo it, but just provide proper deprecation warnings for the change.


@ivar _nameResolution: the callable L{resolveHostName} invokes to
resolve hostnames.
@type _nameResolution: A L{callable} with the same signature as
Copy link
Contributor

Choose a reason for hiding this comment

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

There are some missing words here.

resolutionReceiver.resolutionBegan(HostResolution(hostName))
d = self._nameResolution(hostName, portNumber)
d.addCallback(self._deliver, resolutionReceiver)
d.addErrback(self._ebLog, hostName)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think there is any value in having these callbacks out-of-line. There is a bunch of code like this in twisted, but I think that newer code tends to avoid it.

Copy link
Member Author

Choose a reason for hiding this comment

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

It does - I took this approach because I had pretensions of testing each of these methods. But coverage is good, so I might as well switch back to closures.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also the docstrings looked nicer on instance methods than they did on closures.

Copy link
Contributor

Choose a reason for hiding this comment

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

If they are closures, they might not need docstrings (certainly they could do with more abbreviated docstrings in that case).

Copy link
Member Author

Choose a reason for hiding this comment

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

They don't - I found them useful while writing the patch, so I thought I'd leave them in.

def _nameResolution(self, host, port):
"""
Resolve the hostname string into a tuple containing the host
address.
Copy link
Contributor

Choose a reason for hiding this comment

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

It would probably be good to make it clear at least in the docstring and perhaps in the method name that this method is just used as a fallback when the reactor provided to the endpoint doesn't implement the new interface.

return value.
"""
return self._deferToThread(self._getaddrinfo, host, port, 0,
socket.SOCK_STREAM)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure that it makes sense to allow overriding deferToThread and getaddrinfo individually, instead of just overriding this function.

Copy link
Member Author

Choose a reason for hiding this comment

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

deferToThread has to be replaceable to make this function testable. The original implementation also made getaddrinfo replaceable, apparently with the intent of verifying that deferToThread invokes it.

I was inclined to leave these two staticmethods in for two reasons:

  1. My tests looked more like the existing ones, apparently verifying the same behaviors.
  2. A vague concern that some tests, inside or outside Twisted itself, relied on it.

A cursory GitHub search indicated that 2. wasn't the case. 1. remains true and useful, I think.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at the tests, everywhere that ._deferToThread and ._getaddrinfo are overridden could be replaced by

self._nameResolution = fakegetaddrinfo

(modulo fakegetaddrinfo taking some additional arguments that it ignores). That would mean that _nameResolution isn't covered by the tests but I don't think the coverage that it currently gets is meaningful, since in tests it is just calling fake function that aren't verified to be equivalent to the real thing anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

@tomprince I've filed a new ticket that we can use to track this improvement.


def connectArgs(self):
"""

Copy link
Contributor

Choose a reason for hiding this comment

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

There is an extra newline here.

Copy link
Member Author

Choose a reason for hiding this comment

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

twistedchecker complains if there isn't a newline here, and it complains if the return type isn't documented.

Copy link
Contributor

Choose a reason for hiding this comment

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

That seems like a bug in twistedchecker. Or an indication there should be some prose here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's a bug in twistedchecker. All of the other test classes that define connectArgs have the same docstring, minus the newline, presumably to quiet twistedchecker.

Copy link
Member

Choose a reason for hiding this comment

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

Please file & link the bug in twistedchecker so we know about it, per policy :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like @rodrigc filed the bug: twisted/twistedchecker#124

Copy link
Member

@glyph glyph left a comment

Choose a reason for hiding this comment

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

Some minor observations inline, but pending a clean CI run I think this is more than baked. Please address and land.


class HostnameEndpointFallbackNameResolutionTests(unittest.TestCase):
"""
Verify that L{HostnameEndpoint._fallbackNameResolution} defers a
Copy link
Member

Choose a reason for hiding this comment

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


def test_fallbackNameResolution(self):
"""
A L{Deferred} is returned that fires with the resoution of the
Copy link
Member

Choose a reason for hiding this comment

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

Active voice not passive voice, "_fallbackNameResolution returns a Deferred that..."


def connectArgs(self):
"""

Copy link
Member

Choose a reason for hiding this comment

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

Please file & link the bug in twistedchecker so we know about it, per policy :)


def test_endpointConnectFailure(self):
"""
If L{HostnameEndpoint.connect} is invoked and there is no server
Copy link
Member

Choose a reason for hiding this comment

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

"when" is preferable to "if", because it stipulates the existence of a supported configuration.

@@ -1425,7 +1425,7 @@ def test_deprecatedDuckPolicy(self):
WebClientContextFactory} to do it.
"""
def warnMe():
client.Agent(MemoryReactorClock(),
client.Agent(deterministicResolvingReactor(MemoryReactorClock()),
Copy link
Member

Choose a reason for hiding this comment

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

twisted.testing HERE WE COME

@markrwilliams
Copy link
Member Author

The failing Codecov status appears to be down to partial coverage on this line. That could be covered by resolving an IPv6 address.

We now skip tests that need IPv6 on platforms that don't support it, but the way we determine that needs to be generalized. Rather than introduce another instance of this function's definition in this patch, I'll merge with the slightly reduced coverage and address it in the patch for 9146.

@markrwilliams markrwilliams merged commit b278d2e into trunk May 23, 2017
@markrwilliams markrwilliams deleted the 9032-hostnameendpoint-old-memoryreactor branch May 23, 2017 21:45
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

4 participants