diff -rN -u old-tahoe-maybestarted-71/src/allmydata/client.py new-tahoe-maybestarted-71/src/allmydata/client.py --- old-tahoe-maybestarted-71/src/allmydata/client.py 2007-11-08 10:53:54.000000000 -0800 +++ new-tahoe-maybestarted-71/src/allmydata/client.py 2007-11-08 10:53:54.000000000 -0800 @@ -136,6 +136,7 @@ ic.setServiceParent(self) self.register_control() + self.log("The node is now running.") def register_control(self): c = ControlServer() diff -rN -u old-tahoe-maybestarted-71/src/allmydata/introducer.py new-tahoe-maybestarted-71/src/allmydata/introducer.py --- old-tahoe-maybestarted-71/src/allmydata/introducer.py 2007-11-08 10:53:54.000000000 -0800 +++ new-tahoe-maybestarted-71/src/allmydata/introducer.py 2007-11-08 10:53:54.000000000 -0800 @@ -58,12 +58,17 @@ service.Service.startService(self) self.introducer_reconnector = self.tub.connectTo(self.introducer_furl, self._got_introducer) + # the reconnector does all the actual work. We do a separate + # getReference here just for logging the first attempt + def connect_succeeded(res): + self.log("Initial Introducer connection succeeded: " + "we are now on the grid.") def connect_failed(failure): + self.log(str(failure)) self.log("\n\nInitial Introducer connection failed: " "perhaps it's down\n") - self.log(str(failure)) d = self.tub.getReference(self.introducer_furl) - d.addErrback(connect_failed) + d.addCallbacks(connect_succeeded, connect_failed) def log(self, msg): self.parent.log(msg) diff -rN -u old-tahoe-maybestarted-71/src/allmydata/scripts/runner.py new-tahoe-maybestarted-71/src/allmydata/scripts/runner.py --- old-tahoe-maybestarted-71/src/allmydata/scripts/runner.py 2007-11-08 10:53:54.000000000 -0800 +++ new-tahoe-maybestarted-71/src/allmydata/scripts/runner.py 2007-11-08 10:53:54.000000000 -0800 @@ -34,6 +34,7 @@ command = config.subCommand so = config.subOptions + so['quiet'] = config['quiet'] if config['quiet']: stdout = StringIO() diff -rN -u old-tahoe-maybestarted-71/src/allmydata/scripts/startstop_node.py new-tahoe-maybestarted-71/src/allmydata/scripts/startstop_node.py --- old-tahoe-maybestarted-71/src/allmydata/scripts/startstop_node.py 2007-11-08 10:53:54.000000000 -0800 +++ new-tahoe-maybestarted-71/src/allmydata/scripts/startstop_node.py 2007-11-08 10:53:54.000000000 -0800 @@ -28,6 +28,13 @@ ["profile", "p", "whether to run under the Python profiler, putting results in \"profiling_results.prof\""], ] +def do_start2(basedir, config): + from allmydata.scripts import startup + fileutil.make_dirs(os.path.join(basedir, "logs")) + startup.start(basedir, config) + + + def do_start(basedir, profile=False, out=sys.stdout, err=sys.stderr): print >>out, "STARTING", basedir if os.path.exists(os.path.join(basedir, "client.tac")): @@ -125,8 +132,11 @@ def start(config, stdout, stderr): rc = 0 - for basedir in config['basedirs']: - rc = do_start(basedir, config['profile'], stdout, stderr) or rc + if len(config['basedirs']) > 1: + for basedir in config['basedirs']: + rc = do_start(basedir, config['profile'], stdout, stderr) or rc + else: + rc = do_start2(config['basedirs'][0], config) return rc def stop(config, stdout, stderr): diff -rN -u old-tahoe-maybestarted-71/src/allmydata/scripts/startup.py new-tahoe-maybestarted-71/src/allmydata/scripts/startup.py --- old-tahoe-maybestarted-71/src/allmydata/scripts/startup.py 1969-12-31 16:00:00.000000000 -0800 +++ new-tahoe-maybestarted-71/src/allmydata/scripts/startup.py 2007-11-08 10:53:54.000000000 -0800 @@ -0,0 +1,210 @@ + +import os, sys, time +from twisted.internet import task, defer, reactor +from twisted.python.runtime import platformType +from twisted.python.failure import Failure +from twisted.protocols.basic import LineOnlyReceiver + +class ClientTimeoutError(Exception): + pass +class ClientFailedToConnectError(Exception): + pass + +class FakeTransport: + disconnecting = False + +class LogWatcher(LineOnlyReceiver): + POLL_INTERVAL = 0.1 + TIMEOUT_DELAY = 10.0 + delimiter = os.linesep + + def __init__(self, logfile, processtype): + self.logfile = logfile + self.in_startup = False + self.transport = FakeTransport() + self.f = None + self.processtype = processtype + + def start(self): + # return a Deferred that fires when the startup process has finished. + # It errbacks with TimeoutError if the finish line has not been seen + # within 10 seconds, and with ClientFailedToConnectError if the error + # line was seen. If the logfile could not be opened, it errbacks with + # an IOError. + self.running = True + d = defer.maybeDeferred(self._start) + return d + + def _start(self): + self.d = defer.Deferred() + try: + self.f = open(self.logfile, "rb") + self.f.seek(0, 2) # start watching from the end + except IOError: + pass + reactor.callLater(self.TIMEOUT_DELAY, self.timeout) + self.poller = task.LoopingCall(self.poll) + self.poller.start(self.POLL_INTERVAL) + return self.d + + def timeout(self): + self.d.errback(ClientTimeoutError()) + + def finished(self, results): + self.running = False + self.in_startup = False + self.d.callback(results) + + def lineReceived(self, line): + if not self.running: + return + if "Node constructed. tahoe version:" in line: + self.in_startup = True + if self.processtype == "client": + if "Initial Introducer connection succeeded:" in line: + print "CHILD:", line + self.finished(self.processtype) + return + if "Initial Introducer connection failed:" in line: + print "CHILD:", line + self.finished(Failure(ClientFailedToConnectError())) + return + else: + if "The node is now running." in line: + print "CHILD:", line + self.finished(self.processtype) + return + + if self.in_startup: + print "CHILD:", line + + def poll(self): + if not self.f: + try: + self.f = open(self.logfile, "rb") + except IOError: + return + while True: + data = self.f.read(1000) + if not data: + return + self.dataReceived(data) + +class Follower: + def follow(self, logfile, processtype): + self.rc = 0 + print "Following twistd.log until startup finished.." + lw = LogWatcher(logfile, processtype) + d = lw.start() + d.addCallbacks(self._success, self._failure) + reactor.run() + return self.rc + + def _success(self, processtype): + print "The %s appears to have (re)started correctly." % processtype + self.rc = 0 + reactor.stop() + + def _failure(self, why): + if why.check(ClientTimeoutError): + print """ +The node took more than 10 seconds to start, so we were unable to confirm +that it started correctly. Please 'tail logs/twistd.log' and look for a line +that says 'The node is now running.' to verify correct startup. +""" + else: + print """ +Unable to confirm that the node started correctly. You may need to stop it, +modify the configuration, and restart. +""" + print why + self.rc = 1 + reactor.stop() + +def get_node_type(basedir): + if os.path.exists(os.path.join(basedir, "client.tac")): + tac = "client.tac" + processtype = "client" + elif os.path.exists(os.path.join(basedir, "tahoe-client.tac")): + tac = "tahoe-client.tac" + processtype = "client" + elif os.path.exists(os.path.join(basedir, "introducer.tac")): + tac = "introducer.tac" + processtype = "introducer" + elif os.path.exists(os.path.join(basedir, "tahoe-introducer.tac")): + tac = "tahoe-introducer.tac" + processtype = "introducer" + else: + tac = None + processtype = None + return tac, processtype + +def launch(basedir, config): + # we are in a child process, so nothing we do will affect the parent + + # see if we can launch the application without actually having to + # spawn twistd, since spawning processes correctly is a real hassle + # on windows. + err = sys.stderr + + tac, processtype = get_node_type(basedir) + if processtype == None: + print >>err, "%s does not look like a node directory" % basedir + if not os.path.isdir(basedir): + print >>err, " in fact, it doesn't look like a directory at all!" + return 1 + + os.chdir(basedir) + + argv = ["twistd", + "--python", tac, + "--logfile", os.path.join("logs", "twistd.log"), + ] + if platformType == "win32": + argv.append("--reactor=win32") + sys.argv = argv + #print "in child, argv is", argv + + if True: + # this approach requires twisted-2.5.0 + from twisted.scripts import twistd + run = twistd.run + else: + # this is copied from bin/twistd. twisted-2.0.0 uses _twistw. + if platformType == "win32": + from twisted.scripts import _twistw + run = _twistw.run + else: + from twisted.scripts import twistd + run = twistd.run + #print "in child, about to call run()" + run() # this never returns, right? + sys.exit(0) # just in case it does + + +def start(basedir, config): + if config['quiet']: + return launch(basedir, config) + + # we probably can't do this os.fork under windows + if platformType == "win32": + return launch(basedir, config) + + # fork a child to launch the daemon, while the parent process tails the + # logfile + if os.fork(): + # this is the parent + logfile = os.path.join(basedir, "logs", "twistd.log") + tac, processtype = get_node_type(basedir) + rc = Follower().follow(logfile, processtype) + sys.exit(rc) + # this is the child: give the logfile-watching parent a chance to start + # watching it before we start the daemon. We want to make extra sure that + # the child never returns. + try: + time.sleep(0.2) + launch(basedir, config) + except: + sys.exit(-1) + +