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:
parent
0c679e74ce
commit
d44c7d2c1a
|
@ -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:
|
||||
|
|
|
@ -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`"
|
||||
|
|
Loading…
Reference in New Issue
Block a user