Ticket #71: prototype.diff

File prototype.diff, 10.5 KB (added by warner, at 2007-11-08T18:55:17Z)

prototype implementation

  • src/allmydata/client.py

    diff -rN -u old-tahoe-maybestarted-71/src/allmydata/client.py new-tahoe-maybestarted-71/src/allmydata/client.py
    old new  
    136136        ic.setServiceParent(self)
    137137
    138138        self.register_control()
     139        self.log("The node is now running.")
    139140
    140141    def register_control(self):
    141142        c = ControlServer()
  • src/allmydata/introducer.py

    diff -rN -u old-tahoe-maybestarted-71/src/allmydata/introducer.py new-tahoe-maybestarted-71/src/allmydata/introducer.py
    old new  
    5858        service.Service.startService(self)
    5959        self.introducer_reconnector = self.tub.connectTo(self.introducer_furl,
    6060                                                         self._got_introducer)
     61        # the reconnector does all the actual work. We do a separate
     62        # getReference here just for logging the first attempt
     63        def connect_succeeded(res):
     64            self.log("Initial Introducer connection succeeded: "
     65                     "we are now on the grid.")
    6166        def connect_failed(failure):
     67            self.log(str(failure))
    6268            self.log("\n\nInitial Introducer connection failed: "
    6369                     "perhaps it's down\n")
    64             self.log(str(failure))
    6570        d = self.tub.getReference(self.introducer_furl)
    66         d.addErrback(connect_failed)
     71        d.addCallbacks(connect_succeeded, connect_failed)
    6772
    6873    def log(self, msg):
    6974        self.parent.log(msg)
  • src/allmydata/scripts/runner.py

    diff -rN -u old-tahoe-maybestarted-71/src/allmydata/scripts/runner.py new-tahoe-maybestarted-71/src/allmydata/scripts/runner.py
    old new  
    3434
    3535    command = config.subCommand
    3636    so = config.subOptions
     37    so['quiet'] = config['quiet']
    3738
    3839    if config['quiet']:
    3940        stdout = StringIO()
  • src/allmydata/scripts/startstop_node.py

    diff -rN -u old-tahoe-maybestarted-71/src/allmydata/scripts/startstop_node.py new-tahoe-maybestarted-71/src/allmydata/scripts/startstop_node.py
    old new  
    2828        ["profile", "p", "whether to run under the Python profiler, putting results in \"profiling_results.prof\""],
    2929        ]
    3030
     31def do_start2(basedir, config):
     32    from allmydata.scripts import startup
     33    fileutil.make_dirs(os.path.join(basedir, "logs"))
     34    startup.start(basedir, config)
     35
     36
     37
    3138def do_start(basedir, profile=False, out=sys.stdout, err=sys.stderr):
    3239    print >>out, "STARTING", basedir
    3340    if os.path.exists(os.path.join(basedir, "client.tac")):
     
    125132
    126133def start(config, stdout, stderr):
    127134    rc = 0
    128     for basedir in config['basedirs']:
    129         rc = do_start(basedir, config['profile'], stdout, stderr) or rc
     135    if len(config['basedirs']) > 1:
     136        for basedir in config['basedirs']:
     137            rc = do_start(basedir, config['profile'], stdout, stderr) or rc
     138    else:
     139        rc = do_start2(config['basedirs'][0], config)
    130140    return rc
    131141
    132142def stop(config, stdout, stderr):
  • src/allmydata/scripts/startup.py

    diff -rN -u old-tahoe-maybestarted-71/src/allmydata/scripts/startup.py new-tahoe-maybestarted-71/src/allmydata/scripts/startup.py
    old new  
     1
     2import os, sys, time
     3from twisted.internet import task, defer, reactor
     4from twisted.python.runtime import platformType
     5from twisted.python.failure import Failure
     6from twisted.protocols.basic import LineOnlyReceiver
     7
     8class ClientTimeoutError(Exception):
     9    pass
     10class ClientFailedToConnectError(Exception):
     11    pass
     12
     13class FakeTransport:
     14    disconnecting = False
     15
     16class LogWatcher(LineOnlyReceiver):
     17    POLL_INTERVAL = 0.1
     18    TIMEOUT_DELAY = 10.0
     19    delimiter = os.linesep
     20
     21    def __init__(self, logfile, processtype):
     22        self.logfile = logfile
     23        self.in_startup = False
     24        self.transport = FakeTransport()
     25        self.f = None
     26        self.processtype = processtype
     27
     28    def start(self):
     29        # return a Deferred that fires when the startup process has finished.
     30        # It errbacks with TimeoutError if the finish line has not been seen
     31        # within 10 seconds, and with ClientFailedToConnectError if the error
     32        # line was seen. If the logfile could not be opened, it errbacks with
     33        # an IOError.
     34        self.running = True
     35        d = defer.maybeDeferred(self._start)
     36        return d
     37
     38    def _start(self):
     39        self.d = defer.Deferred()
     40        try:
     41            self.f = open(self.logfile, "rb")
     42            self.f.seek(0, 2) # start watching from the end
     43        except IOError:
     44            pass
     45        reactor.callLater(self.TIMEOUT_DELAY, self.timeout)
     46        self.poller = task.LoopingCall(self.poll)
     47        self.poller.start(self.POLL_INTERVAL)
     48        return self.d
     49
     50    def timeout(self):
     51        self.d.errback(ClientTimeoutError())
     52
     53    def finished(self, results):
     54        self.running = False
     55        self.in_startup = False
     56        self.d.callback(results)
     57
     58    def lineReceived(self, line):
     59        if not self.running:
     60            return
     61        if "Node constructed.  tahoe version:" in line:
     62            self.in_startup = True
     63        if self.processtype == "client":
     64            if "Initial Introducer connection succeeded:" in line:
     65                print "CHILD:", line
     66                self.finished(self.processtype)
     67                return
     68            if "Initial Introducer connection failed:" in line:
     69                print "CHILD:", line
     70                self.finished(Failure(ClientFailedToConnectError()))
     71                return
     72        else:
     73            if "The node is now running." in line:
     74                print "CHILD:", line
     75                self.finished(self.processtype)
     76                return
     77
     78        if self.in_startup:
     79            print "CHILD:", line
     80
     81    def poll(self):
     82        if not self.f:
     83            try:
     84                self.f = open(self.logfile, "rb")
     85            except IOError:
     86                return
     87        while True:
     88            data = self.f.read(1000)
     89            if not data:
     90                return
     91            self.dataReceived(data)
     92
     93class Follower:
     94    def follow(self, logfile, processtype):
     95        self.rc = 0
     96        print "Following twistd.log until startup finished.."
     97        lw = LogWatcher(logfile, processtype)
     98        d = lw.start()
     99        d.addCallbacks(self._success, self._failure)
     100        reactor.run()
     101        return self.rc
     102
     103    def _success(self, processtype):
     104        print "The %s appears to have (re)started correctly." % processtype
     105        self.rc = 0
     106        reactor.stop()
     107
     108    def _failure(self, why):
     109        if why.check(ClientTimeoutError):
     110            print """
     111The node took more than 10 seconds to start, so we were unable to confirm
     112that it started correctly. Please 'tail logs/twistd.log' and look for a line
     113that says 'The node is now running.' to verify correct startup.
     114"""
     115        else:
     116            print """
     117Unable to confirm that the node started correctly. You may need to stop it,
     118modify the configuration, and restart.
     119"""
     120            print why
     121        self.rc = 1
     122        reactor.stop()
     123
     124def get_node_type(basedir):
     125    if os.path.exists(os.path.join(basedir, "client.tac")):
     126        tac = "client.tac"
     127        processtype = "client"
     128    elif os.path.exists(os.path.join(basedir, "tahoe-client.tac")):
     129        tac = "tahoe-client.tac"
     130        processtype = "client"
     131    elif os.path.exists(os.path.join(basedir, "introducer.tac")):
     132        tac = "introducer.tac"
     133        processtype = "introducer"
     134    elif os.path.exists(os.path.join(basedir, "tahoe-introducer.tac")):
     135        tac = "tahoe-introducer.tac"
     136        processtype = "introducer"
     137    else:
     138        tac = None
     139        processtype = None
     140    return tac, processtype
     141
     142def launch(basedir, config):
     143    # we are in a child process, so nothing we do will affect the parent
     144
     145    # see if we can launch the application without actually having to
     146    # spawn twistd, since spawning processes correctly is a real hassle
     147    # on windows.
     148    err = sys.stderr
     149
     150    tac, processtype = get_node_type(basedir)
     151    if processtype == None:
     152        print >>err, "%s does not look like a node directory" % basedir
     153        if not os.path.isdir(basedir):
     154            print >>err, " in fact, it doesn't look like a directory at all!"
     155        return 1
     156
     157    os.chdir(basedir)
     158
     159    argv = ["twistd",
     160            "--python", tac,
     161            "--logfile", os.path.join("logs", "twistd.log"),
     162            ]
     163    if platformType == "win32":
     164        argv.append("--reactor=win32")
     165    sys.argv = argv
     166    #print "in child, argv is", argv
     167
     168    if True:
     169        # this approach requires twisted-2.5.0
     170        from twisted.scripts import twistd
     171        run = twistd.run
     172    else:
     173        # this is copied from bin/twistd. twisted-2.0.0 uses _twistw.
     174        if platformType == "win32":
     175            from twisted.scripts import _twistw
     176            run = _twistw.run
     177        else:
     178            from twisted.scripts import twistd
     179            run = twistd.run
     180    #print "in child, about to call run()"
     181    run() # this never returns, right?
     182    sys.exit(0) # just in case it does
     183
     184
     185def start(basedir, config):
     186    if config['quiet']:
     187        return launch(basedir, config)
     188
     189    # we probably can't do this os.fork under windows
     190    if platformType == "win32":
     191        return launch(basedir, config)
     192
     193    # fork a child to launch the daemon, while the parent process tails the
     194    # logfile
     195    if os.fork():
     196        # this is the parent
     197        logfile = os.path.join(basedir, "logs", "twistd.log")
     198        tac, processtype = get_node_type(basedir)
     199        rc = Follower().follow(logfile, processtype)
     200        sys.exit(rc)
     201    # this is the child: give the logfile-watching parent a chance to start
     202    # watching it before we start the daemon. We want to make extra sure that
     203    # the child never returns.
     204    try:
     205        time.sleep(0.2)
     206        launch(basedir, config)
     207    except:
     208        sys.exit(-1)
     209
     210