• 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.
    '
        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
  • 相关阅读:
    逆向工程IL指令集
    关于最近电话面试的体会
    推荐阅读《小就是大》(small is the new big)
    [ZZ]google v. microsoft, and the dev:test ratio debate
    我所用到的Google产品
    [ZZ]7 Useful Tools for Web Development Testing
    拼车新模式
    [ZZ]读《移山之道》后有感
    Google财经香港版上线,香港路演
    [ZZ]从Google到“谷歌”
  • 原文地址:https://www.cnblogs.com/dancesir/p/7008967.html
Copyright © 2020-2023  润新知