def launchServer(pipeline, options):
    # first follow up on the previously reported total number of 
    # stages in the pipeline with how many have already finished:
    pipeline.printNumberProcessedStages()

    # expensive, so only create for pipelines that will actually run
    pipeline.shutdown_ev = Event()

    # for ideological reasons this should live in a method, but pipeline init is
    # rather baroque anyway, and arguably launchServer/pipelineDaemon ought to be
    # a single method with cleaned-up initialization
    #executors_local = pipeline.options.local or (pipeline.options.queue_type is None)
    executors_local = pipeline.options.queue_type in [None, 'pbs']
    if executors_local:
        # measured once -- we assume that server memory usage will be
        # roughly constant at this point
        pipeline.memAvail = pipeline.options.mem - (float(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss) / 10**6)  # 2^20?
    else:
        pipeline.memAvail = pipeline.options.mem
    
    # is the server going to be verbose or not?
    if options.verbose:
        def verboseprint(*args):
            # Print each argument separately so caller doesn't need to
            # stuff everything to be printed into a single string
            for arg in args:
                print(arg,)
            print()
    else:
        verboseprint = lambda *a: None
    
    # getIpAddress is similar to socket.gethostbyname(...)
    # but uses a hack to attempt to avoid returning localhost (127....)
    network_address = Pyro4.socketutil.getIpAddress(socket.gethostname(),
                                                    workaround127 = True, ipVersion = 4)
    daemon = Pyro4.core.Daemon(host=network_address)
    pipelineURI = daemon.register(pipeline)
    
    if options.use_ns:
        # in the future we might want to launch a nameserver here
        # instead of relying on a separate executable running
        ns = Pyro4.locateNS()
        ns.register("pipeline", pipelineURI)
    else:
        # If not using Pyro NameServer, must write uri to file for reading by client.
        uf = open(options.urifile, 'w')
        uf.write(pipelineURI.asString())
        uf.close()
    
    pipeline.setVerbosity(options.verbose)

    shutdown_time = pe.WAIT_TIMEOUT + pipeline.options.latency_tolerance
    
    try:
        # start Pyro server
        t = Process(target=daemon.requestLoop)
        # t.daemon = True # this isn't allowed
        t.start()

        # at this point requests made to the Pyro daemon will touch process `t`'s copy
        # of the pipeline, so modifiying `pipeline` won't have any effect.  The exception is
        # communication through its multiprocessing.Event, which we use below to wait
        # for termination.
        #FIXME does this leak the memory used by the old pipeline?
        #if so, avoid doing this or at least `del` the old graph ...

        verboseprint("Daemon is running at: %s" % daemon.locationStr)
        logger.info("Daemon is running at: %s", daemon.locationStr)
        verboseprint("The pipeline's uri is: %s" % str(pipelineURI))
        logger.info("The pipeline's uri is: %s", str(pipelineURI))

        # handle SIGTERM (sent by SciNet 15-30s before hard kill) by setting
        # the shutdown event (we shouldn't actually see a SIGTERM on PBS
        # since PBS submission logic gives us a lifetime related to our walltime
        # request ...)
        def handler(sig, _stack):
            pipeline.shutdown_ev.set()
        signal.signal(signal.SIGTERM, handler)

        # spawn a loop to manage executors in a separate process
        # (here we use a proxy to make calls to manageExecutors because (a) 
        # processes don't share memory, (b) so that its logic is
        # not interleaved with calls from executors.  We could instead use a `select`
        # for both Pyro and non-Pyro socket events; see the Pyro documentation)
        p = Pyro4.Proxy(pipelineURI)
        def loop():
            try:
                logger.debug("Auxiliary loop started")
                logger.debug("memory limit: %dG; available after server overhead: %.4fG" % (pipeline.options.mem, pipeline.memAvail))
                while p.continueLoop():
                    p.manageExecutors()
                    pipeline.shutdown_ev.wait(LOOP_INTERVAL)
            except:
                logger.exception("Server loop encountered a problem.  Shutting down.")
            finally:
                logger.info("Server loop going to shut down ...")
                p.set_shutdown_ev()

        h = Process(target=loop)
        h.daemon = True
        h.start()

        try:
            jid    = os.environ["PBS_JOBID"]
            output = check_output(['qstat', '-f', jid])

            time_left = int(re.search('Walltime.Remaining = (\d*)', output).group(1))
            logger.debug("Time remaining: %d s" % time_left)
            time_to_live = time_left - shutdown_time
        except:
            logger.info("I couldn't determine your remaining walltime from qstat.")
            time_to_live = None
        flag = pipeline.shutdown_ev.wait(time_to_live)
        if not flag:
            logger.info("Time's up!")
        pipeline.shutdown_ev.set()

    # FIXME if we terminate abnormally, we should _actually_ kill child executors (if running locally)
    except __HOLE__:
        logger.exception("Caught keyboard interrupt, killing executors and shutting down server.")
        print("\nKeyboardInterrupt caught: cleaning up, shutting down executors.\n")
        sys.stdout.flush()
    except:
        logger.exception("Exception running server in daemon.requestLoop. Server shutting down.")
        print("%s" % sys.exc_info())
    else:
        # allow time for all clients to contact the server and be told to shut down
        # (we could instead add a way for the server to notify its registered clients):
        # otherwise they will crash when they try to contact the (shutdown) server.
        # It's not important that clients shut down properly (if they see a server crash, they
        # will cancel their running jobs, but they're done by the time the server exits)
        # TODO this only makes sense if we are actually shutting down nicely,
        # and not because we're out of walltime, in which case this doesn't help
        # (client jobs will die anyway)
        #print("Sleeping %d s to allow time for clients to shutdown..." % pe.SHUTDOWN_TIME)
        #time.sleep(pe.SHUTDOWN_TIME)
        # trying to access variables from `p` in the `finally` clause (in order
        # to print a shutdown message) hangs for some reason, so do it here instead
        p.printShutdownMessage()
    finally:
        # brutal, but awkward to do with our system of `Event`s
        # could send a signal to `t` instead:
        t.terminate()
dataset/ETHPy150Open Mouse-Imaging-Centre/pydpiper/pydpiper/pipeline.py/launchServer