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

asynchronously resolve a name to multiple address objects #548

Merged
merged 42 commits into from
Nov 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b5503bb
interface proposal
glyph Sep 22, 2016
adb0b51
Merge remote-tracking branch 'origin/trunk' into 4362-multiresolve
glyph Oct 18, 2016
f493f55
wtb $NEXTVER
glyph Oct 18, 2016
4d4330b
document return type
glyph Oct 18, 2016
a28268e
enough ceremony to deterministically call a function
glyph Oct 18, 2016
f8e87ab
hrm. should this be a native string?
glyph Oct 18, 2016
ab7db3c
AF_* sensitivity
glyph Oct 19, 2016
5900339
topfile
glyph Oct 29, 2016
eef928f
Merge remote-tracking branch 'origin/trunk' into 4362-multiresolve
glyph Oct 29, 2016
3ef18ce
new style
glyph Oct 29, 2016
0b7919a
test for error case
glyph Oct 29, 2016
1a02f09
assertIs
glyph Oct 29, 2016
6f65252
implement the error case
glyph Oct 29, 2016
7f8fa2b
Merge branch 'trunk' into 4362-multiresolve
glyph Oct 29, 2016
3c1a225
write a test for code that codecov's complaining about
glyph Oct 29, 2016
53c10ab
oops
glyph Oct 29, 2016
8da4f9c
interface doc updates
glyph Nov 13, 2016
c2b1359
Merge remote-tracking branch 'origin/trunk' into 4362-multiresolve
glyph Nov 13, 2016
8037bf3
Merge branch 'trunk' into 4362-multiresolve
glyph Nov 18, 2016
ccf377f
address family selection
glyph Nov 18, 2016
d9d48ec
docstring adjustment
glyph Nov 18, 2016
6795570
pacify twistedchecker
glyph Nov 18, 2016
25d2451
Merge branch 'trunk' into 4362-multiresolve
glyph Nov 18, 2016
983ea33
Merge remote-tracking branch 'origin/4362-multiresolve' into 4362-mul…
glyph Nov 18, 2016
bcdab97
vws
glyph Nov 18, 2016
b59681e
twistedchecker clean
glyph Nov 18, 2016
4bf5601
tests & fix for various AF_ types
glyph Nov 18, 2016
819eed6
hoist constant
glyph Nov 18, 2016
537fabb
fewer lines
glyph Nov 18, 2016
86949be
less code more data
glyph Nov 18, 2016
402778d
None first
glyph Nov 18, 2016
8b0718f
no, Nones are bad
glyph Nov 18, 2016
efb07fe
socket types
glyph Nov 18, 2016
bd740c7
socktype
glyph Nov 18, 2016
0c9d6cc
compatibility layer
glyph Nov 18, 2016
9094566
distinct names since these need to live on the same object
glyph Nov 18, 2016
9cdc20b
twistedchecker
glyph Nov 18, 2016
06afa1e
forward and backward compatibility
glyph Nov 18, 2016
7555e09
defer acquisition of the threadpool
glyph Nov 18, 2016
65eebe9
twistedchecker
glyph Nov 18, 2016
7d3a61c
Merge branch 'trunk' into 4362-multiresolve
hawkowl Nov 26, 2016
6625d4b
too many newlines as per review
glyph Nov 26, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
263 changes: 263 additions & 0 deletions src/twisted/internet/_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
# -*- test-case-name: twisted.internet.test.test_resolver -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
IPv6-aware hostname resolution.

@see: L{IHostnameResolver}
"""

from __future__ import division, absolute_import

__metaclass__ = type
Copy link
Member

Choose a reason for hiding this comment

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

why do we need this

Copy link
Member Author

Choose a reason for hiding this comment

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

To avoid accidentally declaring an old-style class.


from socket import (getaddrinfo, AF_INET, AF_INET6, AF_UNSPEC, SOCK_STREAM,
SOCK_DGRAM, gaierror)

from zope.interface import implementer

from twisted.internet.interfaces import (IHostnameResolver, IHostResolution,
IResolverSimple, IResolutionReceiver)
from twisted.internet.error import DNSLookupError
from twisted.internet.defer import Deferred
from twisted.internet.threads import deferToThreadPool
from twisted.internet.address import IPv4Address, IPv6Address
from twisted.logger import Logger


@implementer(IHostResolution)
class HostResolution(object):
"""
The in-progress resolution of a given hostname.
"""

def __init__(self, name):
"""
Create a L{HostResolution} with the given name.
"""
self.name = name



_any = frozenset([IPv4Address, IPv6Address])

_typesToAF = {
frozenset([IPv4Address]): AF_INET,
frozenset([IPv6Address]): AF_INET6,
_any: AF_UNSPEC,
}

_afToType = {
AF_INET: IPv4Address,
AF_INET6: IPv6Address,
}

_transportToSocket = {
'TCP': SOCK_STREAM,
'UDP': SOCK_DGRAM,
}

_socktypeToType = {
SOCK_STREAM: 'TCP',
SOCK_DGRAM: 'UDP',
}



@implementer(IHostnameResolver)
class GAIResolver(object):
"""
L{IHostnameResolver} implementation that resolves hostnames by calling
L{getaddrinfo} in a thread.
"""

def __init__(self, reactor, getThreadPool=None, getaddrinfo=getaddrinfo):
"""
Create a L{GAIResolver}.

@param reactor: the reactor to schedule result-delivery on
@type reactor: L{IReactorThreads}

@param getThreadPool: a function to retrieve the thread pool to use for
scheduling name resolutions. If not supplied, the use the given
C{reactor}'s thread pool.
@type getThreadPool: 0-argument callable returning a
L{twisted.python.threadpool.ThreadPool}

@param getaddrinfo: a reference to the L{getaddrinfo} to use - mainly
parameterized for testing.
@type getaddrinfo: callable with the same signature as L{getaddrinfo}
"""
self._reactor = reactor
self._getThreadPool = (reactor.getThreadPool if getThreadPool is None
else getThreadPool)
self._getaddrinfo = getaddrinfo


def resolveHostName(self, resolutionReceiver, hostName, portNumber=0,
addressTypes=None, transportSemantics='TCP'):
"""
See L{IHostnameResolver.resolveHostName}

@param resolutionReceiver: see interface

@param hostName: see interface

@param portNumber: see interface

@param addressTypes: see interface

@param transportSemantics: see interface

@return: see interface
"""
pool = self._getThreadPool()
addressFamily = _typesToAF[_any if addressTypes is None
else frozenset(addressTypes)]
socketType = _transportToSocket[transportSemantics]
def get():
try:
return self._getaddrinfo(hostName, portNumber, addressFamily,
socketType)
except gaierror:
return []
d = deferToThreadPool(self._reactor, pool, get)
resolution = HostResolution(hostName)
resolutionReceiver.resolutionBegan(resolution)
@d.addCallback
def deliverResults(result):
for family, socktype, proto, cannoname, sockaddr in result:
addrType = _afToType[family]
resolutionReceiver.addressResolved(
addrType(_socktypeToType.get(socktype, 'TCP'), *sockaddr)
)
resolutionReceiver.resolutionComplete()
return resolution



@implementer(IHostnameResolver)
class SimpleResolverComplexifier(object):
"""
A converter from L{IResolverSimple} to L{IHostnameResolver}.
"""

_log = Logger()

def __init__(self, simpleResolver):
"""
Construct a L{SimpleResolverComplexifier} with an L{IResolverSimple}.
"""
self._simpleResolver = simpleResolver


def resolveHostName(self, resolutionReceiver, hostName, portNumber=0,
addressTypes=None, transportSemantics='TCP'):
"""
See L{IHostnameResolver.resolveHostName}

@param resolutionReceiver: see interface

@param hostName: see interface

@param portNumber: see interface

@param addressTypes: see interface

@param transportSemantics: see interface

@return: see interface
"""
resolution = HostResolution(hostName)
resolutionReceiver.resolutionBegan(resolution)
onAddress = self._simpleResolver.getHostByName(hostName)
def addressReceived(address):
resolutionReceiver.addressResolved(IPv4Address('TCP', address, 0))
def errorReceived(error):
if not error.check(DNSLookupError):
self._log.failure("while looking up {name} with {resolver}",
error, name=hostName,
resolver=self._simpleResolver)
onAddress.addCallbacks(addressReceived, errorReceived)
def finish(result):
resolutionReceiver.resolutionComplete()
onAddress.addCallback(finish)
return resolution



@implementer(IResolutionReceiver)
class FirstOneWins(object):
"""
An L{IResolutionReceiver} which fires a L{Deferred} with its first result.
"""

def __init__(self, deferred):
"""
@param deferred: The L{Deferred} to fire when the first resolution
result arrives.
"""
self._deferred = deferred
self._resolved = False


def resolutionBegan(self, resolution):
"""
See L{IResolutionReceiver.resolutionBegan}

@param resolution: See L{IResolutionReceiver.resolutionBegan}
"""
self._resolution = resolution


def addressResolved(self, address):
"""
See L{IResolutionReceiver.addressResolved}

@param address: See L{IResolutionReceiver.addressResolved}
"""
if self._resolved:
return
self._resolved = True
self._deferred.callback(address.host)


def resolutionComplete(self):
"""
See L{IResolutionReceiver.resolutionComplete}
"""
if self._resolved:
return
self._deferred.errback(DNSLookupError(self._resolution.name))



@implementer(IResolverSimple)
class ComplexResolverSimplifier(object):
"""
A converter from L{IHostnameResolver} to L{IResolverSimple}
"""
def __init__(self, nameResolver):
"""
Create a L{ComplexResolverSimplifier} with an L{IHostnameResolver}.

@param nameResolver: The L{IHostnameResolver} to use.
"""
self._nameResolver = nameResolver


def getHostByName(self, name, timeouts=()):
"""
See L{IResolverSimple.getHostByName}

@param name: see L{IResolverSimple.getHostByName}

@param timeouts: see L{IResolverSimple.getHostByName}

@return: see L{IResolverSimple.getHostByName}
"""
result = Deferred()
self._nameResolver.resolveHostName(FirstOneWins(result), name, 0,
[IPv4Address])
return result
15 changes: 15 additions & 0 deletions src/twisted/internet/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,23 @@ class IPv6Address(_IPAddress):
@ivar host: A string containing a colon-separated, hexadecimal formatted
IPv6 address; for example, "::1".
@type host: C{str}

@ivar flowInfo: the IPv6 flow label. This can be used by QoS routers to
identify flows of traffic; you may generally safely ignore it.
@type flowInfo: L{int}

@ivar scopeID: the IPv6 scope identifier - roughly analagous to what
interface traffic destined for this address must be transmitted over.
@type scopeID: L{int}
"""

compareAttributes = ('type', 'host', 'port', 'flowInfo', 'scopeID')

def __init__(self, type, host, port, flowInfo=0, scopeID=0):
super(IPv6Address, self).__init__(type, host, port)
self.flowInfo = flowInfo
self.scopeID = scopeID



@implementer(IAddress)
Expand Down
42 changes: 41 additions & 1 deletion src/twisted/internet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
from twisted.internet.interfaces import IResolverSimple, IReactorPluggableResolver
from twisted.internet.interfaces import IConnector, IDelayedCall
from twisted.internet import fdesc, main, error, abstract, defer, threads
from twisted.internet._resolver import (
GAIResolver as _GAIResolver,
ComplexResolverSimplifier as _ComplexResolverSimplifier,
SimpleResolverComplexifier as _SimpleResolverComplexifier,
)
from twisted.python import log, failure, reflect
from twisted.python.compat import unicode, iteritems
from twisted.python.runtime import seconds as runtimeSeconds, platform
Expand Down Expand Up @@ -487,6 +492,7 @@ def __init__(self):
self._startedBefore = False
# reactor internal readers, e.g. the waker.
self._internalReaders = set()
self._nameResolver = None
self.waker = None

# Arrange for the running attribute to change to True at the right time
Expand All @@ -509,12 +515,45 @@ def installWaker(self):
raise NotImplementedError(
reflect.qual(self.__class__) + " did not implement installWaker")


def installResolver(self, resolver):
"""
See L{IReactorPluggableResolver}.

@param resolver: see L{IReactorPluggableResolver}.

@return: see L{IReactorPluggableResolver}.
"""
assert IResolverSimple.providedBy(resolver)
oldResolver = self.resolver
self.resolver = resolver
self._nameResolver = _SimpleResolverComplexifier(resolver)
return oldResolver


def installNameResolver(self, resolver):
"""
See L{IReactorPluggableNameResolver}.

@param resolver: See L{IReactorPluggableNameResolver}.

@return: see L{IReactorPluggableNameResolver}.
"""
previousNameResolver = self._nameResolver
self._nameResolver = resolver
self.resolver = _ComplexResolverSimplifier(resolver)
return previousNameResolver


@property
def nameResolver(self):
"""
Implementation of read-only
L{IReactorPluggableNameResolver.nameResolver}.
"""
return self._nameResolver


def wakeUp(self):
"""
Wake up the event loop.
Expand Down Expand Up @@ -938,8 +977,9 @@ def argChecker(arg):
threadpoolShutdownID = None

def _initThreads(self):
self.installNameResolver(_GAIResolver(self, self.getThreadPool))
self.usingThreads = True
self.resolver = ThreadedResolver(self)


def callFromThread(self, f, *args, **kw):
"""
Expand Down