Merge pull request #601 from tahoe-lafs/3026.more-eaddrinuse-fixes

Fix more test suite code that can spuriously trip over EADDRINUSE

Fixes: ticket:3026
This commit is contained in:
Jean-Paul Calderone 2019-04-17 16:22:45 -04:00 committed by GitHub
commit 48a2b42ec1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 78 deletions

0
newsfragments/3026.minor Normal file
View File

View File

@ -680,12 +680,8 @@ def create_control_tub():
port or location
"""
control_tub = Tub()
portnum = iputil.allocate_tcp_port()
port = "tcp:%d:interface=127.0.0.1" % portnum
location = "tcp:127.0.0.1:%d" % portnum
control_tub.listenOn(port)
control_tub.setLocation(location)
log.msg("Control Tub location set to %s" % (location,))
portnum = iputil.listenOnUnused(control_tub)
log.msg("Control Tub location set to 127.0.0.1:%s" % (portnum,))
return control_tub
@ -767,12 +763,8 @@ class Node(service.MultiService):
# to use "flogtool tail" against a remote server), but for now I
# think we can live without it.
self.log_tub = Tub()
portnum = iputil.allocate_tcp_port()
port = "tcp:%d:interface=127.0.0.1" % portnum
location = "tcp:127.0.0.1:%d" % portnum
self.log_tub.listenOn(port)
self.log_tub.setLocation(location)
self.log("Log Tub location set to %s" % (location,))
portnum = iputil.listenOnUnused(self.log_tub)
self.log("Log Tub location set to 127.0.0.1:%s" % (portnum,))
self.log_tub.setServiceParent(self)
def startService(self):

View File

@ -2,7 +2,6 @@
import os, re, itertools
from base64 import b32decode
import json
from socket import socket, AF_INET
from mock import Mock, patch
from testtools.matchers import (
@ -12,9 +11,6 @@ from testtools.matchers import (
from twisted.internet import defer, address
from twisted.python import log
from twisted.python.filepath import FilePath
from twisted.python.reflect import requireModule
from twisted.internet.endpoints import AdoptedStreamServerEndpoint
from twisted.internet.interfaces import IReactorSocket
from foolscap.api import Tub, Referenceable, fireEventually, flushEventualQueue
from twisted.application import service
@ -35,7 +31,10 @@ from allmydata.client import (
create_client,
create_introducer_clients,
)
from allmydata.util import pollmixin, keyutil, idlib, fileutil, iputil, yamlutil
from allmydata.util import pollmixin, keyutil, idlib, fileutil, yamlutil
from allmydata.util.iputil import (
listenOnUnused,
)
import allmydata.test.common_util as testutil
from .common import (
SyncTestCase,
@ -43,8 +42,6 @@ from .common import (
AsyncBrokenTestCase,
)
fcntl = requireModule("fcntl")
class LoggingMultiService(service.MultiService):
def log(self, msg, **kw):
log.msg(msg, **kw)
@ -373,64 +370,6 @@ class Server(AsyncTestCase):
NICKNAME = u"n\u00EDickname-%s" # LATIN SMALL LETTER I WITH ACUTE
def foolscapEndpointForPortNumber(portnum):
"""
Create an endpoint that can be passed to ``Tub.listen``.
:param portnum: Either an integer port number indicating which TCP/IPv4
port number the endpoint should bind or ``None`` to automatically
allocate such a port number.
:return: A two-tuple of the integer port number allocated and a
Foolscap-compatible endpoint object.
"""
if portnum is None:
# Bury this reactor import here to minimize the chances of it having
# the effect of installing the default reactor.
from twisted.internet import reactor
if fcntl is not None and IReactorSocket.providedBy(reactor):
# On POSIX we can take this very safe approach of binding the
# actual socket to an address. Once the bind succeeds here, we're
# no longer subject to any future EADDRINUSE problems.
s = socket()
try:
s.bind(('', 0))
portnum = s.getsockname()[1]
s.listen(1)
fd = os.dup(s.fileno())
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
flags = flags | os.O_NONBLOCK | fcntl.FD_CLOEXEC
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
return (
portnum,
AdoptedStreamServerEndpoint(reactor, fd, AF_INET),
)
finally:
s.close()
else:
# Get a random port number and fall through. This is necessary on
# Windows where Twisted doesn't offer IReactorSocket. This
# approach is error prone for the reasons described on
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2787
portnum = iputil.allocate_tcp_port()
return (portnum, "tcp:%d" % (portnum,))
def listenOnUnused(tub, portnum=None):
"""
Start listening on an unused TCP port number with the given tub.
:param portnum: Either an integer port number indicating which TCP/IPv4
port number the endpoint should bind or ``None`` to automatically
allocate such a port number.
:return: An integer indicating the TCP port number on which the tub is now
listening.
"""
portnum, endpoint = foolscapEndpointForPortNumber(portnum)
tub.listenOn(endpoint)
tub.setLocation("localhost:%d" % (portnum,))
return portnum
class SystemTestMixin(ServiceMixin, pollmixin.PollMixin):
def create_tub(self, portnum=None):

View File

@ -1,14 +1,18 @@
# from the Python Standard Library
import os, re, socket, subprocess, errno
from sys import platform
# from Twisted
from twisted.python.reflect import requireModule
from twisted.internet import defer, threads, reactor
from twisted.internet.protocol import DatagramProtocol
from twisted.internet.error import CannotListenError
from twisted.python.procutils import which
from twisted.python import log
from twisted.internet.endpoints import AdoptedStreamServerEndpoint
from twisted.internet.interfaces import IReactorSocket
fcntl = requireModule("fcntl")
from foolscap.util import allocate_tcp_port # re-exported
@ -239,6 +243,67 @@ def _cygwin_hack_find_addresses():
return defer.succeed(addresses)
def _foolscapEndpointForPortNumber(portnum):
"""
Create an endpoint that can be passed to ``Tub.listen``.
:param portnum: Either an integer port number indicating which TCP/IPv4
port number the endpoint should bind or ``None`` to automatically
allocate such a port number.
:return: A two-tuple of the integer port number allocated and a
Foolscap-compatible endpoint object.
"""
if portnum is None:
# Bury this reactor import here to minimize the chances of it having
# the effect of installing the default reactor.
from twisted.internet import reactor
if fcntl is not None and IReactorSocket.providedBy(reactor):
# On POSIX we can take this very safe approach of binding the
# actual socket to an address. Once the bind succeeds here, we're
# no longer subject to any future EADDRINUSE problems.
s = socket.socket()
try:
s.bind(('', 0))
portnum = s.getsockname()[1]
s.listen(1)
fd = os.dup(s.fileno())
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
flags = flags | os.O_NONBLOCK | fcntl.FD_CLOEXEC
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
return (
portnum,
AdoptedStreamServerEndpoint(reactor, fd, socket.AF_INET),
)
finally:
s.close()
else:
# Get a random port number and fall through. This is necessary on
# Windows where Twisted doesn't offer IReactorSocket. This
# approach is error prone for the reasons described on
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2787
portnum = allocate_tcp_port()
return (portnum, "tcp:%d" % (portnum,))
def listenOnUnused(tub, portnum=None):
"""
Start listening on an unused TCP port number with the given tub.
:param portnum: Either an integer port number indicating which TCP/IPv4
port number the endpoint should bind or ``None`` to automatically
allocate such a port number.
:return: An integer indicating the TCP port number on which the tub is now
listening.
"""
portnum, endpoint = _foolscapEndpointForPortNumber(portnum)
tub.listenOn(endpoint)
tub.setLocation("localhost:%d" % (portnum,))
return portnum
__all__ = ["allocate_tcp_port",
"increase_rlimits",
"get_local_addresses_sync",