Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5037 from matrix-org/erikj/limit_inflight_dns
Browse files Browse the repository at this point in the history
Limit in flight DNS requests
  • Loading branch information
erikjohnston authored May 8, 2019
2 parents 1473058 + 02491e0 commit c8c069d
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog.d/5037.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Workaround bug in twisted where attempting too many concurrent DNS requests could cause it to hang due to running out of file descriptors.
83 changes: 82 additions & 1 deletion synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
import psutil
from daemonize import Daemonize

from twisted.internet import error, reactor
from twisted.internet import defer, error, reactor
from twisted.protocols.tls import TLSMemoryBIOFactory

import synapse
from synapse.app import check_bind_error
from synapse.crypto import context_factory
from synapse.util import PreserveLoggingContext
from synapse.util.async_helpers import Linearizer
from synapse.util.rlimit import change_resource_limit
from synapse.util.versionstring import get_version_string

Expand Down Expand Up @@ -99,6 +100,8 @@ def start_reactor(
logger (logging.Logger): logger instance to pass to Daemonize
"""

install_dns_limiter(reactor)

def run():
# make sure that we run the reactor with the sentinel log context,
# otherwise other PreserveLoggingContext instances will get confused
Expand Down Expand Up @@ -312,3 +315,81 @@ def setup_sentry(hs):
name = hs.config.worker_name if hs.config.worker_name else "master"
scope.set_tag("worker_app", app)
scope.set_tag("worker_name", name)


def install_dns_limiter(reactor, max_dns_requests_in_flight=100):
"""Replaces the resolver with one that limits the number of in flight DNS
requests.
This is to workaround https://twistedmatrix.com/trac/ticket/9620, where we
can run out of file descriptors and infinite loop if we attempt to do too
many DNS queries at once
"""
new_resolver = _LimitedHostnameResolver(
reactor.nameResolver, max_dns_requests_in_flight,
)

reactor.installNameResolver(new_resolver)


class _LimitedHostnameResolver(object):
"""Wraps a IHostnameResolver, limiting the number of in-flight DNS lookups.
"""

def __init__(self, resolver, max_dns_requests_in_flight):
self._resolver = resolver
self._limiter = Linearizer(
name="dns_client_limiter", max_count=max_dns_requests_in_flight,
)

def resolveHostName(self, resolutionReceiver, hostName, portNumber=0,
addressTypes=None, transportSemantics='TCP'):
# Note this is happening deep within the reactor, so we don't need to
# worry about log contexts.

# We need this function to return `resolutionReceiver` so we do all the
# actual logic involving deferreds in a separate function.
self._resolve(
resolutionReceiver, hostName, portNumber,
addressTypes, transportSemantics,
)

return resolutionReceiver

@defer.inlineCallbacks
def _resolve(self, resolutionReceiver, hostName, portNumber=0,
addressTypes=None, transportSemantics='TCP'):

with (yield self._limiter.queue(())):
# resolveHostName doesn't return a Deferred, so we need to hook into
# the receiver interface to get told when resolution has finished.

deferred = defer.Deferred()
receiver = _DeferredResolutionReceiver(resolutionReceiver, deferred)

self._resolver.resolveHostName(
receiver, hostName, portNumber,
addressTypes, transportSemantics,
)

yield deferred


class _DeferredResolutionReceiver(object):
"""Wraps a IResolutionReceiver and simply resolves the given deferred when
resolution is complete
"""

def __init__(self, receiver, deferred):
self._receiver = receiver
self._deferred = deferred

def resolutionBegan(self, resolutionInProgress):
self._receiver.resolutionBegan(resolutionInProgress)

def addressResolved(self, address):
self._receiver.addressResolved(address)

def resolutionComplete(self):
self._deferred.callback(())
self._receiver.resolutionComplete()

0 comments on commit c8c069d

Please sign in to comment.