Odoo启动过程

openerp-server是启动Odoo服务器的第一步,其代码如下。

#!/usr/bin/env python
import openerp

if __name__ == "__main__":
    openerp.cli.main()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

调用了openerp.cli.main()

openerp.cli.main

...
import server
import deploy
import scaffold
import start
def main():
    args = sys.argv[1:]
  # sys.argv:启动文件和配置文件的路径['D:/rhwl/odoo/odoo.py', '-c', 'D:\\rhwl\\local.conf']
# The only shared option is '--addons-path=' needed to discover additional # commands from modules if len(args) > 1 and args[0].startswith('--addons-path=') and not args[1].startswith("-"): # parse only the addons-path, do not setup the logger... tools.config._parse_config([args[0]])#设置路径 args = args[1:] # Default legacy command command = "server" # Subcommand discovery if len(args) and not args[0].startswith("-"): logging.disable(logging.CRITICAL) for m in module.get_modules(): m = 'openerp.addons.' + m __import__(m)#导入模块 #try: #except Exception, e: # raise # print e logging.disable(logging.NOTSET) command = args[0] args = args[1:] if command in commands: o = commands[command]() o.run(args)#运行server

openerp.cli.main() 通过tools.config._parse_config([args[0]])设计模块路径,通过最后一行o.run(args)运行server

openerp.cli.server

def main(args):
    check_root_user()
    openerp.tools.config.parse_config(args)
    check_postgres_user()
    report_configuration()

    config = openerp.tools.config

    if config["test_file"]:
        config["test_enable"] = True

    if config["translate_out"]:
        export_translation()
        sys.exit(0)

    if config["translate_in"]:
        import_translation()
        sys.exit(0)

    # This needs to be done now to ensure the use of the multiprocessing
    # signaling mecanism for registries loaded with -d
    if config['workers']:
        openerp.multi_process = True

    preload = []
    if config['db_name']:
        preload = config['db_name'].split(',')

    stop = config["stop_after_init"]

    setup_pid_file()
    rc = openerp.service.server.start(preload=preload, stop=stop)
    sys.exit(rc)

class Server(Command):
    """Start the odoo server (default command)"""
    def run(self, args):
        main(args)

通过openerp.service.server.start(preload=preload, stop=stop)启动Odoo服务器

openerp.service.server.start

def start(preload=None, stop=False):
    """ Start the openerp http server and cron processor.
    """
    global server
    load_server_wide_modules()
    if openerp.evented:
        server = GeventServer(openerp.service.wsgi_server.application)
    elif config['workers']:
        server = PreforkServer(openerp.service.wsgi_server.application)
    else:
        server = ThreadedServer(openerp.service.wsgi_server.application)

    if config['auto_reload']:
        autoreload = AutoReload(server)
        autoreload.run()

    rc = server.run(preload, stop)#在这里运行服务器

    # like the legend of the phoenix, all ends with beginnings
    if getattr(openerp, 'phoenix', False):
        modules = []
        if config['auto_reload']:
            modules = autoreload.modules.keys()
        _reexec(modules)

    return rc if rc else 0

openerp.service.server.start启动opernerp服务器和cron进程

从代码中可以看到,odoo支持三种服务类型:

    • GeventServer
    • PreforkServer
    • ThreadedServer 
      其中,ThreadedServer为默认的服务器类型

Odoo服务器通过ThreadedServer.run()开始运行

ThreadedServer.run

def run(self, preload=None, stop=False):
        """ Start the http server and the cron thread then wait for a signal.

        The first SIGINT or SIGTERM signal will initiate a graceful shutdown while
        a second one if any will force an immediate exit.
        """
        self.start(stop=stop)

        rc = preload_registries(preload)

        if stop:
            self.stop()
            return rc

        # Wait for a first signal to be handled. (time.sleep will be interrupted
        # by the signal handler.) The try/except is for the win32 case.
        try:
            while self.quit_signals_received == 0:
                time.sleep(60)
        except KeyboardInterrupt:
            pass

        self.stop()

ThreadedServer.start()

start()方法如下。

def start(self, stop=False):
        _logger.debug("Setting signal handlers")
        if os.name == 'posix':
            signal.signal(signal.SIGINT, self.signal_handler)
            signal.signal(signal.SIGTERM, self.signal_handler)
            signal.signal(signal.SIGCHLD, self.signal_handler)
            signal.signal(signal.SIGHUP, self.signal_handler)
            signal.signal(signal.SIGQUIT, dumpstacks)
        elif os.name == 'nt':
            import win32api
            win32api.SetConsoleCtrlHandler(lambda sig: self.signal_handler(sig, None), 1)

        test_mode = config['test_enable'] or config['test_file']
        if test_mode or (config['xmlrpc'] and not stop):
            # some tests need the http deamon to be available...
            self.http_spawn()

        if not stop:
            # only relevant if we are not in "--stop-after-init" mode
            self.cron_spawn()

在默认参数下,将执行self.http_spawn()

ThreadedServer.http_spawn

def http_thread(self):
        def app(e, s):
            return self.app(e, s)
        self.httpd = ThreadedWSGIServerReloadable(self.interface, self.port, app)
        self.httpd.serve_forever()

    def http_spawn(self):
        t = threading.Thread(target=self.http_thread, name="openerp.service.httpd")
        t.setDaemon(True)
        t.start()
        _logger.info('HTTP service (werkzeug) running on %s:%s', self.interface, self.port)

 

http_spawn启动命令为openerp.service.httpd的线程,并在控制台输入'HTTP service (werkzeug) running...的提示信息。

ThreadedWSGIServerReloadable

http_thread线程中,将调用ThreadedWSGIServerReloadable

class ThreadedWSGIServerReloadable(LoggingBaseWSGIServerMixIn, werkzeug.serving.ThreadedWSGIServer):
    """ werkzeug Threaded WSGI Server patched to allow reusing a listen socket
    given by the environement, this is used by autoreload to keep the listen
    socket open when a reload happens.
    """
    def __init__(self, host, port, app):
        super(ThreadedWSGIServerReloadable, self).__init__(host, port, app,
                                                           handler=RequestHandler)

    def server_bind(self):
        envfd = os.environ.get('LISTEN_FDS')
        if envfd and os.environ.get('LISTEN_PID') == str(os.getpid()):
            self.reload_socket = True
            self.socket = socket.fromfd(int(envfd), socket.AF_INET, socket.SOCK_STREAM)
            # should we os.close(int(envfd)) ? it seem python duplicate the fd.
        else:
            self.reload_socket = False
            super(ThreadedWSGIServerReloadable, self).server_bind()

    def server_activate(self):
        if not self.reload_socket:
            super(ThreadedWSGIServerReloadable, self).server_activate()

ThreadedWSGIServerReloadablewerkzeug.serving.ThreadedWSGIServer继承而来,作为参数的app为openerp.service.server.start传递的openerp.service.wsgi_server.application()函数

openerp.service.wsgi_server.application

def application(environ, start_response):
    if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
        return werkzeug.contrib.fixers.ProxyFix(application_unproxied)(environ, start_response)
    else:
        return application_unproxied(environ, start_response)

默认参数的情况下,将调用application_unproxied()

openerp.service.wsgi_server.application_unproxied

def application_unproxied(environ, start_response):
    """ WSGI entry point."""
    # cleanup db/uid trackers - they're set at HTTP dispatch in
    # web.session.OpenERPSession.send() and at RPC dispatch in
    # openerp.service.web_services.objects_proxy.dispatch().
    # /!\ The cleanup cannot be done at the end of this `application`
    # method because werkzeug still produces relevant logging afterwards 
    if hasattr(threading.current_thread(), 'uid'):
        del threading.current_thread().uid
    if hasattr(threading.current_thread(), 'dbname'):
        del threading.current_thread().dbname

    with openerp.api.Environment.manage():
        # Try all handlers until one returns some result (i.e. not None).
        wsgi_handlers = [wsgi_xmlrpc]
        wsgi_handlers += module_handlers
        for handler in wsgi_handlers:
            result = handler(environ, start_response)
            if result is None:
                continue
            return result

    # We never returned from the loop.
    response = 'No handler found.\n'
    start_response('404 Not Found', [('Content-Type', 'text/plain'), ('Content-Length', str(len(response)))])
    return [response]

application_unproxied函数查找合适的回调函数对HTTP访问进行处理。回调函数来源于两个部分: 
- wsgi_xmlrpc 
- module_handlers 
其中,module_handlers是WSGI回调函数列表。系统在执行import openerp的过程中已将Root类添加到了WSGI回调表中。代码在openerp.http

# register main wsgi handler
root = Root()
openerp.service.wsgi_server.register_wsgi_handler(root)

 

openerp.http.Root

Root类是 OpenERP Web客户端的WSGI应用,处理HTTP请求的接口为__call__()

def __call__(self, environ, start_response):
        """ Handle a WSGI request
        """
        if not self._loaded:
            self._loaded = True
            self.load_addons()
        return self.dispatch(environ, start_response)

可见,dispatch()是处理HTTP请求的核心方法

def dispatch(self, environ, start_response):
        """
        Performs the actual WSGI dispatching for the application.
        """
        try:
            httprequest = werkzeug.wrappers.Request(environ)
            httprequest.app = self

            explicit_session = self.setup_session(httprequest)
            self.setup_db(httprequest)
            self.setup_lang(httprequest)

            request = self.get_request(httprequest)

            def _dispatch_nodb():
                try:
                    func, arguments = self.nodb_routing_map.bind_to_environ(request.httprequest.environ).match()
                except werkzeug.exceptions.HTTPException, e:
                    return request._handle_exception(e)
                request.set_handler(func, arguments, "none")
                result = request.dispatch()
                return result

            with request:
                db = request.session.db
                if db:
                    openerp.modules.registry.RegistryManager.check_registry_signaling(db)
                    try:
                        with openerp.tools.mute_logger('openerp.sql_db'):
                            ir_http = request.registry['ir.http']
                    except (AttributeError, psycopg2.OperationalError):
                        # psycopg2 error or attribute error while constructing
                        # the registry. That means the database probably does
                        # not exists anymore or the code doesnt match the db.
                        # Log the user out and fall back to nodb
                        request.session.logout()
                        result = _dispatch_nodb()
                    else:
                        result = ir_http._dispatch()
                        openerp.modules.registry.RegistryManager.signal_caches_change(db)
                else:
                    result = _dispatch_nodb()

                response = self.get_response(httprequest, result, explicit_session)
            return response(environ, start_response)

        except werkzeug.exceptions.HTTPException, e:
            return e(environ, start_response)

dispatch将通过setup_session()方法恢复或者创建sessionsession保存在openerp.tools.config.session_dir所指明的目录下。 
dispatch()中,通过 httprequest = werkzeug.wrappers.Request(environ)得到WSGIRequest对象,并对werkzeug.wrappers.Request类通过HttpRequest类进行包装。 
通过bind_to_environ函数,得到当前Http请求对应的回调函数。并将回调函数通过request.set_handler(func, arguments, "none")传递给request。紧接着通过ir_http = request.registry['ir.http']得到一个ir_http对象

Odoo 模块加载

request.registry

request.registry定义如下:

class WebRequest(object):
    ...
    @property
    def registry(self):
        """
        The registry to the database linked to this request. Can be ``None``
        if the current request uses the ``none`` authentication.

        .. deprecated:: 8.0

            use :attr:`.env`
        """
        return openerp.modules.registry.RegistryManager.get(self.db) if self.db else None

RegistryManager.get()从指定的数据库名称中返回一个registry对象,其定义如下。

@classmethod
    def get(cls, db_name, force_demo=False, status=None, update_module=False):
        """ Return a registry for a given database name."""
        with cls.lock():
            try:
                return cls.registries[db_name]
            except KeyError:
                return cls.new(db_name, force_demo, status,
                               update_module)
            finally:
                # set db tracker - cleaned up at the WSGI
                # dispatching phase in openerp.service.wsgi_server.application
                threading.current_thread().dbname = db_name

新建Registry的代码如下,此时应当为加载一个新的数据库。

 @classmethod
    def new(cls, db_name, force_demo=False, status=None,
            update_module=False):
        """ Create and return a new registry for a given database name.

        The (possibly) previous registry for that database name is discarded.

        """
        import openerp.modules
        with cls.lock():
            with openerp.api.Environment.manage():
                registry = Registry(db_name)

                # Initializing a registry will call general code which will in
                # turn call registries.get (this object) to obtain the registry
                # being initialized. Make it available in the registries
                # dictionary then remove it if an exception is raised.
                cls.delete(db_name)
                cls.registries[db_name] = registry
                try:
                    with registry.cursor() as cr:
                        seq_registry, seq_cache = Registry.setup_multi_process_signaling(cr)
                        registry.base_registry_signaling_sequence = seq_registry
                        registry.base_cache_signaling_sequence = seq_cache
                    # This should be a method on Registry
                    openerp.modules.load_modules(registry._db, force_demo, status, update_module)
                except Exception:
                    del cls.registries[db_name]
                    raise

                # load_modules() above can replace the registry by calling
                # indirectly new() again (when modules have to be uninstalled).
                # Yeah, crazy.
                registry = cls.registries[db_name]

                cr = registry.cursor()
                try:
                    registry.do_parent_store(cr)
                    cr.commit()
                finally:
                    cr.close()

        registry.ready = True

        if update_module:
            # only in case of update, otherwise we'll have an infinite reload loop!
            cls.signal_registry_change(db_name)
        return registry

openerp.modules.load_modules

加载模块

#openerp.modules.load_modules(registry._db, force_demo, status, update_module)
def load_modules(db, force_demo=False, status=None, update_module=False):
    ...
        # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) 
        graph = openerp.modules.graph.Graph()
        graph.add_module(cr, 'base', force)
        ...
        loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)        

        # STEP 2: Mark other modules to be loaded/updated
        ...
        # STEP 3: Load marked modules (skipping base which was done in STEP 1)
        # IMPORTANT: this is done in two parts, first loading all installed or
        #            partially installed modules (i.e. installed/to upgrade), to
        #            offer a consistent system to the second part: installing
        #            newly selected modules.
        #            We include the modules 'to remove' in the first step, because
        #            they are part of the "currently installed" modules. They will
        #            be dropped in STEP 6 later, before restarting the loading
        #            process.
        # IMPORTANT 2: We have to loop here until all relevant modules have been
        #              processed, because in some rare cases the dependencies have
        #              changed, and modules that depend on an uninstalled module
        #              will not be processed on the first pass.
        #              It's especially useful for migrations.
            ...
            if update_module:
                processed_modules += load_marked_modules(cr, graph,
                    ['to install'], force, status, report,
                    loaded_modules, update_module)          
            ...
        # STEP 4: Finish and cleanup installations
        ...
        # STEP 5: Cleanup menus 
        # Remove menu items that are not referenced by any of other
        # (child) menu item, ir_values, or ir_model_data.
        # TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
        ...
        # STEP 6: Uninstall modules to remove
        # Remove records referenced from ir_model_data for modules to be
        # removed (and removed the references from ir_model_data).
           ...

        # STEP 7: verify custom views on every model
        ...
        # STEP 8: call _register_hook on every model
        ...
        # STEP 9: Run the post-install tests
      ...

opernerp.modules.load_module_graph()

for index, package in enumerate(graph):
        module_name = package.name
        module_id = package.id
        ...
        load_openerp_module(package.name)
        new_install = package.installed_version is None
        if new_install:
            py_module = sys.modules['openerp.addons.%s' % (module_name,)]
            pre_init = package.info.get('pre_init_hook')
            if pre_init:
                getattr(py_module, pre_init)(cr)

        models = registry.load(cr, package)#加载模块
        loaded_modules.append(package.name)
        if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
            registry.setup_models(cr, partial=True)
            init_module_models(cr, package.name, models)

        # Can't put this line out of the loop: ir.module.module will be
        # registered by init_module_models() above.
        modobj = registry['ir.module.module']

       ...

openerp.modules.load_openerp_module()

def load_openerp_module(module_name):
    global loaded
    if module_name in loaded:
        return

    initialize_sys_path()
    try:
        mod_path = get_module_path(module_name)
        __import__('openerp.addons.' + module_name)       
        info = load_information_from_description_file(module_name)
        if info['post_load']:
            getattr(sys.modules['openerp.addons.' + module_name], info['post_load'])()

    except Exception, e:
        msg = "Couldn't load module %s" % (module_name)
        _logger.critical(msg)
        _logger.critical(e)
        raise
    else:
        loaded.append(module_name)

openerp.modules.init_module_models()

def init_module_models(cr, module_name, obj_list):
    _logger.info('module %s: creating or updating database tables', module_name)
    todo = []
    for obj in obj_list:
        result = obj._auto_init(cr, {'module': module_name})
        ...
    cr.commit()

通过obj._auto_end(cr, {'module': module_name})初始化数据库表

def _auto_init(self, cr, context=None):
    ...
        self._field_create(cr, context=context)#添加ir_model、ir_model_fields和ir_model_data数据项
        create = not self._table_exist(cr)
        ...
             self._create_table(cr)
                ...

openerp.modules.registry.load()

def load(self, cr, module):
        ...
        from .. import models

        models_to_load = [] # need to preserve loading order
        lazy_property.reset_all(self)
        for cls in models.MetaModel.module_to_models.get(module.name, []):
            model = cls._build_model(self, cr)#实例化模块
            if model._name not in models_to_load:               
                models_to_load.append(model._name)
        return [self.models[m] for m in models_to_load]

openrp.Models.build_model()

openerp.modules.registry将自身作为pool传递给_build_model

@classmethod
    def _build_model(cls, pool, cr):       
        ...
        # instantiate the model, and initialize it
        model = object.__new__(cls)
        model.__init__(pool, cr)
        return model

 

posted @ 2017-06-14 15:08  553490191  阅读(4566)  评论(0编辑  收藏  举报