server: increase RLIMIT_NOFILE to let us use more sockets

Linux defaults to a soft limit of 1024, which limits us to 512 simultaneous
non-transit-using connections. The transit relay runs in the same process, so
long-running relayed transfers will compete for those sockets too.

This raises the soft limit to equal the hard limit (if possible), or as much
as we can manage, if the soft limit was less than 10k. If the
resource.setrlimit calls aren't available (e.g. windows), or some other error
happens, this will log a message and continue without changing the limits.

closes #238
This commit is contained in:
Brian Warner 2017-07-31 14:14:06 -07:00
parent 0c679e74ce
commit d44c7d2c1a
2 changed files with 97 additions and 1 deletions

View File

@ -2,6 +2,7 @@
# a str on Python 2
from __future__ import print_function
import os, time, json
import resource
from twisted.python import log
from twisted.internet import reactor, endpoints
from twisted.application import service, internet
@ -103,8 +104,39 @@ class RelayServer(service.MultiService):
self._transit = transit
self._transit_service = transit_service
def increase_rlimits(self):
try:
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
except AttributeError:
log.msg("AttributeError during getrlimit, leaving it alone")
return
if soft >= 10000:
log.msg("RLIMIT_NOFILE.soft was %d, leaving it alone" % soft)
return
# OS-X defaults to soft=7168, and reports a huge number for 'hard',
# but won't accept anything more than soft=10240, so we can't just
# set soft=hard. Linux returns (1024, 1048576) and is fine with
# soft=hard. Cygwin is reported to return (256,-1) and accepts up to
# soft=3200. So we try multiple values until something works.
for newlimit in [hard, 10000, 3200, 1024]:
log.msg("changing RLIMIT_NOFILE from (%s,%s) to (%s,%s)" %
(soft, hard, newlimit, hard))
try:
resource.setrlimit(resource.RLIMIT_NOFILE, (newlimit, hard))
log.msg("setrlimit successful")
return
except ValueError as e:
log.msg("error during setrlimit: %s" % e)
continue
except:
log.msg("other error during setrlimit, leaving it alone")
log.err()
return
log.msg("unable to change rlimit, leaving it alone")
def startService(self):
service.MultiService.startService(self)
self.increase_rlimits()
log.msg("websocket listening on /wormhole-relay/ws")
log.msg("Wormhole relay server (Rendezvous and Transit) running")
if self._blur_usage:

View File

@ -3,7 +3,7 @@ import os, json, itertools, time
import mock
from twisted.trial import unittest
from twisted.python import log
from twisted.internet import reactor, defer
from twisted.internet import reactor, defer, endpoints
from twisted.internet.defer import inlineCallbacks, returnValue
from autobahn.twisted import websocket
from .common import ServerBase
@ -24,6 +24,70 @@ def easy_relay(
**kwargs
)
class RLimits(unittest.TestCase):
def test_rlimit(self):
def patch_s(name, *args, **kwargs):
return mock.patch("wormhole.server.server." + name, *args, **kwargs)
# We never start this, so the endpoints can be fake.
# serverFromString() requires bytes on py2 and str on py3, so this
# is easier than just passing "tcp:0"
ep = endpoints.TCP4ServerEndpoint(None, 0)
with patch_s("endpoints.serverFromString", return_value=ep):
s = server.RelayServer("fake", None, None)
noattrs = object()
fakelog = []
def checklog(*expected):
self.assertEqual(fakelog, list(expected))
fakelog[:] = []
NF = "NOFILE"
mock_NF = patch_s("resource.RLIMIT_NOFILE", NF)
with patch_s("log.msg", fakelog.append):
with patch_s("resource", noattrs):
s.increase_rlimits()
checklog("AttributeError during getrlimit, leaving it alone")
with mock_NF:
with patch_s("resource.getrlimit",
return_value=(20000, 30000)) as gr:
s.increase_rlimits()
self.assertEqual(gr.mock_calls, [mock.call(NF)])
checklog("RLIMIT_NOFILE.soft was 20000, leaving it alone")
with patch_s("resource.getrlimit",
return_value=(10, 30000)) as gr:
with patch_s("resource.setrlimit",
side_effect=TypeError("other")):
with patch_s("log.err") as err:
s.increase_rlimits()
self.assertEqual(err.mock_calls, [mock.call()])
checklog("changing RLIMIT_NOFILE from (10,30000) to (30000,30000)",
"other error during setrlimit, leaving it alone")
for maxlimit in [40000, 20000, 9000, 2000, 1000]:
def setrlimit(which, newlimit):
if newlimit[0] > maxlimit:
raise ValueError("nope")
return None
calls = []
expected = []
for tries in [30000, 10000, 3200, 1024]:
calls.append(mock.call(NF, (tries, 30000)))
expected.append("changing RLIMIT_NOFILE from (10,30000) to (%d,30000)" % tries)
if tries > maxlimit:
expected.append("error during setrlimit: nope")
else:
expected.append("setrlimit successful")
break
else:
expected.append("unable to change rlimit, leaving it alone")
with patch_s("resource.setrlimit",
side_effect=setrlimit) as sr:
s.increase_rlimits()
self.assertEqual(sr.mock_calls, calls)
checklog(*expected)
class _Util:
def _nameplate(self, app, name):
np_row = app._db.execute("SELECT * FROM `nameplates`"