server/server.py
author Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
Fri, 19 Mar 2010 19:21:31 +0100
changeset 4964 d9e8af8a7a42
parent 4959 2cb79b8a1aea
child 5421 8167de96c523
permissions -rw-r--r--
[source] implement storages right in the source rather than in hooks The problem is that Storage objects will most probably change entity's dictionary so that values are correctly set before the source's corresponding method (e.g. entity_added()) is called. For instance, the BFSFileStorage will change the original binary data and replace it with the destination file path in order to store the file path in the database. This change must be local to the source in order not to impact other hooks or attribute access during the transaction, the whole idea being that the same application code should work exactly the same whether or not a BFSStorage is used or not.

"""Pyro RQL server

:organization: Logilab
:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"

import os
import sys
import select
import warnings
from time import localtime, mktime

from cubicweb.cwconfig import CubicWebConfiguration
from cubicweb.server.repository import Repository

class Finished(Exception):
    """raise to remove an event from the event loop"""

class TimeEvent:
    """base event"""
    # timefunc = staticmethod(localtime)
    timefunc = localtime

    def __init__(self, absolute=None, period=None):
        # local time tuple
        if absolute is None:
            absolute = self.timefunc()
        self.absolute = absolute
        # optional period in seconds
        self.period = period

    def is_ready(self):
        """return  true if the event is ready to be fired"""
        now = self.timefunc()
        if self.absolute < now:
            return True
        return False

    def fire(self, server):
        """fire the event
        must be overridden by concrete events
        """
        raise NotImplementedError()

    def update(self):
        """update the absolute date for the event or raise a finished exception
        """
        if self.period is None:
            raise Finished
        self.absolute = localtime(mktime(self.absolute) + self.period)


class QuitEvent(TimeEvent):
    """stop the server"""
    def fire(self, server):
        server.repo.shutdown()
        server.quiting = True


class RepositoryServer(object):

    def __init__(self, config, debug=False):
        """make the repository available as a PyRO object"""
        self.config = config
        self.repo = Repository(config, debug=debug)
        self.ns = None
        self.quiting = None
        # event queue
        self.events = []
        # start repository looping tasks

    def add_event(self, event):
        """add an event to the loop"""
        self.info('adding event %s', event)
        self.events.append(event)

    def trigger_events(self):
        """trigger ready events"""
        for event in self.events[:]:
            if event.is_ready():
                self.info('starting event %s', event)
                event.fire(self)
                try:
                    event.update()
                except Finished:
                    self.events.remove(event)

    def run(self, req_timeout=5.0):
        """enter the service loop"""
        self.repo.start_looping_tasks()
        while self.quiting is None:
            try:
                self.daemon.handleRequests(req_timeout)
            except select.error:
                continue
            self.trigger_events()

    def quit(self):
        """stop the server"""
        self.add_event(QuitEvent())

    def connect(self, host='', port=0):
        """the connect method on the repository only register to pyro if
        necessary
        """
        self.daemon = self.repo.pyro_register(host)

    # server utilitities ######################################################

    def install_sig_handlers(self):
        """install signal handlers"""
        import signal
        self.info('installing signal handlers')
        signal.signal(signal.SIGINT, lambda x, y, s=self: s.quit())
        signal.signal(signal.SIGTERM, lambda x, y, s=self: s.quit())

    def daemonize(self, pid_file=None):
        """daemonize the process"""
        # fork so the parent can exist
        if (os.fork()):
            return -1
        # deconnect from tty and create a new session
        os.setsid()
        # fork again so the parent, (the session group leader), can exit.
        # as a non-session group leader, we can never regain a controlling
        # terminal.
        if (os.fork()):
            return -1
        # move to the root to avoit mount pb
        os.chdir('/')
        # set paranoid umask
        os.umask(077)
        if pid_file is not None:
            # write pid in a file
            f = open(pid_file, 'w')
            f.write(str(os.getpid()))
            f.close()
        # filter warnings
        warnings.filterwarnings('ignore')
        # close standard descriptors
        sys.stdin.close()
        sys.stdout.close()
        sys.stderr.close()

from logging import getLogger
from cubicweb import set_log_methods
LOGGER = getLogger('cubicweb.reposerver')
set_log_methods(CubicWebConfiguration, LOGGER)