mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-30 01:38:55 +00:00
Merge pull request #1306 from meejah/4036.option-for-closing-stdin
Add an option for not exiting `tahoe run` when stdin closes. Fixes: ticket:4036
This commit is contained in:
commit
2304f77dfd
1
newsfragments/4036.feature
Normal file
1
newsfragments/4036.feature
Normal file
@ -0,0 +1 @@
|
||||
tahoe run now accepts --allow-stdin-close to mean "keep running if stdin closes"
|
@ -104,6 +104,11 @@ class RunOptions(BasedirOptions):
|
||||
" [default: %s]" % quote_local_unicode_path(_default_nodedir)),
|
||||
]
|
||||
|
||||
optFlags = [
|
||||
("allow-stdin-close", None,
|
||||
'Do not exit when stdin closes ("tahoe run" otherwise will exit).'),
|
||||
]
|
||||
|
||||
def parseArgs(self, basedir=None, *twistd_args):
|
||||
# This can't handle e.g. 'tahoe run --reactor=foo', since
|
||||
# '--reactor=foo' looks like an option to the tahoe subcommand, not to
|
||||
@ -156,6 +161,7 @@ class DaemonizeTheRealService(Service, HookMixin):
|
||||
"running": None,
|
||||
}
|
||||
self.stderr = options.parent.stderr
|
||||
self._close_on_stdin_close = False if options["allow-stdin-close"] else True
|
||||
|
||||
def startService(self):
|
||||
|
||||
@ -199,10 +205,12 @@ class DaemonizeTheRealService(Service, HookMixin):
|
||||
d = service_factory()
|
||||
|
||||
def created(srv):
|
||||
srv.setServiceParent(self.parent)
|
||||
if self.parent is not None:
|
||||
srv.setServiceParent(self.parent)
|
||||
# exiting on stdin-closed facilitates cleanup when run
|
||||
# as a subprocess
|
||||
on_stdin_close(reactor, reactor.stop)
|
||||
if self._close_on_stdin_close:
|
||||
on_stdin_close(reactor, reactor.stop)
|
||||
d.addCallback(created)
|
||||
d.addErrback(handle_config_error)
|
||||
d.addBoth(self._call_hook, 'running')
|
||||
@ -213,11 +221,13 @@ class DaemonizeTheRealService(Service, HookMixin):
|
||||
|
||||
class DaemonizeTahoeNodePlugin(object):
|
||||
tapname = "tahoenode"
|
||||
def __init__(self, nodetype, basedir):
|
||||
def __init__(self, nodetype, basedir, allow_stdin_close):
|
||||
self.nodetype = nodetype
|
||||
self.basedir = basedir
|
||||
self.allow_stdin_close = allow_stdin_close
|
||||
|
||||
def makeService(self, so):
|
||||
so["allow-stdin-close"] = self.allow_stdin_close
|
||||
return DaemonizeTheRealService(self.nodetype, self.basedir, so)
|
||||
|
||||
|
||||
@ -304,7 +314,9 @@ def run(reactor, config, runApp=twistd.runApp):
|
||||
print(config, file=err)
|
||||
print("tahoe %s: usage error from twistd: %s\n" % (config.subcommand_name, ue), file=err)
|
||||
return 1
|
||||
twistd_config.loadedPlugins = {"DaemonizeTahoeNode": DaemonizeTahoeNodePlugin(nodetype, basedir)}
|
||||
twistd_config.loadedPlugins = {
|
||||
"DaemonizeTahoeNode": DaemonizeTahoeNodePlugin(nodetype, basedir, config["allow-stdin-close"])
|
||||
}
|
||||
|
||||
# our own pid-style file contains PID and process creation time
|
||||
pidfile = FilePath(get_pidfile(config['basedir']))
|
||||
|
@ -1,16 +1,8 @@
|
||||
"""
|
||||
Tests for ``allmydata.scripts.tahoe_run``.
|
||||
|
||||
Ported to Python 3.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from future.utils import PY2
|
||||
if PY2:
|
||||
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from six.moves import (
|
||||
@ -31,6 +23,12 @@ from twisted.python.filepath import (
|
||||
from twisted.internet.testing import (
|
||||
MemoryReactor,
|
||||
)
|
||||
from twisted.python.failure import (
|
||||
Failure,
|
||||
)
|
||||
from twisted.internet.error import (
|
||||
ConnectionDone,
|
||||
)
|
||||
from twisted.internet.test.modulehelpers import (
|
||||
AlternateReactor,
|
||||
)
|
||||
@ -147,6 +145,91 @@ class DaemonizeTheRealServiceTests(SyncTestCase):
|
||||
)
|
||||
|
||||
|
||||
class DaemonizeStopTests(SyncTestCase):
|
||||
"""
|
||||
Tests relating to stopping the daemon
|
||||
"""
|
||||
def setUp(self):
|
||||
self.nodedir = FilePath(self.mktemp())
|
||||
self.nodedir.makedirs()
|
||||
config = ""
|
||||
self.nodedir.child("tahoe.cfg").setContent(config.encode("ascii"))
|
||||
self.nodedir.child("tahoe-client.tac").touch()
|
||||
|
||||
# arrange to know when reactor.stop() is called
|
||||
self.reactor = MemoryReactor()
|
||||
self.stop_calls = []
|
||||
|
||||
def record_stop():
|
||||
self.stop_calls.append(object())
|
||||
self.reactor.stop = record_stop
|
||||
|
||||
super().setUp()
|
||||
|
||||
def _make_daemon(self, extra_argv: list[str]) -> DaemonizeTheRealService:
|
||||
"""
|
||||
Create the daemonization service.
|
||||
|
||||
:param extra_argv: Extra arguments to pass between ``run`` and the
|
||||
node path.
|
||||
"""
|
||||
options = parse_options(["run"] + extra_argv + [self.nodedir.path])
|
||||
options.stdout = StringIO()
|
||||
options.stderr = StringIO()
|
||||
options.stdin = StringIO()
|
||||
run_options = options.subOptions
|
||||
return DaemonizeTheRealService(
|
||||
"client",
|
||||
self.nodedir.path,
|
||||
run_options,
|
||||
)
|
||||
|
||||
def _run_daemon(self) -> None:
|
||||
"""
|
||||
Simulate starting up the reactor so the daemon plugin can do its
|
||||
stuff.
|
||||
"""
|
||||
# We happen to know that the service uses reactor.callWhenRunning
|
||||
# to schedule all its work (though I couldn't tell you *why*).
|
||||
# Make sure those scheduled calls happen.
|
||||
waiting = self.reactor.whenRunningHooks[:]
|
||||
del self.reactor.whenRunningHooks[:]
|
||||
for f, a, k in waiting:
|
||||
f(*a, **k)
|
||||
|
||||
def _close_stdin(self) -> None:
|
||||
"""
|
||||
Simulate closing the daemon plugin's stdin.
|
||||
"""
|
||||
# there should be a single reader: our StandardIO process
|
||||
# reader for stdin. Simulate it closing.
|
||||
for r in self.reactor.getReaders():
|
||||
r.connectionLost(Failure(ConnectionDone()))
|
||||
|
||||
def test_stop_on_stdin_close(self):
|
||||
"""
|
||||
We stop when stdin is closed.
|
||||
"""
|
||||
with AlternateReactor(self.reactor):
|
||||
service = self._make_daemon([])
|
||||
service.startService()
|
||||
self._run_daemon()
|
||||
self._close_stdin()
|
||||
self.assertEqual(len(self.stop_calls), 1)
|
||||
|
||||
def test_allow_stdin_close(self):
|
||||
"""
|
||||
If --allow-stdin-close is specified then closing stdin doesn't
|
||||
stop the process
|
||||
"""
|
||||
with AlternateReactor(self.reactor):
|
||||
service = self._make_daemon(["--allow-stdin-close"])
|
||||
service.startService()
|
||||
self._run_daemon()
|
||||
self._close_stdin()
|
||||
self.assertEqual(self.stop_calls, [])
|
||||
|
||||
|
||||
class RunTests(SyncTestCase):
|
||||
"""
|
||||
Tests for ``run``.
|
||||
|
Loading…
Reference in New Issue
Block a user